1use anyhow::{Context, Result, ensure};
15use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend};
16use metrale_gpu_runtime::kernel_args::KernelLaunch;
17
18use super::{
19 AttnMat, AttnV41, AttnV41LayerState, AttnV41LayerWeights, SharedV41, upload_i32_async,
20};
21use crate::deepseek_v41_ref::attn::window_topk_idxs;
22use metrale_model_layers::layers::ops::{kquant_mmvq_pair_w, kquant_q8_1_rows};
23
24fn sparse_split() -> u32 {
30 static V: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
31 *V.get_or_init(|| {
32 std::env::var("METRALE_DS41_SPARSE_SPLIT")
33 .ok()
34 .and_then(|v| v.parse().ok())
35 .filter(|&s| (1..=8).contains(&s))
36 .unwrap_or(2)
37 })
38}
39
40impl AttnV41 {
41 pub(super) fn q_kv_projections(
46 &self,
47 gpu: &dyn GpuBackend,
48 w: &AttnV41LayerWeights,
49 x: DevicePtr,
50 m: usize,
51 yarn: bool,
52 stream: u64,
53 ) -> Result<()> {
54 let c = &self.cfg;
55 let (nh, hd, dim) = (c.n_heads, c.head_dim, c.dim);
56 if let (AttnMat::Q2K(a), AttnMat::Q2K(kv), true) = (w.wq_a, w.wkv, m <= 8) {
59 kquant_q8_1_rows(
60 gpu,
61 self.k.q8_rows,
62 x,
63 self.a_q8,
64 m as u32,
65 dim as u32,
66 stream,
67 )?;
68 kquant_mmvq_pair_w(
69 gpu,
70 self.k.mmvq_q2k_pair_w,
71 (a, self.qr_raw, c.q_rank as u32),
72 (kv, self.kv_raw, hd as u32),
73 self.a_q8,
74 dim as u32,
75 m as u32,
76 stream,
77 )?;
78 } else {
79 self.gemm(gpu, x, w.wq_a, self.qr_raw, m, c.q_rank, dim, stream)?;
80 self.gemm(gpu, x, w.wkv, self.kv_raw, m, hd, dim, stream)?;
81 }
82 self.rmsnorm(
83 gpu,
84 false,
85 self.qr_raw,
86 w.q_norm,
87 self.qr,
88 m,
89 c.q_rank,
90 stream,
91 )?;
92 self.gemm(gpu, self.qr, w.wq_b, self.q, m, nh * hd, c.q_rank, stream)?;
93 self.rope(gpu, self.q, self.head_pos, m * nh, hd, yarn, false, stream)?;
94 self.rmsnorm(gpu, false, self.kv_raw, w.kv_norm, self.kv, m, hd, stream)?;
95 self.rope(gpu, self.kv, self.pos, m, hd, yarn, false, stream)?;
96 self.act_quant(gpu, self.kv, m * hd, stream)
97 }
98
99 #[allow(clippy::too_many_arguments)]
103 pub(super) fn attend_and_project(
104 &self,
105 gpu: &dyn GpuBackend,
106 w: &AttnV41LayerWeights,
107 rows_a: DevicePtr,
108 rows_a_len: usize,
109 rows_b: Option<DevicePtr>,
110 idx: DevicePtr,
111 topk: usize,
112 m: usize,
113 yarn: bool,
114 stream: u64,
115 ) -> Result<()> {
116 let c = &self.cfg;
117 let (nh, hd, dim) = (c.n_heads, c.head_dim, c.dim);
118 let scale = (hd as f32).powf(-0.5);
119 KernelLaunch::new(gpu, self.k.sparse_attn)
120 .grid([m as u32, nh as u32, sparse_split()])
121 .block([256, 1, 1])
122 .arg_ptr(self.q)
123 .arg_ptr(rows_a)
124 .arg_ptr(rows_b.unwrap_or(rows_a))
125 .arg_u32(rows_a_len as u32)
126 .arg_ptr(idx)
127 .arg_ptr(w.sink)
128 .arg_ptr(self.o)
129 .arg_u32(nh as u32)
130 .arg_u32(hd as u32)
131 .arg_u32(topk as u32)
132 .arg_f32(scale)
133 .launch(stream)?;
134 let o_copy = self.o_rot;
137 gpu.copy_d2d_async(self.o, o_copy, m * nh * hd * 2, stream)?;
138 self.rope(gpu, o_copy, self.head_pos, m * nh, hd, yarn, true, stream)?;
139
140 if let (AttnMat::Q2K(blocks), true) = (w.wo_a, m <= 8) {
142 self.wo_a_grouped(gpu, blocks, o_copy, m, stream)?;
143 } else {
144 self.wo_a_per_group(gpu, w, o_copy, m, stream)?;
145 }
146 self.gemm(
147 gpu,
148 self.og,
149 w.wo_b,
150 self.out,
151 m,
152 dim,
153 c.groups * c.o_rank,
154 stream,
155 )?;
156 Ok(())
157 }
158
159 pub(super) fn wo_a_per_group(
163 &self,
164 gpu: &dyn GpuBackend,
165 w: &AttnV41LayerWeights,
166 o_copy: DevicePtr,
167 m: usize,
168 stream: u64,
169 ) -> Result<()> {
170 let c = &self.cfg;
171 let (nh, hd) = (c.n_heads, c.head_dim);
172 let gw = c.gw();
173 for g in 0..c.groups {
174 KernelLaunch::new(gpu, self.k.slice_cols)
175 .grid([m as u32, 1, 1])
176 .block([256, 1, 1])
177 .arg_ptr(o_copy)
178 .arg_ptr(self.slice_in)
179 .arg_u32((nh * hd) as u32)
180 .arg_u32((g * gw) as u32)
181 .arg_u32(gw as u32)
182 .launch(stream)?;
183 self.gemm(
184 gpu,
185 self.slice_in,
186 w.wo_a.at_rows(g * c.o_rank, gw),
187 self.slice_out,
188 m,
189 c.o_rank,
190 gw,
191 stream,
192 )?;
193 KernelLaunch::new(gpu, self.k.scatter_cols)
194 .grid([m as u32, 1, 1])
195 .block([256, 1, 1])
196 .arg_ptr(self.slice_out)
197 .arg_ptr(self.og)
198 .arg_u32((c.groups * c.o_rank) as u32)
199 .arg_u32((g * c.o_rank) as u32)
200 .arg_u32(c.o_rank as u32)
201 .launch(stream)?;
202 }
203 Ok(())
204 }
205
206 pub fn decode_capturable(&self, w: &AttnV41LayerWeights) -> bool {
212 !w.role.is_kv_source && !w.role.is_index_source
213 }
214
215 pub fn decode_fixed_topk(&self, w: &AttnV41LayerWeights) -> usize {
221 self.decode_fixed_topk_of(w.role.ratio > 0)
222 }
223
224 fn decode_fixed_topk_of(&self, ratio_positive: bool) -> usize {
225 let c = &self.cfg;
226 if ratio_positive {
227 (c.window + c.index_topk).min(2048)
228 } else {
229 c.window
230 }
231 }
232
233 pub fn invalidate_decode_uploads(&mut self) {
237 self.decode_pos = None;
238 self.decode_idx = None;
239 self.decode_idx_win = None;
240 }
241
242 fn decode_idx_of(&self, ratio_positive: bool) -> DevicePtr {
245 if ratio_positive {
246 self.idx_dev
247 } else {
248 self.idx_dev_win
249 }
250 }
251
252 pub fn decode_prep(
260 &mut self,
261 w: &AttnV41LayerWeights,
262 shared: &SharedV41,
263 gpu: &dyn GpuBackend,
264 start_pos: usize,
265 stream: u64,
266 ) -> Result<Option<DevicePtr>> {
267 ensure!(
268 self.decode_capturable(w),
269 "attn_v41: decode_prep on a kv/index source layer"
270 );
271 self.decode_prep_role(w.role.ratio > 0, shared, gpu, start_pos, stream)
272 }
273
274 pub fn decode_prep_role(
280 &mut self,
281 ratio_positive: bool,
282 shared: &SharedV41,
283 gpu: &dyn GpuBackend,
284 start_pos: usize,
285 stream: u64,
286 ) -> Result<Option<DevicePtr>> {
287 let c = &self.cfg;
288 ensure!(
289 start_pos >= 1 && start_pos < c.max_seq,
290 "attn_v41: position {start_pos} outside the decode range 1..{}",
291 c.max_seq
292 );
293 if self.decode_pos != Some(start_pos) {
294 let p = start_pos as i32;
295 upload_i32_async(gpu, self.pos, &[p], stream)?;
296 upload_i32_async(gpu, self.head_pos, &vec![p; c.n_heads], stream)?;
297 self.decode_pos = Some(start_pos);
298 }
299 let fixed = self.decode_fixed_topk_of(ratio_positive);
300 let (mut idx, topk) = window_topk_idxs(c.window, 1, start_pos);
301 debug_assert_eq!(topk, c.window);
302 let rows_b = if ratio_positive {
303 ensure!(
304 shared.topk_idxs.len() == shared.topk,
305 "attn_v41: shared index selection is {} for one token x {}",
306 shared.topk_idxs.len(),
307 shared.topk
308 );
309 idx.extend_from_slice(&shared.topk_idxs);
310 Some(
311 shared
312 .compress_kv
313 .context("compressed layer before any kv source published")?,
314 )
315 } else {
316 None
317 };
318 ensure!(
319 idx.len() <= fixed,
320 "attn_v41: selection {} exceeds the fixed {fixed}",
321 idx.len()
322 );
323 idx.resize(fixed, -1);
324 let (buf, cache) = if ratio_positive {
325 (self.idx_dev, &mut self.decode_idx)
326 } else {
327 (self.idx_dev_win, &mut self.decode_idx_win)
328 };
329 if cache.as_deref() != Some(&idx[..]) {
330 upload_i32_async(gpu, buf, &idx, stream)?;
331 *cache = Some(idx);
332 }
333 Ok(rows_b)
334 }
335
336 pub fn decode_body(
343 &self,
344 gpu: &dyn GpuBackend,
345 w: &AttnV41LayerWeights,
346 st: &AttnV41LayerState,
347 x: DevicePtr,
348 rows_b: Option<DevicePtr>,
349 stream: u64,
350 ) -> Result<DevicePtr> {
351 let c = &self.cfg;
352 ensure!(
353 self.decode_capturable(w),
354 "attn_v41: decode_body on a kv/index source layer"
355 );
356 ensure!(
357 (w.role.ratio > 0) == rows_b.is_some(),
358 "attn_v41: compressed rows {} on a ratio-{} layer",
359 if rows_b.is_some() { "given" } else { "missing" },
360 w.role.ratio
361 );
362 let yarn = w.role.ratio > 0;
363 self.q_kv_projections(gpu, w, x, 1, yarn, stream)?;
364 KernelLaunch::new(gpu, self.k.ring_put)
365 .grid([1, 1, 1])
366 .block([256, 1, 1])
367 .arg_ptr(self.kv)
368 .arg_ptr(st.window)
369 .arg_ptr(self.pos)
370 .arg_u32(c.window as u32)
371 .arg_u32(c.head_dim as u32)
372 .launch(stream)?;
373 self.attend_and_project(
374 gpu,
375 w,
376 st.window,
377 c.window,
378 rows_b,
379 self.decode_idx_of(yarn),
380 self.decode_fixed_topk(w),
381 1,
382 yarn,
383 stream,
384 )?;
385 Ok(self.out)
386 }
387}