spark_model/layers/ops/
prefill_attn_main_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/// Flash Attention v2 prefill on contiguous Q/K/V.
17///
18/// Kernel: `inferspark_prefill(Q, K, V, O, seq_len, num_q_heads, num_kv_heads,
19///          head_dim, inv_sqrt_d, causal)`
20/// Grid: (num_q_heads, ceil(seq_len/32), batch)  Block: (128, 1, 1)
21///
22/// Layout: Q [batch, seq_len, num_q_heads, head_dim] BF16
23///         K/V [batch, seq_len, num_kv_heads, head_dim] BF16
24///         O [batch, seq_len, num_q_heads, head_dim] BF16
25#[allow(clippy::too_many_arguments)]
26pub fn prefill_attention(
27    gpu: &dyn GpuBackend,
28    kernel: KernelHandle,
29    q: DevicePtr,
30    k: DevicePtr,
31    v: DevicePtr,
32    output: DevicePtr,
33    seq_len: u32,
34    batch: u32,
35    num_q_heads: u32,
36    num_kv_heads: u32,
37    head_dim: u32,
38    inv_sqrt_d: f32,
39    causal: bool,
40    sliding_window: u32, // 0 = no sliding limit; >0 = mask keys where q - k >= window
41    stream: u64,
42) -> Result<()> {
43    // ★ SSOT: the kernel NAME and its BR must agree, and they are chosen in two
44    // different files — `init.rs` resolves the handle, this launches it. A grid
45    // computed from the wrong BR does not fail, it silently computes the wrong
46    // q-tiles. `wide_prefill_kernel()` is the one reader of that decision.
47    let br = if head_dim > 256 {
48        wide_prefill_kernel(gpu).1
49    } else {
50        32u32
51    };
52    KernelLaunch::new(gpu, kernel)
53        .grid([num_q_heads, div_ceil(seq_len, br), batch])
54        .block([128, 1, 1])
55        .arg_ptr(q)
56        .arg_ptr(k)
57        .arg_ptr(v)
58        .arg_ptr(output)
59        .arg_u32(seq_len)
60        .arg_u32(num_q_heads)
61        .arg_u32(num_kv_heads)
62        .arg_u32(head_dim)
63        .arg_f32(inv_sqrt_d)
64        .arg_u32(if causal { 1 } else { 0 })
65        .arg_u32(sliding_window)
66        .launch(stream)
67}
68
69/// DeepSeek-V4 full-attention (non-CSA) prefill with a per-head attention sink.
70///
71/// Same as [`prefill_attention`] for HDIM=512 (BR=16), but passes the per-head
72/// `sinks` logit so the softmax denominator matches the decode path (which
73/// always applies the sink). Launches the V4-specific `inferspark_prefill_512`
74/// kernel. `sinks` may be `DevicePtr::NULL` for no sink.
75#[allow(clippy::too_many_arguments)]
76pub fn prefill_attention_512_sink(
77    gpu: &dyn GpuBackend,
78    kernel: KernelHandle,
79    q: DevicePtr,
80    k: DevicePtr,
81    v: DevicePtr,
82    output: DevicePtr,
83    seq_len: u32,
84    batch: u32,
85    num_q_heads: u32,
86    num_kv_heads: u32,
87    head_dim: u32,
88    inv_sqrt_d: f32,
89    causal: bool,
90    sliding_window: u32,
91    sinks: DevicePtr,
92    stream: u64,
93) -> Result<()> {
94    KernelLaunch::new(gpu, kernel)
95        .grid([num_q_heads, div_ceil(seq_len, 16), batch])
96        .block([128, 1, 1])
97        .arg_ptr(q)
98        .arg_ptr(k)
99        .arg_ptr(v)
100        .arg_ptr(output)
101        .arg_u32(seq_len)
102        .arg_u32(num_q_heads)
103        .arg_u32(num_kv_heads)
104        .arg_u32(head_dim)
105        .arg_f32(inv_sqrt_d)
106        .arg_u32(if causal { 1 } else { 0 })
107        .arg_u32(sliding_window)
108        .arg_ptr(sinks)
109        .launch(stream)
110}
111
112/// Contiguous prefill Flash Attention — BF16, BR=64 (256 threads).
113///
114/// Larger tile size halves CTA count and causal KV iterations for long sequences.
115/// Grid: (num_q_heads, ceil(seq_len/64), batch)  Block: (256, 1, 1)
116#[allow(clippy::too_many_arguments)]
117pub fn prefill_attention_64(
118    gpu: &dyn GpuBackend,
119    kernel: KernelHandle,
120    q: DevicePtr,
121    k: DevicePtr,
122    v: DevicePtr,
123    output: DevicePtr,
124    seq_len: u32,
125    batch: u32,
126    num_q_heads: u32,
127    num_kv_heads: u32,
128    head_dim: u32,
129    inv_sqrt_d: f32,
130    causal: bool,
131    sliding_window: u32,
132    stream: u64,
133) -> Result<()> {
134    // BR64 = query rows processed per CTA. The kernel clamps this to 32 on the
135    // AMD targets (gfx1151 64 KB LDS cap: inferspark_prefill.cu's
136    // `#if __SCALE__ || __HIP_PLATFORM_AMD__ #define BR64 32`). The grid stride
137    // MUST match the kernel's BR64, else CTAs are spaced 64 rows apart while each
138    // writes only 32 → query rows 32..63 of every 64-row band are silently left
139    // unwritten (gross attention corruption for any prompt >32 tokens). cfg!
140    // (atlas_scale) is set for both `strix` and `strix-hip`; NVIDIA keeps 64
141    // (byte-identical). See the @human-review note in inferspark_prefill.cu.
142    let br = if cfg!(atlas_scale) { 32u32 } else { 64u32 };
143    KernelLaunch::new(gpu, kernel)
144        .grid([num_q_heads, div_ceil(seq_len, br), batch])
145        .block([256, 1, 1])
146        .arg_ptr(q)
147        .arg_ptr(k)
148        .arg_ptr(v)
149        .arg_ptr(output)
150        .arg_u32(seq_len)
151        .arg_u32(num_q_heads)
152        .arg_u32(num_kv_heads)
153        .arg_u32(head_dim)
154        .arg_f32(inv_sqrt_d)
155        .arg_u32(if causal { 1 } else { 0 })
156        .arg_u32(sliding_window)
157        .launch(stream)
158}
159
160/// Contiguous prefill Flash Attention — FP8 E4M3 K/V variant (BR=64).
161///
162/// Q is BF16, K/V are FP8 E4M3 (dequantized to BF16 in shared memory).
163/// Halves K/V memory reads compared to the BF16 kernel.
164///
165/// Grid: (num_q_heads, ceil(seq_len/64), batch)  Block: (256, 1, 1)
166#[allow(clippy::too_many_arguments)]
167pub fn prefill_attention_fp8kv(
168    gpu: &dyn GpuBackend,
169    kernel: KernelHandle,
170    q: DevicePtr,
171    k_fp8: DevicePtr,
172    v_fp8: DevicePtr,
173    output: DevicePtr,
174    seq_len: u32,
175    batch: u32,
176    num_q_heads: u32,
177    num_kv_heads: u32,
178    head_dim: u32,
179    inv_sqrt_d: f32,
180    causal: bool,
181    stream: u64,
182) -> Result<()> {
183    let br = 64u32;
184    KernelLaunch::new(gpu, kernel)
185        .grid([num_q_heads, div_ceil(seq_len, br), batch])
186        .block([256, 1, 1])
187        .arg_ptr(q)
188        .arg_ptr(k_fp8)
189        .arg_ptr(v_fp8)
190        .arg_ptr(output)
191        .arg_u32(seq_len)
192        .arg_u32(num_q_heads)
193        .arg_u32(num_kv_heads)
194        .arg_u32(head_dim)
195        .arg_f32(inv_sqrt_d)
196        .arg_u32(if causal { 1 } else { 0 })
197        .launch(stream)
198}
199
200/// Paged prefill Flash Attention — reads K/V from paged KV cache via block_table.
201///
202/// For chunked prefill chunk 1+: Q comes from GEMM (contiguous), K/V reside
203/// in the paged cache from prior chunks. Replaces per-token paged decode loop
204/// with a single Flash Attention pass (O(N) per chunk instead of O(N^2) total).
205///
206/// Kernel: `inferspark_prefill_paged(Q, K_cache, V_cache, O, block_table,
207///          q_len, kv_len, q_offset, nq, nkv, hd, block_size, inv_sqrt_d)`
208/// Grid: (num_q_heads, ceil(q_len/32), 1)  Block: (128, 1, 1)
209#[allow(clippy::too_many_arguments)]
210pub fn prefill_attention_paged(
211    gpu: &dyn GpuBackend,
212    kernel: KernelHandle,
213    q: DevicePtr,
214    k_cache: DevicePtr,
215    v_cache: DevicePtr,
216    output: DevicePtr,
217    block_table: DevicePtr,
218    q_len: u32,
219    kv_len: u32,
220    q_offset: u32,
221    num_q_heads: u32,
222    num_kv_heads: u32,
223    head_dim: u32,
224    cache_block_size: u32,
225    sliding_window: u32,
226    inv_sqrt_d: f32,
227    stream: u64,
228) -> Result<()> {
229    let br = 32u32;
230    KernelLaunch::new(gpu, kernel)
231        .grid([num_q_heads, div_ceil(q_len, br), 1])
232        .block([128, 1, 1])
233        .arg_ptr(q)
234        .arg_ptr(k_cache)
235        .arg_ptr(v_cache)
236        .arg_ptr(output)
237        .arg_ptr(block_table)
238        .arg_u32(q_len)
239        .arg_u32(kv_len)
240        .arg_u32(q_offset)
241        .arg_u32(num_q_heads)
242        .arg_u32(num_kv_heads)
243        .arg_u32(head_dim)
244        .arg_u32(cache_block_size)
245        .arg_u32(sliding_window)
246        // causal_mask_enabled = 1 (default causal). DFlash γ-block kernels
247        // pass 0 via dedicated dispatchers (`prefill_attention_paged_dflash_*`).
248        .arg_u32(1u32)
249        .arg_f32(inv_sqrt_d)
250        .launch(stream)
251}
252
253/// Paged prefill Flash Attention — FP8 KV cache variant.
254#[allow(clippy::too_many_arguments)]
255pub fn prefill_attention_paged_fp8(
256    gpu: &dyn GpuBackend,
257    kernel: KernelHandle,
258    q: DevicePtr,
259    k_cache: DevicePtr,
260    v_cache: DevicePtr,
261    output: DevicePtr,
262    block_table: DevicePtr,
263    q_len: u32,
264    kv_len: u32,
265    q_offset: u32,
266    num_q_heads: u32,
267    num_kv_heads: u32,
268    head_dim: u32,
269    cache_block_size: u32,
270    sliding_window: u32,
271    inv_sqrt_d: f32,
272    k_scale: f32,
273    v_scale: f32,
274    cache_stride: u64,
275    stream: u64,
276) -> Result<()> {
277    let br = 32u32;
278    KernelLaunch::new(gpu, kernel)
279        .grid([num_q_heads, div_ceil(q_len, br), 1])
280        .block([128, 1, 1])
281        .arg_ptr(q)
282        .arg_ptr(k_cache)
283        .arg_ptr(v_cache)
284        .arg_ptr(output)
285        .arg_ptr(block_table)
286        .arg_u32(q_len)
287        .arg_u32(kv_len)
288        .arg_u32(q_offset)
289        .arg_u32(num_q_heads)
290        .arg_u32(num_kv_heads)
291        .arg_u32(head_dim)
292        .arg_u32(cache_block_size)
293        .arg_u32(sliding_window)
294        // causal_mask_enabled = 1 (default causal). DFlash γ-block kernels
295        // pass 0 via dedicated dispatchers (`prefill_attention_paged_dflash_*`).
296        .arg_u32(1u32)
297        .arg_f32(inv_sqrt_d)
298        .arg_f32(k_scale)
299        .arg_f32(v_scale)
300        .arg_u64(cache_stride)
301        .launch(stream)
302}
303
304/// DFlash γ-block paged Flash Attention — FP8 KV cache variant.
305///
306/// Same kernel binary as [`prefill_attention_paged_fp8`] but launched with
307/// `causal_mask_enabled = 0`, producing bidirectional attention within the
308/// γ-token query block. The prefix KV positions are still strictly less
309/// than `q_offset` so they need no causal mask in this mode (every prefix
310/// position is "older" than every query, which is the no-mask case anyway).
311///
312/// Used by `BlockDiffusionDraftHead::forward_block` once per drafter layer.
313/// `q_len` is γ (typically 16). `q_offset` is the absolute starting index
314/// of the γ-block in the drafter's logical sequence; the kernel uses it to
315/// skip the now-disabled causal compare against `kv_start+col`.
316#[allow(clippy::too_many_arguments)]
317pub fn prefill_attention_paged_fp8_dflash(
318    gpu: &dyn GpuBackend,
319    kernel: KernelHandle,
320    q: DevicePtr,
321    k_cache: DevicePtr,
322    v_cache: DevicePtr,
323    output: DevicePtr,
324    block_table: DevicePtr,
325    q_len: u32,
326    kv_len: u32,
327    q_offset: u32,
328    num_q_heads: u32,
329    num_kv_heads: u32,
330    head_dim: u32,
331    cache_block_size: u32,
332    sliding_window: u32,
333    inv_sqrt_d: f32,
334    k_scale: f32,
335    v_scale: f32,
336    cache_stride: u64,
337    stream: u64,
338) -> Result<()> {
339    let br = 32u32;
340    KernelLaunch::new(gpu, kernel)
341        .grid([num_q_heads, div_ceil(q_len, br), 1])
342        .block([128, 1, 1])
343        .arg_ptr(q)
344        .arg_ptr(k_cache)
345        .arg_ptr(v_cache)
346        .arg_ptr(output)
347        .arg_ptr(block_table)
348        .arg_u32(q_len)
349        .arg_u32(kv_len)
350        .arg_u32(q_offset)
351        .arg_u32(num_q_heads)
352        .arg_u32(num_kv_heads)
353        .arg_u32(head_dim)
354        .arg_u32(cache_block_size)
355        .arg_u32(sliding_window)
356        .arg_u32(0u32) // causal_mask_enabled = 0 (DFlash bidirectional)
357        .arg_f32(inv_sqrt_d)
358        .arg_f32(k_scale)
359        .arg_f32(v_scale)
360        .arg_u64(cache_stride)
361        .launch(stream)
362}
363
364/// DFlash γ-block paged Flash Attention — BF16 KV cache variant.
365///
366/// Same kernel binary as [`prefill_attention_paged`] but launched with
367/// `causal_mask_enabled = 0`, producing bidirectional attention within the
368/// γ-token query block. The prefix KV positions are strictly less than
369/// `q_offset` so they need no causal mask in this mode (every prefix
370/// position is "older" than every query, which is the no-mask case anyway).
371///
372/// Used by `BlockDiffusionDraftHead::forward_block` once per drafter layer
373/// when the drafter KV cache is BF16 (current default — FP8 acceptance
374/// collapses on SM12.x per `dflash_head.rs:82–86`).
375///
376/// `q_len` is γ (typically 16). `q_offset` is the absolute starting index
377/// of the γ-block in the drafter's logical sequence; the kernel uses it to
378/// skip the now-disabled causal compare against `kv_start+col`.
379#[allow(clippy::too_many_arguments)]
380pub fn prefill_attention_paged_dflash(
381    gpu: &dyn GpuBackend,
382    kernel: KernelHandle,
383    q: DevicePtr,
384    k_cache: DevicePtr,
385    v_cache: DevicePtr,
386    output: DevicePtr,
387    block_table: DevicePtr,
388    q_len: u32,
389    kv_len: u32,
390    q_offset: u32,
391    num_q_heads: u32,
392    num_kv_heads: u32,
393    head_dim: u32,
394    cache_block_size: u32,
395    sliding_window: u32,
396    inv_sqrt_d: f32,
397    stream: u64,
398) -> Result<()> {
399    let br = 32u32;
400    KernelLaunch::new(gpu, kernel)
401        .grid([num_q_heads, div_ceil(q_len, br), 1])
402        .block([128, 1, 1])
403        .arg_ptr(q)
404        .arg_ptr(k_cache)
405        .arg_ptr(v_cache)
406        .arg_ptr(output)
407        .arg_ptr(block_table)
408        .arg_u32(q_len)
409        .arg_u32(kv_len)
410        .arg_u32(q_offset)
411        .arg_u32(num_q_heads)
412        .arg_u32(num_kv_heads)
413        .arg_u32(head_dim)
414        .arg_u32(cache_block_size)
415        .arg_u32(sliding_window)
416        .arg_u32(0u32) // causal_mask_enabled = 0 (DFlash bidirectional)
417        .arg_f32(inv_sqrt_d)
418        // Non-indirect kernel: q_rope_pos is a local var (= q_offset) in the
419        // .cuh body — no extra kernel arg. Fix applies via indirect path only.
420        .launch(stream)
421}
422
423/// DFlash γ-block paged Flash Attention — BF16 KV cache, INDIRECT scalar args.
424///
425/// Phase 5 (CUDA graph) variant of [`prefill_attention_paged_dflash`]. Reads
426/// `kv_len`, `q_offset`, and `q_rope_pos` from device pointers at kernel entry
427/// instead of taking them as kernel scalar arguments. This makes the launch
428/// graph-friendly: the host writes the dynamic triple into `kv_len_q_offset_dev`
429/// (12 bytes: `[u32 kv_len, u32 q_offset, u32 q_rope_pos]`) BEFORE entering the
430/// captured region, and the captured graph node binds only the pointer — replays
431/// pick up whatever values the host wrote pre-launch.
432/// `q_offset` = ctx_count (cache-block addressing); `q_rope_pos` = absolute
433/// decode position (query RoPE rotation, decoupled from cache addressing).
434///
435/// Resolves to kernel `inferspark_prefill_paged_indirect`. The kernel binary
436/// is otherwise identical to `inferspark_prefill_paged` (`causal_mask_enabled
437/// = 0` is still hardcoded here on the launch side).
438///
439/// Phase B note: defined but NOT YET WIRED IN to forward_block_layer_paged.
440/// Phase C swaps the dispatcher; Phase D adds graph capture around it.
441#[allow(clippy::too_many_arguments)]
442pub fn prefill_attention_paged_dflash_bf16_indirect(
443    gpu: &dyn GpuBackend,
444    kernel: KernelHandle,
445    q: DevicePtr,
446    k_cache: DevicePtr,
447    v_cache: DevicePtr,
448    output: DevicePtr,
449    block_table: DevicePtr,
450    q_len: u32,
451    kv_len_q_offset_dev: DevicePtr,
452    num_q_heads: u32,
453    num_kv_heads: u32,
454    head_dim: u32,
455    cache_block_size: u32,
456    sliding_window: u32,
457    inv_sqrt_d: f32,
458    stream: u64,
459) -> Result<()> {
460    let br = 32u32;
461    // q_offset's only role in the captured-args path is grid sizing; we still
462    // pass q_len (a model constant, γ) directly. The kernel will overwrite its
463    // scalar `kv_len`/`q_offset` slots from the indirect buffer at entry, so
464    // we feed placeholder zeros for those two args here — the values are
465    // *ignored* once `KERNEL_PREAMBLE` runs in the .cu file.
466    KernelLaunch::new(gpu, kernel)
467        .grid([num_q_heads, div_ceil(q_len, br), 1])
468        .block([128, 1, 1])
469        .arg_ptr(q)
470        .arg_ptr(k_cache)
471        .arg_ptr(v_cache)
472        .arg_ptr(output)
473        .arg_ptr(block_table)
474        .arg_u32(q_len)
475        .arg_u32(0u32) // kv_len placeholder — overwritten by KERNEL_PREAMBLE
476        .arg_u32(0u32) // q_offset placeholder — overwritten by KERNEL_PREAMBLE
477        .arg_u32(num_q_heads)
478        .arg_u32(num_kv_heads)
479        .arg_u32(head_dim)
480        .arg_u32(cache_block_size)
481        .arg_u32(sliding_window)
482        .arg_u32(0u32) // causal_mask_enabled = 0 (DFlash bidirectional)
483        .arg_f32(inv_sqrt_d)
484        .arg_ptr(kv_len_q_offset_dev) // KERNEL_EXTRA_PARAMS: kv_len_ptr
485        .arg_ptr(kv_len_q_offset_dev.offset(4)) // KERNEL_EXTRA_PARAMS: q_offset_ptr
486        .arg_ptr(kv_len_q_offset_dev.offset(8)) // KERNEL_EXTRA_PARAMS: q_rope_pos_ptr
487        .launch(stream)
488}