metrale_model_layers/layers/ops/
prefill_attn_main_a.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: Launchers for the prefill flash-attention kernels: contiguous
4//! Q/K/V (BF16 and FP8 K/V, 32- and 64-row tiles, the HDIM=512 sink variant)
5//! and paged KV (BF16 and FP8, causal and the bidirectional DFlash launches).
6//!
7//! Owner: model-layers ops.
8//! Invariants: none beyond the types.
9
10#![allow(unused_imports)]
11
12use anyhow::Result;
13use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
14use metrale_gpu_runtime::kernel_args::{KernelLaunch, div_ceil};
15
16use crate::layers::moe;
17use crate::weight_map::{DenseWeight, Fp8DenseWeight, Fp8Weight, QuantizedWeight};
18
19use super::*;
20
21/// 2026-09-25: Flash-attention prefill on contiguous BF16 tensors: Q and O
22/// `[batch, seq_len, num_q_heads, head_dim]`, K and V `[batch, seq_len,
23/// num_kv_heads, head_dim]`. `sliding_window == 0` disables the window;
24/// otherwise keys with `q - k >= sliding_window` are masked.
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,
41    stream: u64,
42) -> Result<()> {
43    // 2026-09-25: For head_dim > 256 the handle is resolved in `init.rs` and the
44    // grid is built here; both call `wide_prefill_kernel()`, which returns the
45    // kernel and its BR together.
46    let br = if head_dim > 256 {
47        wide_prefill_kernel(gpu).1
48    } else {
49        32u32
50    };
51    KernelLaunch::new(gpu, kernel)
52        .grid([num_q_heads, div_ceil(seq_len, br), batch])
53        .block([128, 1, 1])
54        .arg_ptr(q)
55        .arg_ptr(k)
56        .arg_ptr(v)
57        .arg_ptr(output)
58        .arg_u32(seq_len)
59        .arg_u32(num_q_heads)
60        .arg_u32(num_kv_heads)
61        .arg_u32(head_dim)
62        .arg_f32(inv_sqrt_d)
63        .arg_u32(if causal { 1 } else { 0 })
64        .arg_u32(sliding_window)
65        .launch(stream)
66}
67
68/// 2026-09-25: [`prefill_attention`] for the deepseek-v4-flash `attn_prefill_512`
69/// kernel (HDIM 512, 16 query rows per block) with a per-head sink logit:
70/// `sinks` is FP32 `[num_q_heads]`, added to the softmax denominator only.
71/// `DevicePtr::NULL` means no sink.
72#[allow(clippy::too_many_arguments)]
73pub fn prefill_attention_512_sink(
74    gpu: &dyn GpuBackend,
75    kernel: KernelHandle,
76    q: DevicePtr,
77    k: DevicePtr,
78    v: DevicePtr,
79    output: DevicePtr,
80    seq_len: u32,
81    batch: u32,
82    num_q_heads: u32,
83    num_kv_heads: u32,
84    head_dim: u32,
85    inv_sqrt_d: f32,
86    causal: bool,
87    sliding_window: u32,
88    sinks: DevicePtr,
89    stream: u64,
90) -> Result<()> {
91    KernelLaunch::new(gpu, kernel)
92        .grid([num_q_heads, div_ceil(seq_len, 16), batch])
93        .block([128, 1, 1])
94        .arg_ptr(q)
95        .arg_ptr(k)
96        .arg_ptr(v)
97        .arg_ptr(output)
98        .arg_u32(seq_len)
99        .arg_u32(num_q_heads)
100        .arg_u32(num_kv_heads)
101        .arg_u32(head_dim)
102        .arg_f32(inv_sqrt_d)
103        .arg_u32(if causal { 1 } else { 0 })
104        .arg_u32(sliding_window)
105        .arg_ptr(sinks)
106        .launch(stream)
107}
108
109/// 2026-09-25: [`prefill_attention`] with 64 query rows per block (32 on the
110/// SCALE and HIP targets).
111#[allow(clippy::too_many_arguments)]
112pub fn prefill_attention_64(
113    gpu: &dyn GpuBackend,
114    kernel: KernelHandle,
115    q: DevicePtr,
116    k: DevicePtr,
117    v: DevicePtr,
118    output: DevicePtr,
119    seq_len: u32,
120    batch: u32,
121    num_q_heads: u32,
122    num_kv_heads: u32,
123    head_dim: u32,
124    inv_sqrt_d: f32,
125    causal: bool,
126    sliding_window: u32,
127    stream: u64,
128) -> Result<()> {
129    // 2026-09-25: The grid's rows per block must equal the kernel's `BR64`, or
130    // rows 32..63 of every 64-row band are never written. `BR64` is 32 under
131    // `__SCALE__` (gb10 `attn_prefill.cu`) and `__SCALE__ || __HIP_PLATFORM_AMD__`
132    // (strix-hip), for the 64 KB LDS limit; `metrale_scale` is set for exactly
133    // those targets, `strix` and `strix-hip` (`crates/model-layers/build.rs`).
134    let br = if cfg!(metrale_scale) { 32u32 } else { 64u32 };
135    KernelLaunch::new(gpu, kernel)
136        .grid([num_q_heads, div_ceil(seq_len, br), batch])
137        .block([256, 1, 1])
138        .arg_ptr(q)
139        .arg_ptr(k)
140        .arg_ptr(v)
141        .arg_ptr(output)
142        .arg_u32(seq_len)
143        .arg_u32(num_q_heads)
144        .arg_u32(num_kv_heads)
145        .arg_u32(head_dim)
146        .arg_f32(inv_sqrt_d)
147        .arg_u32(if causal { 1 } else { 0 })
148        .arg_u32(sliding_window)
149        .launch(stream)
150}
151
152/// 2026-09-25: Contiguous prefill with BF16 Q and FP8 E4M3 K and V, which the
153/// kernel dequantizes to BF16 in shared memory; 64 query rows per block.
154#[allow(clippy::too_many_arguments)]
155pub fn prefill_attention_fp8kv(
156    gpu: &dyn GpuBackend,
157    kernel: KernelHandle,
158    q: DevicePtr,
159    k_fp8: DevicePtr,
160    v_fp8: DevicePtr,
161    output: DevicePtr,
162    seq_len: u32,
163    batch: u32,
164    num_q_heads: u32,
165    num_kv_heads: u32,
166    head_dim: u32,
167    inv_sqrt_d: f32,
168    causal: bool,
169    stream: u64,
170) -> Result<()> {
171    let br = 64u32;
172    KernelLaunch::new(gpu, kernel)
173        .grid([num_q_heads, div_ceil(seq_len, br), batch])
174        .block([256, 1, 1])
175        .arg_ptr(q)
176        .arg_ptr(k_fp8)
177        .arg_ptr(v_fp8)
178        .arg_ptr(output)
179        .arg_u32(seq_len)
180        .arg_u32(num_q_heads)
181        .arg_u32(num_kv_heads)
182        .arg_u32(head_dim)
183        .arg_f32(inv_sqrt_d)
184        .arg_u32(if causal { 1 } else { 0 })
185        .launch(stream)
186}
187
188/// 2026-09-25: Causal flash-attention prefill of a contiguous Q chunk against
189/// K and V in the paged BF16 cache, found through `block_table`. Query row `i`
190/// is at absolute position `q_offset + i`; 32 query rows per block.
191#[allow(clippy::too_many_arguments)]
192pub fn prefill_attention_paged(
193    gpu: &dyn GpuBackend,
194    kernel: KernelHandle,
195    q: DevicePtr,
196    k_cache: DevicePtr,
197    v_cache: DevicePtr,
198    output: DevicePtr,
199    block_table: DevicePtr,
200    q_len: u32,
201    kv_len: u32,
202    q_offset: u32,
203    num_q_heads: u32,
204    num_kv_heads: u32,
205    head_dim: u32,
206    cache_block_size: u32,
207    sliding_window: u32,
208    inv_sqrt_d: f32,
209    stream: u64,
210) -> Result<()> {
211    let br = 32u32;
212    KernelLaunch::new(gpu, kernel)
213        .grid([num_q_heads, div_ceil(q_len, br), 1])
214        .block([128, 1, 1])
215        .arg_ptr(q)
216        .arg_ptr(k_cache)
217        .arg_ptr(v_cache)
218        .arg_ptr(output)
219        .arg_ptr(block_table)
220        .arg_u32(q_len)
221        .arg_u32(kv_len)
222        .arg_u32(q_offset)
223        .arg_u32(num_q_heads)
224        .arg_u32(num_kv_heads)
225        .arg_u32(head_dim)
226        .arg_u32(cache_block_size)
227        .arg_u32(sliding_window)
228        .arg_u32(1u32)
229        .arg_f32(inv_sqrt_d)
230        .launch(stream)
231}
232
233/// 2026-09-25: [`prefill_attention_paged`] over an FP8 KV cache.
234#[allow(clippy::too_many_arguments)]
235pub fn prefill_attention_paged_fp8(
236    gpu: &dyn GpuBackend,
237    kernel: KernelHandle,
238    q: DevicePtr,
239    k_cache: DevicePtr,
240    v_cache: DevicePtr,
241    output: DevicePtr,
242    block_table: DevicePtr,
243    q_len: u32,
244    kv_len: u32,
245    q_offset: u32,
246    num_q_heads: u32,
247    num_kv_heads: u32,
248    head_dim: u32,
249    cache_block_size: u32,
250    sliding_window: u32,
251    inv_sqrt_d: f32,
252    k_scale: f32,
253    v_scale: f32,
254    cache_stride: u64,
255    stream: u64,
256) -> Result<()> {
257    let br = 32u32;
258    KernelLaunch::new(gpu, kernel)
259        .grid([num_q_heads, div_ceil(q_len, br), 1])
260        .block([128, 1, 1])
261        .arg_ptr(q)
262        .arg_ptr(k_cache)
263        .arg_ptr(v_cache)
264        .arg_ptr(output)
265        .arg_ptr(block_table)
266        .arg_u32(q_len)
267        .arg_u32(kv_len)
268        .arg_u32(q_offset)
269        .arg_u32(num_q_heads)
270        .arg_u32(num_kv_heads)
271        .arg_u32(head_dim)
272        .arg_u32(cache_block_size)
273        .arg_u32(sliding_window)
274        .arg_u32(1u32)
275        .arg_f32(inv_sqrt_d)
276        .arg_f32(k_scale)
277        .arg_f32(v_scale)
278        .arg_u64(cache_stride)
279        .launch(stream)
280}
281
282/// 2026-09-25: [`prefill_attention_paged_fp8`] launched with
283/// `causal_mask_enabled = 0`: the causal mask is skipped, so a query of the
284/// DFlash draft block also attends to the block's later positions.
285#[allow(clippy::too_many_arguments)]
286pub fn prefill_attention_paged_fp8_dflash(
287    gpu: &dyn GpuBackend,
288    kernel: KernelHandle,
289    q: DevicePtr,
290    k_cache: DevicePtr,
291    v_cache: DevicePtr,
292    output: DevicePtr,
293    block_table: DevicePtr,
294    q_len: u32,
295    kv_len: u32,
296    q_offset: u32,
297    num_q_heads: u32,
298    num_kv_heads: u32,
299    head_dim: u32,
300    cache_block_size: u32,
301    sliding_window: u32,
302    inv_sqrt_d: f32,
303    k_scale: f32,
304    v_scale: f32,
305    cache_stride: u64,
306    stream: u64,
307) -> Result<()> {
308    let br = 32u32;
309    KernelLaunch::new(gpu, kernel)
310        .grid([num_q_heads, div_ceil(q_len, br), 1])
311        .block([128, 1, 1])
312        .arg_ptr(q)
313        .arg_ptr(k_cache)
314        .arg_ptr(v_cache)
315        .arg_ptr(output)
316        .arg_ptr(block_table)
317        .arg_u32(q_len)
318        .arg_u32(kv_len)
319        .arg_u32(q_offset)
320        .arg_u32(num_q_heads)
321        .arg_u32(num_kv_heads)
322        .arg_u32(head_dim)
323        .arg_u32(cache_block_size)
324        .arg_u32(sliding_window)
325        .arg_u32(0u32)
326        .arg_f32(inv_sqrt_d)
327        .arg_f32(k_scale)
328        .arg_f32(v_scale)
329        .arg_u64(cache_stride)
330        .launch(stream)
331}
332
333/// 2026-09-25: [`prefill_attention_paged`] launched with
334/// `causal_mask_enabled = 0`, the BF16-cache counterpart of
335/// [`prefill_attention_paged_fp8_dflash`].
336#[allow(clippy::too_many_arguments)]
337pub fn prefill_attention_paged_dflash(
338    gpu: &dyn GpuBackend,
339    kernel: KernelHandle,
340    q: DevicePtr,
341    k_cache: DevicePtr,
342    v_cache: DevicePtr,
343    output: DevicePtr,
344    block_table: DevicePtr,
345    q_len: u32,
346    kv_len: u32,
347    q_offset: u32,
348    num_q_heads: u32,
349    num_kv_heads: u32,
350    head_dim: u32,
351    cache_block_size: u32,
352    sliding_window: u32,
353    inv_sqrt_d: f32,
354    stream: u64,
355) -> Result<()> {
356    let br = 32u32;
357    KernelLaunch::new(gpu, kernel)
358        .grid([num_q_heads, div_ceil(q_len, br), 1])
359        .block([128, 1, 1])
360        .arg_ptr(q)
361        .arg_ptr(k_cache)
362        .arg_ptr(v_cache)
363        .arg_ptr(output)
364        .arg_ptr(block_table)
365        .arg_u32(q_len)
366        .arg_u32(kv_len)
367        .arg_u32(q_offset)
368        .arg_u32(num_q_heads)
369        .arg_u32(num_kv_heads)
370        .arg_u32(head_dim)
371        .arg_u32(cache_block_size)
372        .arg_u32(sliding_window)
373        .arg_u32(0u32)
374        .arg_f32(inv_sqrt_d)
375        .launch(stream)
376}
377
378/// 2026-09-25: [`prefill_attention_paged_dflash`] with `kv_len`, `q_offset` and
379/// `q_rope_pos` read on the device from `kv_len_q_offset_dev` (three u32 at byte
380/// offsets 0, 4 and 8) instead of passed as scalars, so a captured CUDA graph
381/// can replay the launch with values the host writes before each replay.
382/// `q_offset` addresses the cache; `q_rope_pos` is the absolute query position
383/// the causal and sliding-window masks compare against. The kernel applies no
384/// RoPE. Kernel: `attn_prefill_paged_indirect`.
385#[allow(clippy::too_many_arguments)]
386pub fn prefill_attention_paged_dflash_bf16_indirect(
387    gpu: &dyn GpuBackend,
388    kernel: KernelHandle,
389    q: DevicePtr,
390    k_cache: DevicePtr,
391    v_cache: DevicePtr,
392    output: DevicePtr,
393    block_table: DevicePtr,
394    q_len: u32,
395    kv_len_q_offset_dev: DevicePtr,
396    num_q_heads: u32,
397    num_kv_heads: u32,
398    head_dim: u32,
399    cache_block_size: u32,
400    sliding_window: u32,
401    inv_sqrt_d: f32,
402    stream: u64,
403) -> Result<()> {
404    let br = 32u32;
405    // 2026-09-25: The kernel's `KERNEL_PREAMBLE` overwrites its scalar `kv_len` and
406    // `q_offset` from the device buffer, so zeros are passed for both.
407    KernelLaunch::new(gpu, kernel)
408        .grid([num_q_heads, div_ceil(q_len, br), 1])
409        .block([128, 1, 1])
410        .arg_ptr(q)
411        .arg_ptr(k_cache)
412        .arg_ptr(v_cache)
413        .arg_ptr(output)
414        .arg_ptr(block_table)
415        .arg_u32(q_len)
416        .arg_u32(0u32)
417        .arg_u32(0u32)
418        .arg_u32(num_q_heads)
419        .arg_u32(num_kv_heads)
420        .arg_u32(head_dim)
421        .arg_u32(cache_block_size)
422        .arg_u32(sliding_window)
423        .arg_u32(0u32)
424        .arg_f32(inv_sqrt_d)
425        .arg_ptr(kv_len_q_offset_dev)
426        .arg_ptr(kv_len_q_offset_dev.offset(4))
427        .arg_ptr(kv_len_q_offset_dev.offset(8))
428        .launch(stream)
429}