1use anyhow::{Context, Result};
9use spark_runtime::gpu::{DevicePtr, GpuBackend};
10
11use super::{QsaIndexer, QsaSeqState};
12use crate::layers::ops;
13
14impl QsaIndexer {
15 #[allow(clippy::too_many_arguments)]
25 pub fn prefill_select(
26 &self,
27 st: &mut QsaSeqState,
28 normed: DevicePtr,
29 q_roped: DevicePtr,
30 attn_ctx: DevicePtr,
31 k_pool: DevicePtr,
32 v_pool: DevicePtr,
33 seq_block_table: &[u32],
34 seq_start: usize,
35 num_tokens: usize,
36 nq: u32,
37 block_size: u32,
38 inv_sqrt_d: f32,
39 scratch: DevicePtr,
40 gpu: &dyn GpuBackend,
41 stream: u64,
42 ) -> Result<()> {
43 let bound = self.inert_bound(); let total = seq_start + num_tokens;
45 if total <= bound {
46 return Ok(());
47 }
48 static S2_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
51 if *S2_OFF
52 .get_or_init(|| std::env::var("ATLAS_QSA_NO_PREFILL_SELECT").as_deref() == Ok("1"))
53 {
54 return Ok(());
55 }
56 let diag = {
57 static D: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
58 *D.get_or_init(|| std::env::var("ATLAS_QSA_S2_DIAG").as_deref() == Ok("1"))
59 };
60 let q_row = nq as usize * self.hd_attn as usize;
65 let mut dense_last = Vec::new();
66 if diag {
67 dense_last = vec![0u8; q_row * 2];
68 gpu.copy_d2h_on_stream(
69 attn_ctx.offset((num_tokens - 1) * q_row * 2),
70 &mut dense_last,
71 stream,
72 )?;
73 let probe = |row: usize| -> Result<f64> {
77 let mut b = vec![0u8; q_row * 2];
78 gpu.copy_d2h_on_stream(attn_ctx.offset(row * q_row * 2), &mut b, stream)?;
79 Ok(b.chunks_exact(2)
80 .map(|c| {
81 let v =
82 f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16) as f64;
83 v * v
84 })
85 .sum::<f64>()
86 .sqrt())
87 };
88 tracing::warn!(
89 "QSA S2 DIAG norms: row100={:.3} first_sel(row {bound})={:.3} last={:.3} q_row={q_row}",
90 probe(100)?,
91 probe(bound)?,
92 probe(num_tokens - 1)?
93 );
94 let probe_at = |base: DevicePtr, row: usize| -> Result<f64> {
96 let mut b = vec![0u8; q_row * 2];
97 gpu.copy_d2h_on_stream(base.offset(row * q_row * 2), &mut b, stream)?;
98 Ok(b.chunks_exact(2)
99 .map(|c| {
100 let v =
101 f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16) as f64;
102 v * v
103 })
104 .sum::<f64>()
105 .sqrt())
106 };
107 let mut ctx_line = String::new();
108 let mut q_line = String::new();
109 for row in [128usize, 256, 512, 768, 1024, 1280, 1536, 1792, 1900, 2000] {
110 ctx_line += &format!(" {row}:{:.2}", probe_at(attn_ctx, row)?);
111 }
112 tracing::warn!("QSA S2 DIAG wide:{ctx_line}");
113 ctx_line = String::new();
114 for row in (2040..2056).step_by(2) {
115 ctx_line += &format!(" {row}:{:.2}", probe_at(attn_ctx, row)?);
116 q_line += &format!(" {row}:{:.2}", probe_at(q_roped, row)?);
117 }
118 tracing::warn!("QSA S2 DIAG ctx rows:{ctx_line}");
119 tracing::warn!("QSA S2 DIAG q rows:{q_line}");
120 }
121 let pages_needed = total.div_ceil(block_size as usize);
124 anyhow::ensure!(
125 seq_block_table.len() >= pages_needed,
126 "QSA: block table has {} pages for {} tokens",
127 seq_block_table.len(),
128 pages_needed
129 );
130 let tbytes: Vec<u8> = seq_block_table[..pages_needed]
131 .iter()
132 .flat_map(|b| (*b as i32).to_le_bytes())
133 .collect();
134 gpu.copy_h2d_async(&tbytes, self.prefill_table_dev, stream)?;
135 let block_table_dev = self.prefill_table_dev;
136 const ROWS: usize = 2048; let ratio = self.ratio as usize;
138 let topk = self.block_topk as usize;
139 let heads = self.n_heads as usize;
140 let hd = self.hd as usize;
141 let hd_attn = self.hd_attn as usize;
142 let qkw = self.qk_width();
143 let q_row = nq as usize * hd_attn;
144
145 let stride = total.div_ceil(ratio);
148 let qk_buf = scratch;
149 let qpost = scratch.offset(ROWS * qkw * 2);
150 let scores = qpost.offset(ROWS * heads * hd * 4);
151 let lists = scores.offset(ROWS * stride * 4);
152
153 let first_sel_pos = bound.max(seq_start);
155 let n_sel_total = total - first_sel_pos;
156 let mut slab = 0usize;
157 while slab < n_sel_total {
158 let rows = ROWS.min(n_sel_total - slab);
159 let first_pos = first_sel_pos + slab; let first_row = first_pos - seq_start; ops::cublas_bf16_proj_dense(
163 normed.offset(first_row * self.hidden as usize * 2),
164 self.qk_proj_w,
165 qk_buf,
166 rows as u32,
167 qkw as u32,
168 self.hidden,
169 stream,
170 )
171 .context("QSA qk projection (prefill select)")?;
172 ops::qsa_qprep_rows(
173 gpu,
174 self.k_qprep_rows_k,
175 qk_buf,
176 self.q_norm_w,
177 qpost,
178 rows as u32,
179 first_pos as u32,
180 qkw as u32,
181 self.n_heads,
182 self.hd,
183 self.rot,
184 self.theta,
185 self.eps,
186 stream,
187 )?;
188 let n_blocks_max = (first_pos + rows) / ratio; let tc = self.k_score_rows_tc_k.0 != 0
195 && self.n_heads == 4
196 && self.hd == 128
197 && std::env::var("ATLAS_QSA_SCORE_SCALAR").as_deref() != Ok("1");
198 if tc {
199 ops::qsa_score_rows_tc(
200 gpu,
201 self.k_score_rows_tc_k,
202 qpost,
203 st.block_keys,
204 scores,
205 rows as u32,
206 n_blocks_max as u32,
207 first_pos as u32,
208 stride as u32,
209 self.ratio,
210 stream,
211 )?;
212 } else {
213 ops::qsa_score_rows(
214 gpu,
215 self.k_score_rows_k,
216 qpost,
217 st.block_keys,
218 scores,
219 rows as u32,
220 n_blocks_max as u32,
221 first_pos as u32,
222 stride as u32,
223 self.ratio,
224 self.n_heads,
225 self.hd,
226 stream,
227 )?;
228 }
229
230 let mut raw = vec![0u8; rows * stride * 4];
233 gpu.copy_d2h_on_stream(scores, &mut raw, stream)?;
234 let sc: Vec<f32> = raw
235 .chunks_exact(4)
236 .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
237 .collect();
238 let mut host_lists = vec![0u8; rows * topk * 4];
239 for r in 0..rows {
240 let complete = (first_pos + r + 1) / ratio;
241 let row_sc = &sc[r * stride..r * stride + complete];
242 let mut order: Vec<u32> = (0..complete as u32).collect();
243 order.sort_by(|&a, &b| {
244 row_sc[b as usize]
245 .partial_cmp(&row_sc[a as usize])
246 .unwrap_or(std::cmp::Ordering::Equal)
247 .then(a.cmp(&b))
248 });
249 for (i, b) in order[..topk].iter().enumerate() {
250 host_lists[(r * topk + i) * 4..(r * topk + i) * 4 + 4]
251 .copy_from_slice(&(*b as i32).to_le_bytes());
252 }
253 }
254 gpu.copy_h2d_async(&host_lists, lists, stream)?;
255
256 ops::qsa_prefill_attn(
257 gpu,
258 self.k_prefill_attn_k,
259 q_roped.offset(first_row * q_row * 2),
260 k_pool,
261 v_pool,
262 block_table_dev,
263 lists,
264 attn_ctx.offset(first_row * q_row * 2),
265 rows as u32,
266 first_pos as u32,
267 topk as u32,
268 self.ratio,
269 block_size,
270 nq,
271 self.nkv_attn,
272 self.hd_attn,
273 inv_sqrt_d,
274 stream,
275 )?;
276 slab += rows;
277 }
278 if diag {
279 let mut sel_last = vec![0u8; q_row * 2];
280 gpu.copy_d2h_on_stream(
281 attn_ctx.offset((num_tokens - 1) * q_row * 2),
282 &mut sel_last,
283 stream,
284 )?;
285 let f = |b: &[u8]| -> Vec<f32> {
286 b.chunks_exact(2)
287 .map(|c| f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16))
288 .collect()
289 };
290 let (a, b) = (f(&dense_last), f(&sel_last));
291 let dot: f64 = a.iter().zip(&b).map(|(x, y)| *x as f64 * *y as f64).sum();
292 let na: f64 = a.iter().map(|x| (*x as f64).powi(2)).sum::<f64>().sqrt();
293 let nb: f64 = b.iter().map(|x| (*x as f64).powi(2)).sum::<f64>().sqrt();
294 tracing::warn!(
295 "QSA S2 DIAG: last-row ctx dense-vs-selected cos={:.6} |dense|={:.3} |sel|={:.3}",
296 dot / (na * nb).max(1e-30),
297 na,
298 nb
299 );
300 }
301 tracing::debug!(
302 "QSA prefill select: {} selective rows over {} tokens",
303 n_sel_total,
304 num_tokens
305 );
306 Ok(())
307 }
308}