metrale_gpu_runtime/buffers/
sizes.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: Byte size of every buffer in the GPU buffer arena, from the model config, the serve limits and four environment levers.
4//!
5//! Owner: gpu-runtime.
6//! Invariants:
7//! - [`BufferSizes::from_config`] allocates nothing; besides its arguments it
8//!   reads only `METRALE_GGUF_NATIVE_Q2`, `METRALE_GGUF_NATIVE_Q2_MMQ`,
9//!   `METRALE_FP8_ROWWISE` and, through `attn_splitk::policy_from_env`,
10//!   `METRALE_ATTN_DECODE_SPLITK`.
11
12use metrale_config::ModelConfig;
13use metrale_kernels::attn_splitk;
14
15mod regions;
16mod total;
17
18/// 2026-09-25: The widest `M` the fused dense-FFN gate+up decode GEMM serves, and so
19/// the row extent of `ffn_gate_up_fused`. Declared here because the arena is sized
20/// in this crate; the dispatch (`layers/dense_ffn_gateup_fused.rs` in
21/// metrale-model-layers) reads it for its `5..=GATEUP_FUSED_MAX_M` band, so the band
22/// and the buffer cannot disagree.
23pub const GATEUP_FUSED_MAX_M: usize = 16;
24
25/// 2026-09-29: Most rows (`R = Σ ks`) one batched MTP verify scores: 128 sequences at one
26/// draft (the serve's widest batch, `DECODE_META_MAX_ROWS`). It sizes the verify block-table
27/// rows (`sizes/regions.rs`) and the logits floor here; model-engine's `VERIFY_ROW_CAP` and
28/// the scheduler's `VERIFY_ROW_BUDGET` are this value. Was 160 (32 sequences at up to five
29/// rows).
30pub const VERIFY_ROW_CAP: usize = 256;
31
32/// 2026-09-25: Byte size of each arena buffer. `M` is `max_batch_tokens` and
33/// `ceil16(M)` is M rounded up to a multiple of 16.
34#[derive(Debug, Clone)]
35pub struct BufferSizes {
36    pub hidden_states: usize,
37    pub residual: usize,
38    pub norm_output: usize,
39    pub qkv_output: usize,
40    pub attn_output: usize,
41    pub gate_logits: usize,
42    /// 2026-09-25: FP32 router logits `[M, num_experts + zero_expert_num]` for
43    /// the `METRALE_FP32_GATE` and `METRALE_FP32_ROUTING` paths, which run the
44    /// top-K on unrounded logits. 256 bytes for a model without experts.
45    pub gate_logits_f32: usize,
46    /// 2026-09-25: FP32 MoE-input norm output `[M, hidden]` that the router GEMM
47    /// reads under `METRALE_FP32_ROUTING`. 256 bytes for a model without experts.
48    pub moe_router_in_f32: usize,
49    pub moe_output: usize,
50    pub logits: usize,
51    pub ssm_qkvz: usize,
52    pub ssm_ba: usize,
53    pub ssm_deinterleaved: usize,
54    pub ssm_gates: usize,
55    pub ssm_conv_out_f32: usize,
56    pub scratch: usize,
57    pub expert_gate_out: usize,
58    pub expert_up_out: usize,
59    pub expert_down_out: usize,
60    pub splitk_workspace: usize,
61    /// 2026-09-25: GDN FLA chunked-prefill scratch, W | U | S | uc | gc back to
62    /// back. 0 unless the linear-attention key and value head dims are both 128,
63    /// the only shape the FLA prefill kernels run.
64    pub gdn_fla_scratch: usize,
65    /// 2026-09-25: Mamba-2 SSD chunked-scan scratch, dt | dA_cumsum | CB. 0
66    /// unless the model has Mamba-2 heads and a state size.
67    pub ssd_scratch: usize,
68    /// 2026-09-25: Grouped O-projection latent `[M, o_groups * o_lora_rank]`
69    /// BF16, at least 256 bytes.
70    pub o_latent: usize,
71    /// 2026-09-25: The zero weight `norm_unit_w`, `max(hidden, Mamba-2 d_inner)`
72    /// BF16 values.
73    pub norm_unit_w: usize,
74    /// 2026-09-25: Hyper-connection residual streams `[M, hc_mult, hidden]`,
75    /// FP32. 256 bytes when `hc_mult == 0`.
76    pub hc_streams: usize,
77    /// 2026-09-25: Hyper-connection `post` weights `[M, hc_mult]` F32.
78    pub hc_post: usize,
79    /// 2026-09-25: Hyper-connection `comb` matrix `[M, hc_mult, hc_mult]` F32.
80    pub hc_comb: usize,
81    /// 2026-09-25: Low-rank hyper-connection scratch: the larger of the decode
82    /// split layout (T <= 64, FP32) and the prefill GEMM layout (slabs of at
83    /// most 2048 tokens, BF16); see the sizing in `from_config`. 256 bytes
84    /// without a low-rank hyper-connection.
85    pub hc_lowrank_scratch: usize,
86    /// 2026-09-25: QSA prefill-selection scratch for 2048-row slabs, shared by
87    /// the indexer layers: qk `[2048, (index_n_heads + 1) * index_head_dim]`
88    /// BF16, q_post `[2048, index_n_heads, index_head_dim]` F32, scores
89    /// `[2048, ceil(max_seq_len / index_compress_ratio)]` F32, lists
90    /// `[2048, index_topk / index_compress_ratio]` i32. 256 bytes without an
91    /// indexer. `layers/qsa_select.rs` carves the same layout.
92    pub qsa_select_scratch: usize,
93    /// 2026-09-25: Token ids `[M]` u32 of the current pass, read by the
94    /// DeepSeek-V4 hash-routed MoE layers (`tid2eid[token_id]`). At least 256
95    /// bytes.
96    pub token_ids: usize,
97    /// 2026-09-25: Dense-FFN activation-quant scratch shared by every layer's
98    /// prefill, sized for K = max(hidden, intermediate); all 0 for a MoE model.
99    /// `ffn_act_q8`: q8_1_mmq activations for the Q4_K MMQ path.
100    /// `ffn_act_a`: int8 `[ceil16(M), K]`, also NVFP4 packed and FP8 activations.
101    /// `ffn_act_scale`: `[ceil16(M), K/32]` 4-byte int8 scales, also the NVFP4
102    /// and FP8 scales.
103    pub ffn_act_q8: usize,
104    pub ffn_act_a: usize,
105    pub ffn_act_scale: usize,
106    /// 2026-09-25: `[K/128, ceil16(M)]` f32 copy of the FP8 activation scales,
107    /// token index contiguous, for the cuBLASLt block-scaled FFN GEMM; the
108    /// quantizer writes `[M, K/128]` into `ffn_act_scale`. 0 for a MoE model.
109    pub ffn_act_scale_kmajor: usize,
110    /// 2026-09-25: `[ceil16(GATEUP_FUSED_MAX_M), 2 * intermediate]` BF16 output of
111    /// the fused dense-FFN gate+up decode GEMM; a row is `[gate | up]`. A buffer
112    /// of its own because the fused arm serves only the decode band, and a
113    /// widened `expert_gate_out` would carry `max_batch_tokens` rows. Sized for
114    /// every dense model, whatever the lever says: the levers are resolved above
115    /// this crate. 0 for a MoE model.
116    pub ffn_gate_up_fused: usize,
117    /// 2026-09-25: FP8 activation scratch for the prefill projections, 1 byte per
118    /// element, `[ceil16(M), K]` for the widest K among hidden,
119    /// `q_heads * head_dim`, Mamba-2 d_inner and the GDN value dim.
120    pub fp8_act: usize,
121    /// 2026-09-25: The grouped FP8 MoE scratch slab (`moe_fp8_scratch::Layout`); 0 for a
122    /// dense model.
123    pub moe_fp8_scratch: usize,
124    /// 2026-09-25: One f32 scale per 128 elements of `fp8_act`.
125    pub fp8_act_scale: usize,
126    /// 2026-09-25: `[K/128, ceil16(M)]` f32 transpose of `fp8_act_scale`, token
127    /// index contiguous, for the cuBLASLt block-scaled projections. Sized for
128    /// every model, the SSM `in_proj_qkvz` arm included.
129    pub fp8_act_scale_kmajor: usize,
130    /// 2026-09-25: LoRA shrink output `xa = x @ Aᵀ`, `[M, adapter_max_rank]`
131    /// BF16. This and the other `lora_*` entries are 0 when
132    /// `adapter_max_rank == 0`.
133    pub lora_xa: usize,
134    /// 2026-09-25: LoRA expand output `delta = xa @ Bᵀ`, `[M, n]` BF16 with n the
135    /// widest target: max(hidden, intermediate, the q projection width).
136    pub lora_delta: usize,
137    /// 2026-09-25: LoRA hidden-activation scratch `[M, intermediate_size]` BF16.
138    pub lora_hact: usize,
139    /// 2026-09-25: LoRA adapter slot per prefill token, `[M]` i32. A buffer of
140    /// its own, so it cannot overlap the metadata regions of `scratch`.
141    pub lora_seq_slot: usize,
142    /// 2026-09-25: Keep-packed Q2_0 prefill dequant scratch
143    /// (`METRALE_GGUF_NATIVE_Q2=1`): one BF16 buffer for the largest keep-packed
144    /// projection, reused by each projection's dequant. Sized by
145    /// [`crate::buffers::q2_dequant_scratch_bytes`]; 0 unless the lever is `1`.
146    pub q2_dequant_scratch: usize,
147    /// 2026-09-25: Keep-packed Q2_0 MMQ prefill q8_1 activation scratch
148    /// (`METRALE_GGUF_NATIVE_Q2_MMQ=1`), shared by every keep-packed projection:
149    /// each quantizes its BF16 activation here and runs the packed MMQ GEMM.
150    /// 0 unless the lever is `1`.
151    pub q2_act_q8: usize,
152    /// 2026-09-25: Row-wise FP8 GDN prefill BF16-weight slab
153    /// (`METRALE_FP8_ROWWISE=1`): every GDN layer's BF16 `in_proj_qkvz` and
154    /// `out_proj`, one slice pair per layer carved on its first prefill and kept
155    /// for the arena's life. Sized in `sizes_rowwise.rs`; 0 unless the lever is
156    /// `1`.
157    pub ssm_rowwise_w_bf16: usize,
158}
159
160impl BufferSizes {
161    /// 2026-09-25: Every buffer size, in bytes. `max_seq_len` and `kv_block_size`
162    /// set the block-table width in `scratch` (`max_seq_len / kv_block_size + 1`
163    /// blocks, 256 when `kv_block_size` is 0) and the QSA score width;
164    /// `max_batch_size` sets the decode-metadata rows.
165    pub fn from_config(
166        config: &ModelConfig,
167        max_batch_tokens: usize,
168        max_seq_len: usize,
169        kv_block_size: usize,
170        max_batch_size: usize,
171    ) -> Self {
172        let decode_meta = super::DecodeMetaLayout::for_max_batch_size(max_batch_size);
173        let bf16 = 2;
174        let m = max_batch_tokens;
175        let h = config.hidden_size;
176
177        // 2026-09-25: A gated q projection (`attn_gated`) writes [Q | gate], twice
178        // `q_heads * head_dim` wide.
179        let q_heads = config.num_attention_heads;
180        let kv_heads = config.num_key_value_heads;
181        let hd = config.head_dim;
182        let q_proj_mul = if config.attn_gated { 2 } else { 1 };
183        let qkv_dim = (q_heads * q_proj_mul + 2 * kv_heads) * hd;
184
185        let scratch = regions::scratch_bytes(config, m, max_seq_len, kv_block_size, &decode_meta);
186
187        // 2026-09-25: The row extent of every buffer a cuBLASLt block-scaled FP8
188        // GEMM touches. `ops::cublas_fp8_proj_prequant` (metrale-model-layers)
189        // hands cuBLASLt `ceil16(M)` rows: the pad rows of the activation and its
190        // scales are read, and the pad rows of the output are written, so a
191        // buffer sized for M rows would be overrun by up to 15 rows.
192        let m_pad = m.div_ceil(16) * 16;
193        // 2026-09-25: Expert outputs: `ceil16(max(M, 3))` rows of top_k experts
194        // (or of the dense FFN).
195        let k_max = m.max(3).div_ceil(16) * 16;
196        let expert_inter = if config.num_experts > 0 {
197            let routed = config.num_experts_per_tok * config.moe_intermediate_size;
198            k_max * routed.max(config.intermediate_size)
199        } else {
200            k_max * config.intermediate_size
201        };
202        let expert_gate_out = expert_inter * bf16;
203        let expert_up_out = expert_inter * bf16;
204        // 2026-09-25: A LatentMoE model's routed experts write `moe_latent_size`
205        // wide rows (`moe_input_size`).
206        let moe_out_dim = config.moe_input_size();
207        let expert_down_out = if config.num_experts > 0 {
208            k_max * config.num_experts_per_tok * moe_out_dim * bf16
209        } else {
210            k_max * h * bf16
211        };
212
213        // 2026-09-25: Logit rows: min(M, max(VERIFY_ROW_CAP, decode rows + 1)):
214        // `VERIFY_ROW_CAP` is the most rows a batched MTP verify scores. The mixed
215        // decode step (`decode_b2.rs`) writes a prefill row after its `padded_n`
216        // decode rows, and `padded_n` can reach `decode_meta.rows()`, hence + 1.
217        let logits_tokens = m.min(VERIFY_ROW_CAP.max(decode_meta.rows() + 1));
218
219        // 2026-09-25: Mamba-2 d_inner may exceed hidden_size; norm_output and
220        // attn_output hold rows of either.
221        let mamba2_d_inner = config.mamba2_d_inner();
222        let max_dim = h.max(mamba2_d_inner);
223
224        // 2026-09-25: Split-K decode workspace: one `[o[head_dim], m, l]` F32 slot
225        // per (sequence, q head, split). A short workspace is an out-of-bounds
226        // device write, so the slot count comes from `attn_splitk::workspace_slots`
227        // under the policy the dispatch resolves too (`policy_from_env`), for
228        // `decode_meta.rows()` sequences, the widest batch the metadata upload
229        // accepts.
230        let splitk_slots = attn_splitk::workspace_slots(
231            attn_splitk::policy_from_env(),
232            metrale_kernels::TARGET_SM_COUNT,
233            q_heads as u32,
234            decode_meta.rows() as u32,
235            (max_batch_size as u32).max(1),
236        ) as usize;
237        let splitk_workspace = splitk_slots * (hd + 2) * 4;
238
239        let residual_elem = bf16;
240
241        // 2026-09-25: The widest K of the prefill projections that quantize into
242        // `fp8_act`: hidden (qkv, ssm qkvz), `q_heads * head_dim` (o_proj), Mamba-2
243        // d_inner (its out_proj) and the GDN value dim (its out_proj).
244        let max_proj_k = h
245            .max(q_heads * hd)
246            .max(mamba2_d_inner)
247            .max(config.linear_num_value_heads * config.linear_value_head_dim);
248        let fp8_act = m_pad * max_proj_k;
249        let fp8_act_scale = m_pad * max_proj_k.div_ceil(128) * 4;
250        // 2026-09-25: The transpose is written from `fp8_act_scale` while that is
251        // still live, so the two cannot share a buffer.
252        let fp8_act_scale_kmajor = fp8_act_scale;
253        // 2026-09-25: LoRA scratch, only when `adapter_max_rank > 0`. `max_n` is
254        // the widest projection output an adapter can target.
255        let (lora_xa, lora_delta, lora_hact, lora_seq_slot) = if config.adapter_max_rank > 0 {
256            let max_n = h
257                .max(config.intermediate_size)
258                .max(q_proj_mul * q_heads * hd);
259            (
260                m * config.adapter_max_rank * bf16,
261                m * max_n * bf16,
262                m * config.intermediate_size * bf16,
263                m * 4,
264            )
265        } else {
266            (0, 0, 0, 0)
267        };
268
269        let (ssd_scratch, gdn_fla_scratch) = regions::ssm_scratch_bytes(config, m, bf16);
270
271        let (q2_dequant_scratch, q2_act_q8) = super::sizes_q2::q2_scratch_sizes(config, m, h, hd);
272
273        let ssm_rowwise_w_bf16 = super::sizes_rowwise::ssm_rowwise_w_bf16_bytes(config);
274
275        // 2026-09-25: `ceil16` rows for the same cuBLASLt pad as `m_pad`.
276        let ffn_gate_up_fused = if config.num_experts == 0 {
277            let rows = GATEUP_FUSED_MAX_M.div_ceil(16) * 16;
278            rows * 2 * config.intermediate_size * bf16
279        } else {
280            0
281        };
282
283        let (ffn_act_q8, ffn_act_a, ffn_act_scale, ffn_act_scale_kmajor) =
284            if config.num_experts == 0 {
285                let kmax = h.max(config.intermediate_size);
286                let kpad = kmax.div_ceil(256) * 256;
287                // 2026-09-25: The q8_1 term is `q8_1_scratch_bytes` in
288                // metrale-model-layers (`ops/q4k_mmq.rs`). The int8 activation
289                // `[ceil16(M), K]` and scale `[ceil16(M), K/32]` terms also hold
290                // the NVFP4 (`[M, K/2]`, `[M, K/16]`) and FP8 (`[M, K]`,
291                // `[M, K/128]`) forms.
292                (
293                    m * kpad * 4 + (1 << 20),
294                    m_pad * kmax,
295                    m_pad * (kmax / 32) * 4,
296                    m_pad * (kmax / 128) * 4,
297                )
298            } else {
299                (0, 0, 0, 0)
300            };
301
302        Self {
303            hidden_states: m * h * residual_elem,
304            residual: m * h * residual_elem,
305            norm_output: m * max_dim * bf16,
306            // 2026-09-25: `m_pad` rows: the cuBLASLt Q/K/V prefill arm
307            // (`prefill_qkv_w8a8.rs`) writes `ceil16(M)` rows of Q here.
308            qkv_output: m_pad * qkv_dim * bf16,
309            attn_output: (m * config.num_attention_heads * config.head_dim * bf16)
310                .max(m * mamba2_d_inner * bf16)
311                // 2026-09-25: Absorbed MLA writes [M, q_heads, kv_lora_rank + rope dim].
312                .max(if config.kv_lora_rank > 0 {
313                    m * config.num_attention_heads
314                        * (config.kv_lora_rank + config.qk_rope_head_dim)
315                        * bf16
316                } else {
317                    0
318                }),
319            gate_logits: if config.num_experts > 0 {
320                // 2026-09-25: The router also scores the `zero_expert_num` zero
321                // experts, which have no FFN.
322                m * (config.num_experts + config.zero_expert_num) * bf16
323            } else {
324                256
325            },
326            gate_logits_f32: if config.num_experts > 0 {
327                m * (config.num_experts + config.zero_expert_num) * 4
328            } else {
329                256
330            },
331            moe_router_in_f32: if config.num_experts > 0 {
332                m * h * 4
333            } else {
334                256
335            },
336            // 2026-09-25: `ceil16(M)` rows: the cuBLASLt dense-FFN down
337            // projection writes its output here.
338            moe_output: m.div_ceil(16) * 16 * h * bf16,
339            logits: logits_tokens * config.vocab_size * bf16,
340            // 2026-09-25: The SSM buffers are also scratch for other layers, so
341            // each is the largest of its uses and at least 256 bytes. `ssm_qkvz`
342            // holds the QKVZ projection (`ceil16(M)` rows for its cuBLASLt arm),
343            // the Mamba-2 in_proj output, prefill K and V, and the MoE shared-expert
344            // up output.
345            ssm_qkvz: (m_pad * config.ssm_qkvz_size() * bf16)
346                .max(m * config.mamba2_in_proj_size() * bf16)
347                // 2026-09-25: K at row 0 and V at row M, V `ceil16(M)` rows tall
348                // on the cuBLASLt arm (`prefill_qkv_w8a8.rs`).
349                .max((m + m_pad) * kv_heads * hd * bf16)
350                .max(m * config.shared_expert_intermediate_size * bf16)
351                .max(256),
352            ssm_ba: (m * config.ssm_ba_size() * bf16)
353                .max(m * config.moe_latent_size * bf16)
354                // 2026-09-25: MLA uses `ssm_ba` for q_latent [M, q_lora_rank] and
355                // later for the K rope rows [M, qk_rope_head_dim], one at a time.
356                .max(if config.kv_lora_rank > 0 {
357                    (m * config.qk_rope_head_dim * bf16).max(m * config.q_lora_rank * bf16)
358                } else {
359                    0
360                })
361                // 2026-09-29: The MTP batched propose's `[n, 2 * hidden]` concat, for a propose
362                // as wide as the serve's widest batch (`DECODE_META_MAX_ROWS`, at most M): below
363                // that the propose splits into groups, each reading the drafter's experts again.
364                .max(m.min(super::DECODE_META_MAX_ROWS) * 2 * h * bf16)
365                .max(256),
366            // 2026-09-25: `ceil16(M)` rows as in `ssm_qkvz`: on a
367            // `sequential_qkvz` model the QKVZ projection writes here.
368            ssm_deinterleaved: (m_pad * config.ssm_qkvz_size() * bf16)
369                .max(m * config.mamba2_d_xbc() * bf16)
370                .max(m * q_heads * hd * bf16)
371                // 2026-09-25: Absorbed MLA's Q [M, q_heads, kv_lora_rank + rope dim].
372                .max(if config.kv_lora_rank > 0 {
373                    m * q_heads * (config.kv_lora_rank + config.qk_rope_head_dim) * bf16
374                } else {
375                    0
376                })
377                .max(256),
378            // 2026-09-29: Also the MTP batched propose's normed hidden rows `[n, hidden]` BF16,
379            // n up to `DECODE_META_MAX_ROWS`, at most M (see `ssm_ba`).
380            ssm_gates: (m * config.linear_num_value_heads * 2 * 4)
381                .max(m.min(super::DECODE_META_MAX_ROWS) * h * bf16)
382                .max(256),
383            // 2026-09-25: FP32 conv1d output, bounded by the QKVZ width. MLA also
384            // uses it for its Q rope rows [M, q_heads * qk_rope_head_dim] BF16.
385            ssm_conv_out_f32: (m * config.ssm_qkvz_size() * 4)
386                .max(if config.kv_lora_rank > 0 {
387                    m * q_heads * config.qk_rope_head_dim * bf16
388                } else {
389                    0
390                })
391                .max(256),
392            scratch,
393            expert_gate_out,
394            expert_up_out,
395            expert_down_out,
396            splitk_workspace,
397            gdn_fla_scratch,
398            ssd_scratch,
399            o_latent: (m * config.o_groups * config.o_lora_rank * bf16).max(256),
400            norm_unit_w: max_dim * bf16,
401            hc_streams: if config.hc_mult > 0 {
402                m * config.hc_mult * h * 4
403            } else {
404                256
405            },
406            hc_post: if config.hc_mult > 0 {
407                (m * config.hc_mult * 4).max(256)
408            } else {
409                256
410            },
411            hc_comb: if config.hc_mult > 0 {
412                (m * config.hc_mult * config.hc_mult * 4).max(256)
413            } else {
414                256
415            },
416            hc_lowrank_scratch: if config.hc_mult > 0 && config.hc_lowrank > 0 {
417                // 2026-09-25: Two layouts share this region
418                // (`hyper_connection_lowrank.rs`, `hyper_connection_lowrank_gemm.rs`):
419                // - T <= 64: normed FP32 [64, hc*H], then low FP32 [64, rank];
420                // - T > 64, in slabs of Ts <= 2048 tokens: normed BF16 [Ts, hc*H],
421                //   up_pre BF16 [Ts, hc*H], low BF16 [Ts, rank], inj_pre BF16 [Ts, hc].
422                let t = m.min(64);
423                let split = t * (config.hc_mult * h + config.hc_lowrank) * 4;
424                let ts = m.min(2048);
425                let gemm = ts * (2 * config.hc_mult * h + config.hc_lowrank + config.hc_mult) * 2;
426                split.max(gemm)
427            } else {
428                256
429            },
430            qsa_select_scratch: if config.index_topk > 0 && config.index_compress_ratio > 0 {
431                const ROWS: usize = 2048;
432                let qkw = (config.index_n_heads + 1) * config.index_head_dim;
433                let n_blocks = max_seq_len.div_ceil(config.index_compress_ratio);
434                let topk = config.index_topk / config.index_compress_ratio;
435                ROWS * qkw * 2
436                    + ROWS * config.index_n_heads * config.index_head_dim * 4
437                    + ROWS * n_blocks * 4
438                    + ROWS * topk * 4
439            } else {
440                256
441            },
442            token_ids: (m * 4).max(256),
443            ffn_act_q8,
444            ffn_act_a,
445            ffn_act_scale,
446            ffn_act_scale_kmajor,
447            ffn_gate_up_fused,
448            fp8_act,
449            moe_fp8_scratch: super::moe_fp8_scratch::Layout::new(config, m).bytes,
450            fp8_act_scale,
451            fp8_act_scale_kmajor,
452            lora_xa,
453            lora_delta,
454            lora_hact,
455            lora_seq_slot,
456            q2_dequant_scratch,
457            q2_act_q8,
458            ssm_rowwise_w_bf16,
459        }
460    }
461}