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}