metrale_gpu_runtime/buffers/decode_meta.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: Fixed-stride batched-decode metadata layout, derived from the serve `max_batch_size`.
4//!
5//! `sizes.rs` sizes `scratch` from it, and metrale-model-engine's
6//! `upload_batch_metadata_fixed` / `_at` take their offsets from it. Byte
7//! offsets, with `R = rows`:
8//! positions u32 [0, 4R)
9//! seq_slot i32 [4R, 8R) (per-request LoRA slot)
10//! slots i64 [8R, 16R)
11//! seq_lens i32 [16R, 20R)
12//! (unused [20R, 24R))
13//! block_tbl i32 [24R, 24R + R·max_blocks·4)
14//!
15//! Owner: gpu-runtime.
16//! Invariants:
17//! - `rows() >= DECODE_META_MIN_ROWS`.
18//! - The regions do not overlap, and `slots_off()` is a multiple of 8.
19
20/// 2026-09-25: The row floor: `rows = max(DECODE_META_MIN_ROWS, max_batch_size)`.
21pub const DECODE_META_MIN_ROWS: usize = 32;
22
23/// 2026-09-25: The largest serve `max_batch_size` accepted; `serve_load.rs`
24/// refuses a larger one at startup. The layout itself has no upper bound.
25pub const DECODE_META_MAX_ROWS: usize = 128;
26
27/// 2026-09-25: The decode-metadata layout for `rows` rows (see the module docs).
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub struct DecodeMetaLayout {
30 rows: usize,
31}
32
33impl DecodeMetaLayout {
34 /// 2026-09-25: The layout for the serve `max_batch_size`. It applies only the
35 /// floor; the ceiling `DECODE_META_MAX_ROWS` is checked by the serve path.
36 pub fn for_max_batch_size(max_batch_size: usize) -> Self {
37 Self {
38 rows: max_batch_size.max(DECODE_META_MIN_ROWS),
39 }
40 }
41
42 /// 2026-09-25: Row capacity, the widest `padded_n` the upload accepts.
43 pub fn rows(&self) -> usize {
44 self.rows
45 }
46
47 pub fn positions_off(&self) -> usize {
48 0
49 }
50
51 pub fn seq_slot_off(&self) -> usize {
52 4 * self.rows
53 }
54
55 pub fn slots_off(&self) -> usize {
56 8 * self.rows
57 }
58
59 pub fn seq_lens_off(&self) -> usize {
60 16 * self.rows
61 }
62
63 /// 2026-09-25: Offset of the flattened block table, row stride `max_blocks · 4` bytes.
64 pub fn block_table_off(&self) -> usize {
65 24 * self.rows
66 }
67
68 /// 2026-09-25: Bytes of the whole block for `max_blocks` blocks per row.
69 pub fn meta_bytes(&self, max_blocks: usize) -> usize {
70 self.block_table_off() + self.rows * max_blocks * 4
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 /// 2026-09-25: At or below the floor the offsets are 0/128/256/512/768.
79 #[test]
80 fn legacy_layout_at_or_below_32() {
81 for bs in [1usize, 31, 32] {
82 let l = DecodeMetaLayout::for_max_batch_size(bs);
83 assert_eq!(l.rows(), 32, "bs={bs}");
84 assert_eq!(l.positions_off(), 0);
85 assert_eq!(l.seq_slot_off(), 128);
86 assert_eq!(l.slots_off(), 256);
87 assert_eq!(l.seq_lens_off(), 512);
88 assert_eq!(l.block_table_off(), 768);
89 assert_eq!(l.meta_bytes(257), 768 + 32 * 257 * 4);
90 }
91 }
92
93 /// 2026-09-25: Above the floor, rows follow `max_batch_size`, the regions
94 /// are back to back except the 4R gap before the block table, and the i64
95 /// slots are 8-byte aligned.
96 #[test]
97 fn widened_layout_arithmetic() {
98 for bs in [33usize, 64, 128] {
99 let l = DecodeMetaLayout::for_max_batch_size(bs);
100 let r = l.rows();
101 assert_eq!(r, bs, "bs={bs}: rows derive from bs above the floor");
102 assert_eq!(l.seq_slot_off(), l.positions_off() + 4 * r);
103 assert_eq!(l.slots_off(), l.seq_slot_off() + 4 * r);
104 assert_eq!(l.slots_off() % 8, 0);
105 assert_eq!(l.seq_lens_off(), l.slots_off() + 8 * r);
106 assert_eq!(l.block_table_off(), l.seq_lens_off() + 8 * r);
107 assert_eq!(l.meta_bytes(257), 24 * r + r * 257 * 4);
108 }
109 }
110
111 #[test]
112 fn policy_ceiling_and_floor_are_exact() {
113 assert_eq!(DECODE_META_MIN_ROWS, 32);
114 assert_eq!(DECODE_META_MAX_ROWS, 128);
115 }
116}