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