spark_runtime/buffers/
decode_meta.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! Fixed-stride batched-decode metadata layout, derived from the serve
4//! `max_batch_size` (SSOT — consumed by `sizes.rs` for the scratch envelope
5//! and by `spark-model`'s `upload_batch_metadata_fixed`/`_at` for the
6//! upload offsets, replacing the former hardcoded 0/128/256/512/768 gaps
7//! that fit exactly 32 rows).
8//!
9//! Region shapes (byte offsets, `R = rows`):
10//!   positions  u32  [0,       4R)
11//!   seq_slot   i32  [4R,      8R)   (per-request LoRA routing)
12//!   slots      i64  [8R,     16R)
13//!   seq_lens   i32  [16R,    20R)
14//!   (gap            [20R,    24R))  — legacy [640,768) pad, scaled
15//!   block_tbl  i32  [24R,    24R + R·max_blocks·4)
16//!
17//! At `R = 32` this reproduces the legacy layout BYTE-FOR-BYTE
18//! (0/128/256/512/768), so every boot with `max_batch_size <= 32` is
19//! byte-identical in addresses, strides and upload sizes.
20
21/// Layout floor: the legacy fixed layout was sized for exactly 32 rows;
22/// deriving `rows = max(32, bs)` keeps every `bs <= 32` boot byte-identical.
23pub const DECODE_META_MIN_ROWS: usize = 32;
24
25/// Layout ceiling, checked at serve time (`serve.rs`). The metadata gaps
26/// themselves derive cleanly to any width; the binding constraints on this
27/// tip are downstream row consumers sized at 96+ rows:
28/// * the logits arena (`sizes.rs`) — derived `max(96, rows+1)` rows, where
29///   `rows+1` covers the run_standard mixed path parking prefill logits at
30///   row `padded_n`;
31/// * the scratch block-table envelope (`sizes.rs`) — derived
32///   `max(verify 96-row overlay, decode `rows`-row layout)`.
33///
34/// Batched-decode kernels are row-count parametric (grid.y = n, smem per
35/// CTA constant, split-K workspace derived from the pinned max batch), so
36/// 128 is a policy cap for the widths validated by the enterprise-
37/// concurrency campaign, not an smem wall.
38pub const DECODE_META_MAX_ROWS: usize = 128;
39
40/// Derived fixed-stride decode-metadata layout.
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct DecodeMetaLayout {
43    rows: usize,
44}
45
46impl DecodeMetaLayout {
47    /// Derive the layout from the serve `max_batch_size`. Callers gate
48    /// `max_batch_size <= DECODE_META_MAX_ROWS` at serve time; this
49    /// constructor only applies the byte-identity floor.
50    pub fn for_max_batch_size(max_batch_size: usize) -> Self {
51        Self {
52            rows: max_batch_size.max(DECODE_META_MIN_ROWS),
53        }
54    }
55
56    /// Row capacity of the metadata block (== the widest `padded_n` the
57    /// upload accepts).
58    pub fn rows(&self) -> usize {
59        self.rows
60    }
61
62    /// `positions` u32 stream offset.
63    pub fn positions_off(&self) -> usize {
64        0
65    }
66
67    /// Per-request LoRA adapter-slot i32 stream offset.
68    pub fn seq_slot_off(&self) -> usize {
69        4 * self.rows
70    }
71
72    /// KV `slots` i64 stream offset (8-byte aligned: `8R`).
73    pub fn slots_off(&self) -> usize {
74        8 * self.rows
75    }
76
77    /// `seq_lens` i32 stream offset.
78    pub fn seq_lens_off(&self) -> usize {
79        16 * self.rows
80    }
81
82    /// Flattened block-table offset (row stride `max_blocks · 4` bytes).
83    pub fn block_table_off(&self) -> usize {
84        24 * self.rows
85    }
86
87    /// Total bytes of the metadata block for `max_blocks` blocks per row.
88    pub fn meta_bytes(&self, max_blocks: usize) -> usize {
89        self.block_table_off() + self.rows * max_blocks * 4
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    /// bs <= 32 must reproduce the legacy hardcoded layout byte-for-byte.
98    #[test]
99    fn legacy_layout_at_or_below_32() {
100        for bs in [1usize, 31, 32] {
101            let l = DecodeMetaLayout::for_max_batch_size(bs);
102            assert_eq!(l.rows(), 32, "bs={bs}");
103            assert_eq!(l.positions_off(), 0);
104            assert_eq!(l.seq_slot_off(), 128);
105            assert_eq!(l.slots_off(), 256);
106            assert_eq!(l.seq_lens_off(), 512);
107            assert_eq!(l.block_table_off(), 768);
108            // Legacy total: 768 + 32·mb·4 (decode bt region in sizes.rs).
109            assert_eq!(l.meta_bytes(257), 768 + 32 * 257 * 4);
110        }
111    }
112
113    /// Widened layouts: regions must be contiguous-or-gapped exactly like
114    /// the legacy shape scaled by R/32, non-overlapping, and 8-byte aligned
115    /// where i64 lands.
116    #[test]
117    fn widened_layout_arithmetic() {
118        for bs in [33usize, 64, 128] {
119            let l = DecodeMetaLayout::for_max_batch_size(bs);
120            let r = l.rows();
121            assert_eq!(r, bs, "bs={bs}: rows derive from bs above the floor");
122            // positions [0,4R) then seq_slot [4R,8R): no overlap.
123            assert_eq!(l.seq_slot_off(), l.positions_off() + 4 * r);
124            // slots i64 begins exactly after seq_slot and is 8-byte aligned.
125            assert_eq!(l.slots_off(), l.seq_slot_off() + 4 * r);
126            assert_eq!(l.slots_off() % 8, 0);
127            // seq_lens begins exactly after the 8R-byte slots region.
128            assert_eq!(l.seq_lens_off(), l.slots_off() + 8 * r);
129            // block table begins after seq_lens (4R) + the scaled legacy pad (4R).
130            assert_eq!(l.block_table_off(), l.seq_lens_off() + 8 * r);
131            assert_eq!(l.meta_bytes(257), 24 * r + r * 257 * 4);
132        }
133    }
134
135    #[test]
136    fn policy_ceiling_and_floor_are_exact() {
137        assert_eq!(DECODE_META_MIN_ROWS, 32);
138        assert_eq!(DECODE_META_MAX_ROWS, 128);
139    }
140}