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}