spark_model/layers/ops/
ssm_gdn_a.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! Auto-extracted from `ops.rs` during refactor wave 4a.
4
5#![allow(unused_imports)]
6
7use anyhow::Result;
8use spark_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
9use spark_runtime::kernel_args::{KernelLaunch, div_ceil};
10
11use crate::layers::moe;
12use crate::weight_map::{DenseWeight, Fp8DenseWeight, Fp8Weight, QuantizedWeight};
13
14use super::*;
15
16/// FP16 h-state twin of [`gdn_decode_f32_strided_norm`] (`ATLAS_SSM_H_FP16`).
17///
18/// The only signature difference is `h_seq_stride`: the per-sequence stride of
19/// the h-state pool in __half elements. Stage 1 keeps the pool FP32-sized, so
20/// slots are `h_state_bytes` apart while the dense FP16 footprint is half that
21/// — the stride must be passed, not inferred from the head dims.
22#[allow(clippy::too_many_arguments)]
23pub fn gdn_decode_f16_strided_norm(
24    gpu: &dyn GpuBackend,
25    kernel: KernelHandle,
26    h_state: DevicePtr,
27    query: DevicePtr,
28    key: DevicePtr,
29    value: DevicePtr,
30    gate: DevicePtr,
31    beta: DevicePtr,
32    z_gate: DevicePtr,
33    norm_weight: DevicePtr,
34    output: DevicePtr,
35    batch_size: u32,
36    num_k_heads: u32,
37    num_v_heads: u32,
38    k_dim: u32,
39    v_dim: u32,
40    qk_stride: u32,
41    v_stride: u32,
42    gb_stride: u32,
43    z_stride: u32,
44    out_stride: u32,
45    h_seq_stride: u64,
46    eps: f32,
47    stream: u64,
48) -> Result<()> {
49    KernelLaunch::new(gpu, kernel)
50        .grid([num_v_heads, batch_size, 1])
51        .block([128, 1, 1])
52        .arg_ptr(h_state)
53        .arg_ptr(query)
54        .arg_ptr(key)
55        .arg_ptr(value)
56        .arg_ptr(gate)
57        .arg_ptr(beta)
58        .arg_ptr(z_gate)
59        .arg_ptr(norm_weight)
60        .arg_ptr(output)
61        .arg_u32(batch_size)
62        .arg_u32(num_k_heads)
63        .arg_u32(num_v_heads)
64        .arg_u32(k_dim)
65        .arg_u32(v_dim)
66        .arg_u32(qk_stride)
67        .arg_u32(v_stride)
68        .arg_u32(gb_stride)
69        .arg_u32(z_stride)
70        .arg_u32(out_stride)
71        .arg_u64(h_seq_stride)
72        .arg_f32(eps)
73        .launch(stream)
74}
75
76/// One-shot FP32 -> FP16 conversion of one layer's SSM h-state
77/// (`ATLAS_SSM_H_FP16`). `n` is the FP32 ELEMENT count, derived from the
78/// pool's byte size — never a duplicated shape literal.
79///
80/// Kernel: `ssm_h_state_f32_to_f16(src, dst, n)`. Grid-stride; `src` and `dst`
81/// must not alias (a narrowing compaction in place is a data race).
82pub fn ssm_h_state_f32_to_f16(
83    gpu: &dyn GpuBackend,
84    kernel: KernelHandle,
85    src: DevicePtr,
86    dst: DevicePtr,
87    n: u64,
88    stream: u64,
89) -> Result<()> {
90    const BLOCK: u32 = 256;
91    let blocks = div_ceil(n as u32, BLOCK).clamp(1, 4096);
92    KernelLaunch::new(gpu, kernel)
93        .grid([blocks, 1, 1])
94        .block([BLOCK, 1, 1])
95        .arg_ptr(src)
96        .arg_ptr(dst)
97        .arg_u64(n)
98        .launch(stream)
99}
100
101/// One-shot FP16 -> FP32 widening of one layer's SSM h-state
102/// (`ATLAS_SSM_H_FP16`). `n` is the FP32 ELEMENT count of the destination.
103///
104/// Kernel: `ssm_h_state_f16_to_f32(src, dst, n)`. Grid-stride; `src` and `dst`
105/// must not alias.
106pub fn ssm_h_state_f16_to_f32(
107    gpu: &dyn GpuBackend,
108    kernel: KernelHandle,
109    src: DevicePtr,
110    dst: DevicePtr,
111    n: u64,
112    stream: u64,
113) -> Result<()> {
114    const BLOCK: u32 = 256;
115    let blocks = div_ceil(n as u32, BLOCK).clamp(1, 4096);
116    KernelLaunch::new(gpu, kernel)
117        .grid([blocks, 1, 1])
118        .block([BLOCK, 1, 1])
119        .arg_ptr(src)
120        .arg_ptr(dst)
121        .arg_u64(n)
122        .launch(stream)
123}
124
125/// Gated delta rule decode (recurrent SSM update, supports batched sequences).
126///
127/// Kernel: `gated_delta_rule_decode(h_state, query, key, value,
128///          gate, beta, output, batch_size, num_k_heads, num_v_heads,
129///          k_dim, v_dim)`
130/// Grid: (num_v_heads, batch_size, 1)  Block: (128, 1, 1)
131///
132/// For batch_size > 1, h_state layout: [batch, num_v_heads, k_dim, v_dim].
133pub fn gdn_decode(
134    gpu: &dyn GpuBackend,
135    kernel: KernelHandle,
136    h_state: DevicePtr,
137    query: DevicePtr,
138    key: DevicePtr,
139    value: DevicePtr,
140    gate: DevicePtr,
141    beta: DevicePtr,
142    output: DevicePtr,
143    batch_size: u32,
144    num_k_heads: u32,
145    num_v_heads: u32,
146    k_dim: u32,
147    v_dim: u32,
148    stream: u64,
149) -> Result<()> {
150    KernelLaunch::new(gpu, kernel)
151        .grid([num_v_heads, batch_size, 1])
152        .block([128, 1, 1])
153        .arg_ptr(h_state)
154        .arg_ptr(query)
155        .arg_ptr(key)
156        .arg_ptr(value)
157        .arg_ptr(gate)
158        .arg_ptr(beta)
159        .arg_ptr(output)
160        .arg_u32(batch_size)
161        .arg_u32(num_k_heads)
162        .arg_u32(num_v_heads)
163        .arg_u32(k_dim)
164        .arg_u32(v_dim)
165        .launch(stream)
166}
167
168/// FP32 GDN decode fused with gated RMS norm.
169///
170/// Produces the same BF16 post-gated-norm output that a separate
171/// `gdn_decode_f32` + `gated_rms_norm_f32_input` pair would produce, while
172/// avoiding the intermediate FP32 global write/read.
173#[allow(clippy::too_many_arguments)]
174pub fn gdn_decode_f32_norm(
175    gpu: &dyn GpuBackend,
176    kernel: KernelHandle,
177    h_state: DevicePtr,
178    query: DevicePtr,
179    key: DevicePtr,
180    value: DevicePtr,
181    gate: DevicePtr,
182    beta: DevicePtr,
183    z_gate: DevicePtr,
184    norm_weight: DevicePtr,
185    output: DevicePtr,
186    batch_size: u32,
187    num_k_heads: u32,
188    num_v_heads: u32,
189    k_dim: u32,
190    v_dim: u32,
191    eps: f32,
192    stream: u64,
193) -> Result<()> {
194    KernelLaunch::new(gpu, kernel)
195        .grid([num_v_heads, batch_size, 1])
196        .block([128, 1, 1])
197        .arg_ptr(h_state)
198        .arg_ptr(query)
199        .arg_ptr(key)
200        .arg_ptr(value)
201        .arg_ptr(gate)
202        .arg_ptr(beta)
203        .arg_ptr(z_gate)
204        .arg_ptr(norm_weight)
205        .arg_ptr(output)
206        .arg_u32(batch_size)
207        .arg_u32(num_k_heads)
208        .arg_u32(num_v_heads)
209        .arg_u32(k_dim)
210        .arg_u32(v_dim)
211        .arg_f32(eps)
212        .launch(stream)
213}
214
215/// Register-resident token-sequential prefill recurrence (warm-replay path).
216///
217/// Kernel `gated_delta_rule_prefill_regresident`: one WARP owns one v-column,
218/// holding the 128 k-rows of H in registers (4/lane) — no smem-H, no per-token
219/// barriers, >=2 CTA/SM. Token-equal to WY4 (cosine 1.0) and ~2.9x faster.
220/// Grid: (num_v_heads, batch, v_dim / 4)  Block: (128, 1, 1)  (4 warps/block).
221/// Requires k_dim == 128 and v_dim % 4 == 0.
222#[allow(clippy::too_many_arguments)]
223pub fn gdn_prefill_regresident(
224    gpu: &dyn GpuBackend,
225    kernel: KernelHandle,
226    h_state: DevicePtr,
227    query: DevicePtr,
228    key: DevicePtr,
229    value: DevicePtr,
230    gate: DevicePtr,
231    beta: DevicePtr,
232    output: DevicePtr,
233    batch_size: u32,
234    seq_len: u32,
235    num_k_heads: u32,
236    num_v_heads: u32,
237    k_dim: u32,
238    v_dim: u32,
239    qk_stride: u32,
240    v_stride: u32,
241    gb_stride: u32,
242    stream: u64,
243) -> Result<()> {
244    KernelLaunch::new(gpu, kernel)
245        .grid([num_v_heads, batch_size, v_dim / 4])
246        .block([128, 1, 1])
247        .arg_ptr(h_state)
248        .arg_ptr(query)
249        .arg_ptr(key)
250        .arg_ptr(value)
251        .arg_ptr(gate)
252        .arg_ptr(beta)
253        .arg_ptr(output)
254        .arg_u32(batch_size)
255        .arg_u32(seq_len)
256        .arg_u32(num_k_heads)
257        .arg_u32(num_v_heads)
258        .arg_u32(k_dim)
259        .arg_u32(v_dim)
260        .arg_u32(qk_stride)
261        .arg_u32(v_stride)
262        .arg_u32(gb_stride)
263        .launch(stream)
264}
265
266/// Split-v_dim prefill: 2 CTAs per v-head, 64 threads each.
267/// FUSED conv1d_update_l2norm + recurrence + gated-RMS-norm decode.
268///
269/// Collapses the per-seq SSM decode chain `conv1d_l2norm -> gdn -> gated_norm`
270/// into one launch (shorter critical path on the chain-depth-bound decode).
271/// Race-free per-k-head grid: each block owns k-head `kh` and its `head_repeat`
272/// v-heads, conv-updating its own q/k AND v `conv_state` exclusively.
273/// Requires `head_repeat * v_dim == block`, `2*k_dim <= block`, `k_dim == v_dim`.
274#[allow(clippy::too_many_arguments)]
275pub fn gdn_decode_f32_conv_norm(
276    gpu: &dyn GpuBackend,
277    kernel: KernelHandle,
278    h_state: DevicePtr,
279    conv_state: DevicePtr,
280    new_input: DevicePtr,
281    conv_weight: DevicePtr,
282    gate: DevicePtr,
283    beta: DevicePtr,
284    z_gate: DevicePtr,
285    norm_weight: DevicePtr,
286    output: DevicePtr,
287    batch_size: u32,
288    num_k_heads: u32,
289    num_v_heads: u32,
290    k_dim: u32,
291    v_dim: u32,
292    conv_dim: u32,
293    d_conv: u32,
294    l2_eps: f32,
295    eps: f32,
296    stream: u64,
297) -> Result<()> {
298    let head_repeat = num_v_heads / num_k_heads;
299    KernelLaunch::new(gpu, kernel)
300        .grid([num_k_heads, batch_size, 1])
301        .block([head_repeat * v_dim, 1, 1])
302        .arg_ptr(h_state)
303        .arg_ptr(conv_state)
304        .arg_ptr(new_input)
305        .arg_ptr(conv_weight)
306        .arg_ptr(DevicePtr::NULL) // conv_bias
307        .arg_ptr(gate)
308        .arg_ptr(beta)
309        .arg_ptr(z_gate)
310        .arg_ptr(norm_weight)
311        .arg_ptr(output)
312        .arg_u32(batch_size)
313        .arg_u32(num_k_heads)
314        .arg_u32(num_v_heads)
315        .arg_u32(k_dim)
316        .arg_u32(v_dim)
317        .arg_u32(conv_dim)
318        .arg_u32(d_conv)
319        .arg_f32(l2_eps)
320        .arg_f32(eps)
321        .launch(stream)
322}
323
324/// Strided FP32 GDN decode for concurrent sequence decode.
325///
326/// Q/K/V are read from strided rows, typically the FP32 conv output laid out as
327/// `[batch, Q | K | V]`. Gate/beta and output are also strided by batch row.
328#[allow(clippy::too_many_arguments)]
329pub fn gdn_decode_f32_strided(
330    gpu: &dyn GpuBackend,
331    kernel: KernelHandle,
332    h_state: DevicePtr,
333    query: DevicePtr,
334    key: DevicePtr,
335    value: DevicePtr,
336    gate: DevicePtr,
337    beta: DevicePtr,
338    output: DevicePtr,
339    batch_size: u32,
340    num_k_heads: u32,
341    num_v_heads: u32,
342    k_dim: u32,
343    v_dim: u32,
344    qk_stride: u32,
345    v_stride: u32,
346    gb_stride: u32,
347    out_stride: u32,
348    stream: u64,
349) -> Result<()> {
350    KernelLaunch::new(gpu, kernel)
351        .grid([num_v_heads, batch_size, 1])
352        .block([128, 1, 1])
353        .arg_ptr(h_state)
354        .arg_ptr(query)
355        .arg_ptr(key)
356        .arg_ptr(value)
357        .arg_ptr(gate)
358        .arg_ptr(beta)
359        .arg_ptr(output)
360        .arg_u32(batch_size)
361        .arg_u32(num_k_heads)
362        .arg_u32(num_v_heads)
363        .arg_u32(k_dim)
364        .arg_u32(v_dim)
365        .arg_u32(qk_stride)
366        .arg_u32(v_stride)
367        .arg_u32(gb_stride)
368        .arg_u32(out_stride)
369        .launch(stream)
370}
371
372/// Strided FP32 GDN decode fused with gated RMS norm.
373///
374/// Same recurrent update as `gdn_decode_f32_strided`, but writes BF16
375/// post-gated-norm output directly and skips the intermediate FP32 output
376/// buffer plus per-token gated-rms-norm launches.
377#[allow(clippy::too_many_arguments)]
378pub fn gdn_decode_f32_strided_norm(
379    gpu: &dyn GpuBackend,
380    kernel: KernelHandle,
381    h_state: DevicePtr,
382    query: DevicePtr,
383    key: DevicePtr,
384    value: DevicePtr,
385    gate: DevicePtr,
386    beta: DevicePtr,
387    z_gate: DevicePtr,
388    norm_weight: DevicePtr,
389    output: DevicePtr,
390    batch_size: u32,
391    num_k_heads: u32,
392    num_v_heads: u32,
393    k_dim: u32,
394    v_dim: u32,
395    qk_stride: u32,
396    v_stride: u32,
397    gb_stride: u32,
398    z_stride: u32,
399    out_stride: u32,
400    eps: f32,
401    stream: u64,
402) -> Result<()> {
403    KernelLaunch::new(gpu, kernel)
404        .grid([num_v_heads, batch_size, 1])
405        .block([128, 1, 1])
406        .arg_ptr(h_state)
407        .arg_ptr(query)
408        .arg_ptr(key)
409        .arg_ptr(value)
410        .arg_ptr(gate)
411        .arg_ptr(beta)
412        .arg_ptr(z_gate)
413        .arg_ptr(norm_weight)
414        .arg_ptr(output)
415        .arg_u32(batch_size)
416        .arg_u32(num_k_heads)
417        .arg_u32(num_v_heads)
418        .arg_u32(k_dim)
419        .arg_u32(v_dim)
420        .arg_u32(qk_stride)
421        .arg_u32(v_stride)
422        .arg_u32(gb_stride)
423        .arg_u32(z_stride)
424        .arg_u32(out_stride)
425        .arg_f32(eps)
426        .launch(stream)
427}
428
429/// Fused 2-token GDN decode (speculative verification).
430///
431/// Processes exactly 2 tokens through GDN in a single kernel launch.
432/// Saves intermediate H_1 state for rollback on draft rejection.
433/// Reads H_0 once, computes both outputs and H_2 in 3 passes (vs 4 for
434/// 2× sequential decode), with H_1 intermediate staying in L2 cache.
435///
436/// Q/K/V/gate/beta are accessed via stride params (in elements, not bytes)
437/// to support layouts where tokens are interleaved with other data.
438///
439/// Kernel: `gated_delta_rule_chunk2(h_state, query, key, value, gate, beta,
440///          output, h_state_intermediate, batch_size, num_k_heads,
441///          num_v_heads, k_dim, v_dim, qk_stride, v_stride, gb_stride)`
442/// Grid: (num_v_heads, batch, 1)  Block: (128, 1, 1)
443#[allow(clippy::too_many_arguments)]
444pub fn gdn_decode_chunk2(
445    gpu: &dyn GpuBackend,
446    kernel: KernelHandle,
447    h_state: DevicePtr,
448    query: DevicePtr,
449    key: DevicePtr,
450    value: DevicePtr,
451    gate: DevicePtr,
452    beta: DevicePtr,
453    output: DevicePtr,
454    h_state_intermediate: DevicePtr,
455    batch_size: u32,
456    num_k_heads: u32,
457    num_v_heads: u32,
458    k_dim: u32,
459    v_dim: u32,
460    qk_stride: u32,
461    v_stride: u32,
462    gb_stride: u32,
463    stream: u64,
464) -> Result<()> {
465    KernelLaunch::new(gpu, kernel)
466        .grid([num_v_heads, batch_size, 1])
467        .block([128, 1, 1])
468        .arg_ptr(h_state)
469        .arg_ptr(query)
470        .arg_ptr(key)
471        .arg_ptr(value)
472        .arg_ptr(gate)
473        .arg_ptr(beta)
474        .arg_ptr(output)
475        .arg_ptr(h_state_intermediate)
476        .arg_u32(batch_size)
477        .arg_u32(num_k_heads)
478        .arg_u32(num_v_heads)
479        .arg_u32(k_dim)
480        .arg_u32(v_dim)
481        .arg_u32(qk_stride)
482        .arg_u32(v_stride)
483        .arg_u32(gb_stride)
484        .launch(stream)
485}