Skip to main content

kvbm_physical/layout/
kv_block_layout.rs

1// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! KV Block layout types for describing dimension permutations within blocks.
5//!
6//! This module provides types for describing how dimensions are ordered within
7//! a fully contiguous KV cache block, enabling type-driven kernel selection
8//! for transfers between different layout formats.
9
10use serde::{Deserialize, Serialize};
11
12/// Symbolic dimensions that can be permuted within a block.
13///
14/// The head dimension (hd) is always innermost and not included here.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub enum BlockDim {
17    /// Number of layers (nl)
18    Layer,
19    /// Outer dimension - typically 2 for K/V, 1 for MLA (no)
20    Outer,
21    /// Page size / tokens per block (nt)
22    Page,
23    /// Number of attention heads (nh)
24    Head,
25}
26
27/// Block layout defined by dimension ordering.
28///
29/// Describes how the 4 permutable dimensions (layer, outer, page, head) are
30/// ordered within a fully contiguous block. The head dimension (hd) is always
31/// innermost and implicit.
32///
33/// The order specifies outer-to-inner dimensions, with head_dim always last.
34///
35/// # Examples
36///
37/// - `UniversalTP`: `[nh, nl, no, nt, hd]` - heads outermost for TP resharding
38/// - `OperationalNHD`: `[nl, no, nt, nh, hd]` - inner is `[nt, nh, hd]`
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
40pub enum KvBlockLayout {
41    /// Universal format: `[nh, nl, no, nt, hd]`
42    ///
43    /// Heads are outermost to enable tensor parallelism (TP) resharding.
44    /// Cache saved from one TP configuration can be loaded into another
45    /// by simply slicing the head dimension differently.
46    UniversalTP,
47
48    /// Pipeline parallelism format: `[nl, nh, no, nt, hd]`
49    ///
50    /// Layers are outermost for pipeline parallelism scenarios.
51    UniversalPP,
52
53    /// Operational HND format: `[nl, no, nh, nt, hd]`
54    ///
55    /// Inner tensor shape is `[nh, nt, hd]` (heads, tokens, head_dim).
56    OperationalHND,
57
58    /// Operational NHD format: `[nl, no, nt, nh, hd]`
59    ///
60    /// Inner tensor shape is `[nt, nh, hd]` (tokens, heads, head_dim).
61    /// This is the most common format used by vLLM and other frameworks.
62    OperationalNHD,
63
64    /// Custom ordering with explicit dimension list.
65    ///
66    /// The array specifies dimensions from outermost to innermost,
67    /// with head_dim always implicitly last.
68    Custom([BlockDim; 4]),
69
70    /// Unknown layout - fallback when format cannot be determined.
71    ///
72    /// Operations involving Unknown layouts may fail or require explicit
73    /// configuration.
74    #[default]
75    Unknown,
76}
77
78impl KvBlockLayout {
79    /// Get the dimension ordering as an array.
80    ///
81    /// Returns the 4 dimensions from outermost to innermost.
82    /// Head dimension (hd) is implicit as the innermost dimension.
83    ///
84    /// # Returns
85    /// `None` for `Unknown` layout, `Some([BlockDim; 4])` otherwise.
86    pub fn dim_order(&self) -> Option<[BlockDim; 4]> {
87        use BlockDim::*;
88        match self {
89            Self::UniversalTP => Some([Head, Layer, Outer, Page]),
90            Self::UniversalPP => Some([Layer, Head, Outer, Page]),
91            Self::OperationalHND => Some([Layer, Outer, Head, Page]),
92            Self::OperationalNHD => Some([Layer, Outer, Page, Head]),
93            Self::Custom(order) => Some(*order),
94            Self::Unknown => None,
95        }
96    }
97
98    /// Check if two layouts require transformation (not just copy).
99    ///
100    /// Returns `true` if the layouts have different dimension orderings,
101    /// meaning a transformation kernel is needed rather than a simple copy.
102    ///
103    /// For Unknown→Unknown comparisons, returns `false` (compatible) but emits
104    /// a warning so these cases can be tracked and fixed.
105    ///
106    /// Returns `true` if one is Unknown and the other is Known (conservative).
107    pub fn requires_transform(&self, other: &Self) -> bool {
108        match (self.dim_order(), other.dim_order()) {
109            (Some(a), Some(b)) => a != b,
110            (None, None) => {
111                // Unknown→Unknown is compatible, but warn so we can fix these
112                tracing::warn!("Unknown→Unknown KvBlockLayout comparison - this should be fixed");
113                false
114            }
115            // Unknown→Known requires transform (conservative)
116            _ => true,
117        }
118    }
119
120    /// Check if this is an operational layout (NHD or HND).
121    ///
122    /// Operational layouts are used for direct computation and have
123    /// layer/outer as the outermost dimensions.
124    pub fn is_operational(&self) -> bool {
125        matches!(self, Self::OperationalNHD | Self::OperationalHND)
126    }
127
128    /// Check if this is a universal layout (TP or PP).
129    ///
130    /// Universal layouts are optimized for storage and transfer,
131    /// with different parallelism-friendly orderings.
132    pub fn is_universal(&self) -> bool {
133        matches!(self, Self::UniversalTP | Self::UniversalPP)
134    }
135
136    /// Get the layout name as a string identifier.
137    pub fn name(&self) -> &'static str {
138        match self {
139            Self::UniversalTP => "universal_tp",
140            Self::UniversalPP => "universal_pp",
141            Self::OperationalHND => "operational_hnd",
142            Self::OperationalNHD => "operational_nhd",
143            Self::Custom(_) => "custom",
144            Self::Unknown => "unknown",
145        }
146    }
147
148    /// Try to create a KvBlockLayout from an InnerShape.
149    ///
150    /// This provides compatibility with the existing InnerShape enum.
151    pub(crate) fn from_inner_shape(inner_shape: super::InnerShape) -> Self {
152        match inner_shape {
153            super::InnerShape::NHD => Self::OperationalNHD,
154            super::InnerShape::HND => Self::OperationalHND,
155            super::InnerShape::Unknown => Self::Unknown,
156        }
157    }
158
159    /// Convert to InnerShape if this is an operational layout.
160    ///
161    /// Returns `None` for universal or custom layouts.
162    pub(crate) fn to_inner_shape(self) -> Option<super::InnerShape> {
163        match self {
164            Self::OperationalNHD => Some(super::InnerShape::NHD),
165            Self::OperationalHND => Some(super::InnerShape::HND),
166            Self::Unknown => Some(super::InnerShape::Unknown),
167            _ => None,
168        }
169    }
170}
171
172impl std::fmt::Display for KvBlockLayout {
173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174        match self {
175            Self::UniversalTP => write!(f, "Universal TP [nh, nl, no, nt, hd]"),
176            Self::UniversalPP => write!(f, "Universal PP [nl, nh, no, nt, hd]"),
177            Self::OperationalHND => write!(f, "Operational HND [nl, no, nh, nt, hd]"),
178            Self::OperationalNHD => write!(f, "Operational NHD [nl, no, nt, nh, hd]"),
179            Self::Custom(order) => write!(f, "Custom {:?}", order),
180            Self::Unknown => write!(f, "Unknown"),
181        }
182    }
183}
184
185impl std::fmt::Display for BlockDim {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        match self {
188            Self::Layer => write!(f, "nl"),
189            Self::Outer => write!(f, "no"),
190            Self::Page => write!(f, "nt"),
191            Self::Head => write!(f, "nh"),
192        }
193    }
194}
195
196// ============================================================================
197// KvBlocks - Collection wrapper for blocks with shared layout
198// ============================================================================
199
200use crate::BlockId;
201use crate::layout::PhysicalLayout;
202use std::sync::Arc;
203
204/// A collection of blocks with a shared layout configuration and block layout type.
205///
206/// `KvBlocks` provides a convenient way to group blocks that should be treated
207/// uniformly in transfer operations. All blocks in the collection share:
208/// - The same [`PhysicalLayout`] (memory organization)
209/// - The same [`KvBlockLayout`] interpretation (dimension ordering)
210///
211/// This enables efficient batch transfers with optional layout override.
212///
213/// # Example
214///
215/// ```ignore
216/// // Create blocks with universal layout override
217/// let blocks = KvBlocks::new(
218///     physical_layout.clone(),
219///     vec![0, 1, 2, 3],  // block IDs
220///     Some(KvBlockLayout::UniversalTP),
221/// )?;
222///
223/// // Use in transfers - the override tells the transfer system
224/// // to interpret these blocks as universal format
225/// ```
226#[derive(Debug, Clone)]
227pub struct KvBlocks {
228    /// The physical layout containing these blocks
229    layout: Arc<PhysicalLayout>,
230    /// Block IDs within the layout
231    block_ids: Vec<BlockId>,
232    /// Optional layout override (None = use layout's native block_layout)
233    kv_layout_override: Option<KvBlockLayout>,
234}
235
236impl KvBlocks {
237    /// Create a new KvBlocks collection.
238    ///
239    /// # Arguments
240    /// * `layout` - The physical layout containing the blocks
241    /// * `block_ids` - Block IDs to include in this collection
242    /// * `kv_layout_override` - Optional override for the block layout interpretation.
243    ///   If `None`, uses the layout's native `block_layout()`.
244    ///   If `Some`, overrides the interpretation for transfers.
245    ///
246    /// # Validation
247    /// - For layer-separate layouts, only operational layouts (NHD/HND) are valid overrides
248    /// - For fully contiguous layouts, any layout is valid
249    /// - If the override matches the native layout, it is normalized to None
250    pub fn new(
251        layout: Arc<PhysicalLayout>,
252        block_ids: Vec<BlockId>,
253        kv_layout_override: Option<KvBlockLayout>,
254    ) -> anyhow::Result<Self> {
255        // Validate block IDs are in range
256        let num_blocks = layout.layout().num_blocks();
257        for &id in &block_ids {
258            if id >= num_blocks {
259                return Err(anyhow::anyhow!(
260                    "Block ID {} out of range (layout has {} blocks)",
261                    id,
262                    num_blocks
263                ));
264            }
265        }
266
267        // Validate layout override compatibility
268        if let Some(ref override_layout) = kv_layout_override {
269            // Layer-separate layouts can only use operational formats
270            if !layout.layout().is_fully_contiguous() && !override_layout.is_operational() {
271                return Err(anyhow::anyhow!(
272                    "Layer-separate layouts only support operational block layouts (NHD/HND), got {:?}",
273                    override_layout
274                ));
275            }
276        }
277
278        // Normalize: if override matches native layout, set to None
279        let normalized_override = kv_layout_override.and_then(|override_layout| {
280            if override_layout == layout.layout().block_layout() {
281                None
282            } else {
283                Some(override_layout)
284            }
285        });
286
287        Ok(Self {
288            layout,
289            block_ids,
290            kv_layout_override: normalized_override,
291        })
292    }
293
294    /// Create a KvBlocks collection without layout override.
295    #[expect(dead_code)]
296    pub fn from_layout(
297        layout: Arc<PhysicalLayout>,
298        block_ids: Vec<BlockId>,
299    ) -> anyhow::Result<Self> {
300        Self::new(layout, block_ids, None)
301    }
302
303    /// Get the physical layout.
304    #[expect(dead_code)]
305    pub fn layout(&self) -> &Arc<PhysicalLayout> {
306        &self.layout
307    }
308
309    /// Get the block IDs.
310    #[expect(dead_code)]
311    pub fn block_ids(&self) -> &[BlockId] {
312        &self.block_ids
313    }
314
315    /// Get the effective block layout (override or native).
316    pub fn effective_block_layout(&self) -> KvBlockLayout {
317        self.kv_layout_override
318            .unwrap_or_else(|| self.layout.layout().block_layout())
319    }
320
321    /// Get the layout override if set.
322    #[expect(dead_code)]
323    pub fn layout_override(&self) -> Option<KvBlockLayout> {
324        self.kv_layout_override
325    }
326
327    /// Check if this collection has a layout override.
328    #[expect(dead_code)]
329    pub fn has_override(&self) -> bool {
330        self.kv_layout_override.is_some()
331    }
332
333    /// Get the number of blocks in this collection.
334    #[expect(dead_code)]
335    pub fn len(&self) -> usize {
336        self.block_ids.len()
337    }
338
339    /// Check if the collection is empty.
340    #[expect(dead_code)]
341    pub fn is_empty(&self) -> bool {
342        self.block_ids.is_empty()
343    }
344
345    /// Check if a transfer between two KvBlocks collections requires transformation.
346    ///
347    /// Returns `true` if the effective layouts differ and a transformation kernel
348    /// is needed rather than a simple copy.
349    #[expect(dead_code)]
350    pub fn requires_transform_to(&self, dst: &KvBlocks) -> bool {
351        self.effective_block_layout()
352            .requires_transform(&dst.effective_block_layout())
353    }
354}
355
356#[cfg(all(test, feature = "testing-kvbm"))]
357mod tests {
358    use super::*;
359
360    #[test]
361    fn test_dim_order() {
362        use BlockDim::*;
363
364        assert_eq!(
365            KvBlockLayout::UniversalTP.dim_order(),
366            Some([Head, Layer, Outer, Page])
367        );
368        assert_eq!(
369            KvBlockLayout::OperationalNHD.dim_order(),
370            Some([Layer, Outer, Page, Head])
371        );
372        assert_eq!(KvBlockLayout::Unknown.dim_order(), None);
373    }
374
375    #[test]
376    fn test_requires_transform() {
377        // Same layout - no transform
378        assert!(!KvBlockLayout::OperationalNHD.requires_transform(&KvBlockLayout::OperationalNHD));
379
380        // Different layouts - transform required
381        assert!(KvBlockLayout::OperationalNHD.requires_transform(&KvBlockLayout::UniversalTP));
382        assert!(KvBlockLayout::OperationalHND.requires_transform(&KvBlockLayout::OperationalNHD));
383
384        // Unknown→Known requires transform (conservative)
385        assert!(KvBlockLayout::Unknown.requires_transform(&KvBlockLayout::OperationalNHD));
386        assert!(KvBlockLayout::OperationalNHD.requires_transform(&KvBlockLayout::Unknown));
387
388        // Unknown→Unknown is compatible (but emits warning)
389        assert!(!KvBlockLayout::Unknown.requires_transform(&KvBlockLayout::Unknown));
390    }
391
392    #[test]
393    fn test_is_operational() {
394        assert!(KvBlockLayout::OperationalNHD.is_operational());
395        assert!(KvBlockLayout::OperationalHND.is_operational());
396        assert!(!KvBlockLayout::UniversalTP.is_operational());
397        assert!(!KvBlockLayout::Unknown.is_operational());
398    }
399
400    #[test]
401    fn test_is_universal() {
402        assert!(KvBlockLayout::UniversalTP.is_universal());
403        assert!(KvBlockLayout::UniversalPP.is_universal());
404        assert!(!KvBlockLayout::OperationalNHD.is_universal());
405    }
406
407    #[test]
408    fn test_default() {
409        assert_eq!(KvBlockLayout::default(), KvBlockLayout::Unknown);
410    }
411
412    #[test]
413    fn test_serialization() {
414        let layout = KvBlockLayout::UniversalTP;
415        let json = serde_json::to_string(&layout).unwrap();
416        let deserialized: KvBlockLayout = serde_json::from_str(&json).unwrap();
417        assert_eq!(layout, deserialized);
418
419        // Test custom layout
420        let custom = KvBlockLayout::Custom([
421            BlockDim::Head,
422            BlockDim::Page,
423            BlockDim::Layer,
424            BlockDim::Outer,
425        ]);
426        let json = serde_json::to_string(&custom).unwrap();
427        let deserialized: KvBlockLayout = serde_json::from_str(&json).unwrap();
428        assert_eq!(custom, deserialized);
429    }
430
431    #[test]
432    fn test_inner_shape_conversion() {
433        use super::super::InnerShape;
434
435        assert_eq!(
436            KvBlockLayout::from_inner_shape(InnerShape::NHD),
437            KvBlockLayout::OperationalNHD
438        );
439        assert_eq!(
440            KvBlockLayout::from_inner_shape(InnerShape::HND),
441            KvBlockLayout::OperationalHND
442        );
443
444        assert_eq!(
445            KvBlockLayout::OperationalNHD.to_inner_shape(),
446            Some(InnerShape::NHD)
447        );
448        assert_eq!(KvBlockLayout::UniversalTP.to_inner_shape(), None);
449    }
450}