Skip to main content

kvbm_physical/layout/
physical.rs

1// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Physical layout types that combine abstract layouts with storage location metadata.
5
6use crate::BlockId;
7
8use super::{
9    FullyContiguousLayout, InnerShape, LayerSeparateLayout, Layout, MemoryRegion,
10    builder::{PhysicalLayoutBuilder, PhysicalLayoutBuilderDefault},
11    serialize::{LayoutDescriptor, LayoutTypeDetails},
12};
13
14use anyhow::{Result, anyhow};
15use dynamo_memory::{
16    Buffer, MemoryDescriptor, StorageKind,
17    nixl::{MemType, NixlAgent, NixlDescriptor},
18};
19use serde::{Deserialize, Serialize};
20use std::any::Any;
21use std::sync::Arc;
22
23/// Runtime representation of a layout with its physical storage location.
24///
25/// A `PhysicalLayout` wraps an abstract [`Layout`] with information about where
26/// its memory physically resides (GPU, host, disk) and whether it's local or remote.
27/// This enables the transfer system to select appropriate copy strategies and build
28/// NIXL transfer descriptors.
29#[derive(Debug, Clone)]
30pub struct PhysicalLayout {
31    /// The abstract layout defining memory organization
32    layout: Arc<dyn Layout>,
33
34    /// Physical storage location (System, Device, Pinned, Disk)
35    location: StorageKind,
36
37    /// NIXL registration metadata
38    nixl_metadata: NixlMetadata,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct NixlMetadata {
43    agent_name: String,
44    mem_type: MemType,
45    device_id: u64,
46}
47
48impl NixlMetadata {
49    pub fn new(agent_name: String, mem_type: MemType, device_id: u64) -> Self {
50        Self {
51            agent_name,
52            mem_type,
53            device_id,
54        }
55    }
56
57    pub fn agent_name(&self) -> &str {
58        &self.agent_name
59    }
60
61    #[inline(always)]
62    pub fn mem_type(&self) -> MemType {
63        self.mem_type
64    }
65
66    #[inline(always)]
67    pub fn device_id(&self) -> u64 {
68        self.device_id
69    }
70}
71
72impl PhysicalLayout {
73    /// Create a typed builder that enforces NIXL registration.
74    pub fn builder(agent: NixlAgent) -> PhysicalLayoutBuilderDefault {
75        PhysicalLayoutBuilder::new(agent)
76    }
77
78    /// Create a new local physical layout.
79    ///
80    /// # Arguments
81    /// * `layout` - The abstract layout to wrap
82    /// * `location` - Where the layout's memory resides
83    pub(crate) fn new_local(
84        layout: Arc<dyn Layout>,
85        location: StorageKind,
86        nixl_metadata: NixlMetadata,
87    ) -> Self {
88        Self {
89            layout,
90            location,
91            nixl_metadata,
92        }
93    }
94
95    // /// Create a new remote physical layout from a descriptor.
96    // ///
97    // /// # Arguments
98    // /// * `layout` - The abstract layout to wrap
99    // /// * `location` - Where the layout's memory resides (on remote node)
100    // /// * `remote_agent` - Name of the NIXL agent on the remote node
101    // pub fn new_remote(
102    //     layout: Arc<dyn Layout>,
103    //     location: StorageKind,
104    //     remote_agent: String,
105    // ) -> Self {
106    //     let metadata = NixlMetadata::new(
107    //         remote_agent.clone(),
108    //         location.to_nixl_mem_type(),
109    //         location.device_id(),
110    //     );
111    //     let registrations = vec![RegisteredStorageMetadata::new(
112    //         metadata.agent_name().to_string(),
113    //         location,
114    //     )];
115    //     Self {
116    //         layout,
117    //         location,
118    //         locality: Locality::Remote(remote_agent),
119    //         nixl_metadata: Some(metadata),
120    //         registered: registrations,
121    //     }
122    // }
123
124    /// Get the underlying layout.
125    pub fn layout(&self) -> &Arc<dyn Layout> {
126        &self.layout
127    }
128
129    /// Get the storage location.
130    pub(crate) fn location(&self) -> StorageKind {
131        self.location
132    }
133
134    /// Get the NIXL metadata.
135    pub(crate) fn nixl_metadata(&self) -> &NixlMetadata {
136        &self.nixl_metadata
137    }
138
139    /// Get a memory region with location information.
140    ///
141    /// # Arguments
142    /// * `block_id` - Block identifier
143    /// * `layer_id` - Layer identifier
144    /// * `outer_id` - Outer dimension identifier
145    pub fn memory_region(
146        &self,
147        block_id: BlockId,
148        layer_id: usize,
149        outer_id: usize,
150    ) -> Result<MemoryRegion> {
151        self.layout.memory_region(block_id, layer_id, outer_id)
152    }
153
154    /// Serialize this physical layout for transmission to remote nodes.
155    ///
156    /// This converts the runtime `PhysicalLayout` into a `LayoutDescriptor` that
157    /// contains all information needed to reconstruct the layout on a remote node,
158    /// including layout configuration, memory descriptors, NIXL metadata, and
159    /// layout-type-specific details.
160    ///
161    /// # Returns
162    /// A serializable representation of this layout
163    pub(crate) fn to_descriptor(&self) -> Result<LayoutDescriptor> {
164        // Extract memory descriptors
165        let memory_descriptors = self
166            .layout
167            .memory_regions()
168            .iter()
169            .map(|region| MemoryRegion {
170                addr: region.addr(),
171                size: region.size(),
172            })
173            .collect();
174
175        // Get layout type details from the layout itself
176        let layout_type_details = self.layout.serialization_details();
177
178        Ok(LayoutDescriptor {
179            version: LayoutDescriptor::CURRENT_VERSION,
180            layout_config: self.layout.config().clone(),
181            location: self.location,
182            nixl_metadata: self.nixl_metadata.clone(),
183            memory_descriptors,
184            layout_type_details,
185        })
186    }
187
188    /// Reconstruct a physical layout from serialized data received from a remote node.
189    ///
190    /// This creates a new `PhysicalLayout` from a `LayoutDescriptor`. The reconstructed
191    /// layout will have memory descriptors that point to the remote node's memory,
192    /// allowing NIXL to build RDMA descriptors for remote access.
193    ///
194    /// # Arguments
195    /// * `serialized` - Serialized layout data from a remote node
196    ///
197    /// # Returns
198    /// A new `PhysicalLayout` representing the remote layout
199    ///
200    /// # Note
201    /// The memory regions in the reconstructed layout are not valid for local access;
202    /// they represent remote memory addresses and are used to build NIXL transfer descriptors.
203    pub(crate) fn from_descriptor(serialized: LayoutDescriptor) -> Result<Self> {
204        // Validate version
205        if serialized.version > LayoutDescriptor::CURRENT_VERSION {
206            return Err(anyhow!(
207                "Unsupported serialization version: {}. Maximum supported: {}",
208                serialized.version,
209                LayoutDescriptor::CURRENT_VERSION
210            ));
211        }
212
213        // Create remote memory regions from descriptors
214        let remote_regions: Vec<Arc<dyn MemoryDescriptor>> = serialized
215            .memory_descriptors
216            .iter()
217            .map(|desc| {
218                Arc::new(RemoteMemoryDescriptor {
219                    addr: desc.addr,
220                    size: desc.size,
221                    storage_kind: serialized.location,
222                    nixl_metadata: serialized.nixl_metadata.clone(),
223                }) as Arc<dyn MemoryDescriptor>
224            })
225            .collect();
226
227        // Reconstruct the layout based on type
228        let layout: Arc<dyn Layout> = match serialized.layout_type_details {
229            LayoutTypeDetails::FullyContiguous(details) => {
230                if remote_regions.len() != 1 {
231                    return Err(anyhow!(
232                        "FullyContiguous layout requires exactly 1 memory region, got {}",
233                        remote_regions.len()
234                    ));
235                }
236                let layout = FullyContiguousLayout::new_with_format(
237                    serialized.layout_config.clone(),
238                    Buffer::from_arc(remote_regions[0].clone()),
239                    details.block_format,
240                    details.kv_block_layout,
241                )?;
242                Arc::new(layout)
243            }
244            LayoutTypeDetails::LayerSeparate(details) => {
245                if remote_regions.len() != serialized.layout_config.num_layers {
246                    return Err(anyhow!(
247                        "LayerSeparate layout requires {} memory regions (one per layer), got {}",
248                        serialized.layout_config.num_layers,
249                        remote_regions.len()
250                    ));
251                }
252                let inner_shape = details
253                    .kv_block_layout
254                    .to_inner_shape()
255                    .unwrap_or(InnerShape::Unknown);
256                let layout = LayerSeparateLayout::builder()
257                    .config(serialized.layout_config.clone())
258                    .memory(remote_regions.into_iter().map(Buffer::from_arc).collect())
259                    .block_dim(details.block_dim)
260                    .inner_shape(inner_shape)
261                    .build()?;
262                Arc::new(layout)
263            }
264        };
265
266        Ok(Self {
267            layout,
268            location: serialized.location,
269            nixl_metadata: serialized.nixl_metadata,
270        })
271    }
272}
273
274/// A memory region that represents remote memory addresses.
275///
276/// This type is used when reconstructing layouts from serialized data.
277/// The addresses are not valid for local access but can be used to
278/// build NIXL transfer descriptors for remote memory access.
279#[derive(Debug)]
280struct RemoteMemoryDescriptor {
281    addr: usize,
282    size: usize,
283    storage_kind: StorageKind,
284    nixl_metadata: NixlMetadata,
285}
286
287impl MemoryDescriptor for RemoteMemoryDescriptor {
288    fn addr(&self) -> usize {
289        self.addr
290    }
291
292    fn size(&self) -> usize {
293        self.size
294    }
295
296    fn storage_kind(&self) -> StorageKind {
297        self.storage_kind
298    }
299
300    fn as_any(&self) -> &dyn Any {
301        self
302    }
303
304    fn nixl_descriptor(&self) -> Option<NixlDescriptor> {
305        Some(NixlDescriptor {
306            addr: self.addr as u64,
307            size: self.size,
308            mem_type: self.nixl_metadata.mem_type(),
309            device_id: self.nixl_metadata.device_id(),
310        })
311    }
312}