spark_runtime/buffers/
rowwise_slab.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! The row-wise FP8 GDN prefill BF16-weight slab: its size accessor and the
4//! bump carve the prefill arms take their per-layer slices from. Split from
5//! `accessors.rs` (≤500 LoC cap) rather than wedged into it, because the carve
6//! is a tiny allocator and not a getter.
7//!
8//! **WHY IT EXISTS (#917 H100 receipt, 2026-09-11).** The `ATLAS_FP8_ROWWISE`
9//! GDN arms used to get their BF16 weight from a lazy `gpu.alloc` memoised by
10//! weight pointer — `167772160` B for the fused `[QKV|Z]` weight per layer,
11//! with no `BufferSizes` entry, so `--gpu-memory-utilization` could not see it
12//! and a 28-token prefill died at layer 36 with `cuMemAlloc_v2 failed:
13//! status 2`. The bytes are the same; the difference is that they are now ONE
14//! ledgered allocation the preflight fitter prices.
15
16use super::BufferArena;
17use crate::gpu::DevicePtr;
18
19impl BufferArena {
20    /// Allocated byte size of the row-wise GDN prefill BF16-weight slab.
21    /// 0 when `ATLAS_FP8_ROWWISE` was not armed at boot.
22    pub fn ssm_rowwise_w_bf16_bytes(&self) -> usize {
23        self.sizes.ssm_rowwise_w_bf16
24    }
25
26    /// Carve the next `bytes` of the row-wise GDN prefill BF16-weight slab.
27    ///
28    /// Bump-only and never returned: each GDN layer takes its `in_proj_qkvz`
29    /// and `out_proj` slices on its FIRST prefill and holds them for the life
30    /// of the arena, because a dequanted weight is as immutable as the weight
31    /// it came from. Sized in `sizes_rowwise::ssm_rowwise_w_bf16_bytes` for
32    /// exactly `num_ssm_layers` of those pairs, so exhaustion means the sizing
33    /// and the callers disagree — a bug, reported as one rather than papered
34    /// over with a fresh allocation (that is the #917 defect this replaces).
35    ///
36    /// `Relaxed` is enough: the scheduler drives one forward at a time (the
37    /// same single-threaded invariant `cublaslt::Ctx` documents), so this is a
38    /// counter that happens to be atomic rather than a contended one.
39    pub fn take_ssm_rowwise_w_bf16(&self, bytes: usize) -> anyhow::Result<DevicePtr> {
40        use std::sync::atomic::Ordering;
41        if self.ssm_rowwise_w_bf16 == DevicePtr::NULL {
42            anyhow::bail!(
43                "row-wise GDN prefill BF16-weight slab is absent: the arena was sized without                  ATLAS_FP8_ROWWISE=1 but a row-wise prefill arm asked for {bytes} B. Both the                  loader's weight install and this ledger entry read the same lever, so they                  cannot legitimately disagree"
44            );
45        }
46        let base = self
47            .ssm_rowwise_w_bf16_used
48            .fetch_add(bytes, Ordering::Relaxed);
49        let end = base + bytes;
50        if end > self.sizes.ssm_rowwise_w_bf16 {
51            anyhow::bail!(
52                "row-wise GDN prefill BF16-weight slab exhausted: wanted {bytes} B at offset                  {base}, slab is {} B. `sizes_rowwise::ssm_rowwise_w_bf16_bytes` sizes it for                  num_ssm_layers x (in_proj_qkvz + out_proj)",
53                self.sizes.ssm_rowwise_w_bf16
54            );
55        }
56        Ok(self.ssm_rowwise_w_bf16.offset(base))
57    }
58}