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