spark_model/layers/ops/
ssm_gdn_wyn.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! `gdn_decode_wy4`, `gdn_decode_wyn` and the K=5..16 table-driven form.
4//!
5//! Split verbatim out of `ssm_gdn_b.rs`, which crossed the 500-LoC cap when
6//! the K=5..16 pointer-table twins landed (#837/#838: 391 -> 569). Free
7//! functions moved unchanged and `ops.rs` declares both halves, so no call
8//! site changes. Split at wy4 rather than wy2 so BOTH halves clear the cap.
9//!
10//! The bodies are an EXACT copy. If this file and `ssm_gdn_b.rs` ever
11//! disagree about a function, this one was edited by mistake.
12
13// SPDX-License-Identifier: AGPL-3.0-only
14
15#![allow(unused_imports)]
16
17use anyhow::Result;
18use spark_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
19use spark_runtime::kernel_args::{KernelLaunch, div_ceil};
20
21use crate::layers::moe;
22use crate::weight_map::{DenseWeight, Fp8DenseWeight, Fp8Weight, QuantizedWeight};
23
24use super::*;
25
26/// WY-chunkwise 4-token GDN decode (2-pass algorithm).
27///
28/// All 4 H^T @ k_t dot products computed in a single pass, then WY correction
29/// derives v_new values. Second pass applies all 4 state updates + outputs.
30/// 2 passes vs 5, reducing memory traffic by 60%.
31///
32/// Grid: (num_v_heads, batch, 1)  Block: (128, 1, 1)
33#[allow(clippy::too_many_arguments)]
34pub fn gdn_decode_wy4(
35    gpu: &dyn GpuBackend,
36    kernel: KernelHandle,
37    h_state: DevicePtr,
38    query: DevicePtr,
39    key: DevicePtr,
40    value: DevicePtr,
41    gate: DevicePtr,
42    beta: DevicePtr,
43    output: DevicePtr,
44    h_state_inter0: DevicePtr,
45    h_state_inter1: DevicePtr,
46    h_state_inter2: DevicePtr,
47    batch_size: u32,
48    num_k_heads: u32,
49    num_v_heads: u32,
50    k_dim: u32,
51    v_dim: u32,
52    qk_stride: u32,
53    v_stride: u32,
54    gb_stride: u32,
55    // false = contiguous state bases indexed by (b*num_v_heads+vh);
56    // true  = device pointer TABLES, one entry per sequence.
57    //
58    // Contiguous is only correct at batch_size==1: it assumes the intermediates
59    // share h_state's batch stride, but the pool's intermediate stride is
60    // num_intermediates x larger, so at n>1 sequence 1's Hi0 lands on sequence
61    // 0's Hi1 — silent cross-sequence rollback corruption. Pass true with staged
62    // tables for any batched verify. false is byte-identical to the old kernel.
63    state_is_table: bool,
64    stream: u64,
65) -> Result<()> {
66    // HARD guard, not debug_assert: this compiles out in release, which is
67    // exactly where the corruption would be silent.
68    anyhow::ensure!(
69        state_is_table || batch_size == 1,
70        "gdn_decode_wy4: contiguous state addressing is only valid at \
71         batch_size==1 (got {batch_size}) — the intermediates' pool stride is \
72         num_intermediates x h_state's, so sequence 1's Hi0 would land on \
73         sequence 0's Hi1. Stage pointer tables and pass state_is_table=true."
74    );
75    KernelLaunch::new(gpu, kernel)
76        .grid([num_v_heads, batch_size, 1])
77        .block([128, 1, 1])
78        .arg_ptr(h_state)
79        .arg_ptr(query)
80        .arg_ptr(key)
81        .arg_ptr(value)
82        .arg_ptr(gate)
83        .arg_ptr(beta)
84        .arg_ptr(output)
85        .arg_ptr(h_state_inter0)
86        .arg_ptr(h_state_inter1)
87        .arg_ptr(h_state_inter2)
88        .arg_u32(batch_size)
89        .arg_u32(num_k_heads)
90        .arg_u32(num_v_heads)
91        .arg_u32(k_dim)
92        .arg_u32(v_dim)
93        .arg_u32(qk_stride)
94        .arg_u32(v_stride)
95        .arg_u32(gb_stride)
96        .arg_u32(u32::from(state_is_table))
97        .launch(stream)
98}
99
100/// WY-Chunkwise Gated Delta Rule, pool-layout intermediates — K-generic
101/// launch shared by the K=17 DFlash verify (`gated_delta_rule_wy17`) and the
102/// chain-verify K∈{5..8} instantiations (`gated_delta_rule_wy5..wy8`, one
103/// templated source `gated_delta_rule_wyn.cu`). K is compile-time in the
104/// kernel; the caller selects it via the `kernel` handle. Computes K H·k dot
105/// products in 1 pass over H, applies WY algebraic correction over K tokens
106/// (K*(K-1)/2 inter-token k-dots), then applies K state updates in a second
107/// fused pass writing Hi_0..Hi_{K-2} + final H.
108///
109/// `h_state_inter_base` points to a contiguous pool of (K-1) intermediate
110/// H states per (layer, slot). Each Hi_t is at
111/// `h_state_inter_base + t * inter_stride_floats` (per (b, vh) sub-region).
112#[allow(clippy::too_many_arguments)]
113pub fn gdn_decode_wyn(
114    gpu: &dyn GpuBackend,
115    kernel: KernelHandle,
116    h_state: DevicePtr,
117    query: DevicePtr,
118    key: DevicePtr,
119    value: DevicePtr,
120    gate: DevicePtr,
121    beta: DevicePtr,
122    output: DevicePtr,
123    h_state_inter_base: DevicePtr,
124    inter_stride_floats: u32,
125    batch_size: u32,
126    num_k_heads: u32,
127    num_v_heads: u32,
128    k_dim: u32,
129    v_dim: u32,
130    qk_stride: u32,
131    v_stride: u32,
132    gb_stride: u32,
133    stream: u64,
134) -> Result<()> {
135    // `gdn_decode_wyn`'s kernel hardcodes the CONTIGUOUS state stride
136    // ((b*num_v_heads+vh)*hv) for BOTH h_state and the intermediates. That is
137    // wrong for the intermediates, whose pool stride is num_intermediates x
138    // larger — at batch_size>1 sequence 1's Hi0 lands on sequence 0's Hi1,
139    // silently corrupting cross-sequence rollback. Only wy4 has the
140    // `state_is_table` pointer-table form that sidesteps this. Refuse rather
141    // than corrupt; port the table form (see gated_delta_rule_wy4.cu) before
142    // enabling a batched verify at K<4.
143    anyhow::ensure!(
144        batch_size == 1,
145        "gdn_decode_wyn: contiguous state addressing is only valid at batch_size==1 \
146         (got {batch_size}); port the wy4 `state_is_table` pointer-table form first"
147    );
148    KernelLaunch::new(gpu, kernel)
149        .grid([num_v_heads, batch_size, 1])
150        .block([128, 1, 1])
151        .arg_ptr(h_state)
152        .arg_ptr(query)
153        .arg_ptr(key)
154        .arg_ptr(value)
155        .arg_ptr(gate)
156        .arg_ptr(beta)
157        .arg_ptr(output)
158        .arg_ptr(h_state_inter_base)
159        .arg_u32(inter_stride_floats)
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        .arg_u32(qk_stride)
166        .arg_u32(v_stride)
167        .arg_u32(gb_stride)
168        .launch(stream)
169}
170
171/// Cross-sequence batched-verify launch of the wyN pointer-table twins
172/// (`gated_delta_rule_wy{5..16}_table` / `_f16_table`): `state_is_table`
173/// is compiled into the symbol, so the argument list is identical to
174/// [`gdn_decode_wyn`] — but the STATE arguments mean something different
175/// and batching is the point:
176/// - `h_tables`   = the layer's staged table slice: slab 0 holds one
177///   per-sequence H base pointer per entry (`VERIFY_WY_TABLE_SEQS` slots).
178/// - `hi_tables`  = slab 1 (Hi0); consecutive Hi slabs follow at
179///   `VERIFY_WY_TABLE_SEQS`-entry stride.
180/// - `slab_entry_stride` MUST be `VERIFY_WY_TABLE_SEQS as u32` — the
181///   kernel reinterprets the contiguous-form stride argument as "pointer
182///   entries between Hi slabs".
183///
184/// Tables are staged by `upload_verify_wy_tables` (verify_e2.rs) into a
185/// fixed buffer refreshed pre-replay, so the launch is CUDA-graph-stable.
186#[allow(clippy::too_many_arguments)]
187// provenance-id: 526f6e616c6420522e205374657369616b
188#[allow(dead_code)] // seam 2026-09-01: consumer = the dispatcher 5..=16 arm, next change
189pub fn gdn_decode_wyn_table(
190    gpu: &dyn GpuBackend,
191    kernel: KernelHandle,
192    h_tables: DevicePtr,
193    query: DevicePtr,
194    key: DevicePtr,
195    value: DevicePtr,
196    gate: DevicePtr,
197    beta: DevicePtr,
198    output: DevicePtr,
199    hi_tables: DevicePtr,
200    slab_entry_stride: u32,
201    batch_size: u32,
202    num_k_heads: u32,
203    num_v_heads: u32,
204    k_dim: u32,
205    v_dim: u32,
206    qk_stride: u32,
207    v_stride: u32,
208    gb_stride: u32,
209    stream: u64,
210) -> Result<()> {
211    anyhow::ensure!(
212        !h_tables.is_null() && !hi_tables.is_null(),
213        "gdn_decode_wyn_table: staged pointer tables are required (NULL table \
214         means upload_verify_wy_tables declined — run the per-sequence loop)"
215    );
216    KernelLaunch::new(gpu, kernel)
217        .grid([num_v_heads, batch_size, 1])
218        .block([128, 1, 1])
219        .arg_ptr(h_tables)
220        .arg_ptr(query)
221        .arg_ptr(key)
222        .arg_ptr(value)
223        .arg_ptr(gate)
224        .arg_ptr(beta)
225        .arg_ptr(output)
226        .arg_ptr(hi_tables)
227        .arg_u32(slab_entry_stride)
228        .arg_u32(batch_size)
229        .arg_u32(num_k_heads)
230        .arg_u32(num_v_heads)
231        .arg_u32(k_dim)
232        .arg_u32(v_dim)
233        .arg_u32(qk_stride)
234        .arg_u32(v_stride)
235        .arg_u32(gb_stride)
236        .launch(stream)
237}
238
239/// Fused 2-token conv1d sliding window update + SiLU.
240///
241/// Each thread handles one channel independently. The 2-token dependency
242/// (token 1's window includes token 0's input) is resolved in registers.
243/// Saves intermediate conv_state (after token 0) for rollback.
244///
245/// Kernel: `causal_conv1d_update_chunk2(conv_state, input, weight, bias,
246///          output, conv_state_intermediate, batch, dim, d_conv)`
247/// Grid: (ceil(dim/256), batch, 1)  Block: (256, 1, 1)
248#[allow(clippy::too_many_arguments)]
249pub fn conv1d_update_chunk2(
250    gpu: &dyn GpuBackend,
251    kernel: KernelHandle,
252    conv_state: DevicePtr,
253    input: DevicePtr,
254    weight: &DenseWeight,
255    output: DevicePtr,
256    conv_state_intermediate: DevicePtr,
257    d_inner: u32,
258    d_conv: u32,
259    batch_size: u32,
260    stream: u64,
261) -> Result<()> {
262    let bias_ptr = DevicePtr::NULL;
263    KernelLaunch::new(gpu, kernel)
264        .grid([div_ceil(d_inner, 256), batch_size, 1])
265        .block([256, 1, 1])
266        .arg_ptr(conv_state)
267        .arg_ptr(input)
268        .arg_ptr(weight.weight)
269        .arg_ptr(bias_ptr)
270        .arg_ptr(output)
271        .arg_ptr(conv_state_intermediate)
272        .arg_u32(batch_size)
273        .arg_u32(d_inner)
274        .arg_u32(d_conv)
275        .launch(stream)
276}