spark_model/layers/deepseek_v41_ref/compress/
indexer.rs1use super::super::attn::apply_rotary;
11use super::super::hc::rms_norm;
12use super::super::moe::linear_bf16;
13use super::super::to_bf16_rne;
14use super::{FP4_BLOCK, fp4_quant_e8m0_inplace};
15
16pub struct IndexerWeights<'a> {
17 pub wq_b: &'a [f32],
18 pub weights_proj: &'a [f32],
19 pub wk: Option<&'a [f32]>,
21 pub k_norm: Option<&'a [f32]>,
22}
23
24#[derive(Default)]
26pub struct SharedRuntime {
27 pub compress_kv: Vec<f32>,
29 pub index_k: Vec<f32>,
31 pub topk_idxs: Vec<i32>,
33 pub topk: usize,
34 pub candidates: Vec<bool>,
36 pub cand_width: usize,
37}
38
39pub struct IndexerCfg {
40 pub n_heads: usize,
41 pub index_hd: usize,
42 pub rope_dim: usize,
43 pub q_rank: usize,
44 pub dim: usize,
45 pub hd: usize,
46 pub index_topk: usize,
47 pub cand_topk_blocks: usize,
48 pub cand_block: usize,
49 pub eps: f32,
50}
51
52pub fn torch_cpu_topk_set(vals: &[f32], k: usize) -> Vec<usize> {
60 let n = vals.len();
61 assert!(k <= n && k > 0);
62 assert!(
63 k * 64 > n,
64 "torch takes the partial_sort branch for k*64 <= n; not emulated"
65 );
66 let mut q: Vec<(f32, usize)> = vals
67 .iter()
68 .copied()
69 .enumerate()
70 .map(|(i, v)| (v, i))
71 .collect();
72 let comp = |x: &(f32, usize), y: &(f32, usize)| (x.0.is_nan() && !y.0.is_nan()) || x.0 > y.0;
74 let nth = k - 1;
75 let (mut first, mut last) = (0usize, n);
76 let mut depth = 2 * (usize::BITS - n.leading_zeros()) as usize;
77 while last - first > 3 {
78 if depth == 0 {
79 unreachable!("introselect depth limit reached");
81 }
82 depth -= 1;
83 let mid = first + (last - first) / 2;
84 let (a, b, c) = (first + 1, mid, last - 1);
86 let r = if comp(&q[a], &q[b]) {
87 if comp(&q[b], &q[c]) {
88 b
89 } else if comp(&q[a], &q[c]) {
90 c
91 } else {
92 a
93 }
94 } else if comp(&q[a], &q[c]) {
95 a
96 } else if comp(&q[b], &q[c]) {
97 c
98 } else {
99 b
100 };
101 q.swap(first, r);
102 let pivot = q[first];
104 let (mut lo, mut hi) = (first + 1, last);
105 let cut = loop {
106 while comp(&q[lo], &pivot) {
107 lo += 1;
108 }
109 hi -= 1;
110 while comp(&pivot, &q[hi]) {
111 hi -= 1;
112 }
113 if lo >= hi {
114 break lo;
115 }
116 q.swap(lo, hi);
117 lo += 1;
118 };
119 if cut <= nth {
120 first = cut;
121 } else {
122 last = cut;
123 }
124 }
125 for i in (first + 1)..last {
127 let val = q[i];
128 if comp(&val, &q[first]) {
129 for j in (first + 1..=i).rev() {
130 q[j] = q[j - 1];
131 }
132 q[first] = val;
133 } else {
134 let mut j = i;
135 while comp(&val, &q[j - 1]) {
136 q[j] = q[j - 1];
137 j -= 1;
138 }
139 q[j] = val;
140 }
141 }
142 q[..k].iter().map(|&(_, i)| i).collect()
143}
144
145pub fn select_candidate_blocks(
148 logits: &[f32],
149 queries: usize,
150 width: usize,
151 compress_lens: &[usize],
152 topk_blocks: usize,
153 block: usize,
154) -> Vec<bool> {
155 let nb = width.div_ceil(block);
156 let mut keep = vec![false; queries * width];
157 for q in 0..queries {
158 let row = &logits[q * width..(q + 1) * width];
159 let mut scores: Vec<f32> = (0..nb)
160 .map(|b| {
161 (b * block..((b + 1) * block).min(width))
162 .map(|i| row[i])
163 .fold(f32::NEG_INFINITY, f32::max)
164 })
165 .collect();
166 let last = (compress_lens[q] as i64 - 1).div_euclid(block as i64);
168 if last >= 0 && (last as usize) < nb {
169 scores[last as usize] = f32::INFINITY;
170 }
171 for b in torch_cpu_topk_set(&scores, topk_blocks.min(nb)) {
172 if scores[b] > f32::NEG_INFINITY {
173 for i in b * block..((b + 1) * block).min(width) {
174 keep[q * width + i] = true;
175 }
176 }
177 }
178 }
179 keep
180}
181
182#[allow(clippy::too_many_arguments)]
185pub fn indexer(
186 x: &[f32],
187 qr: &[f32],
188 latent: Option<&[f32]>,
189 seqlen: usize,
190 start_pos: usize,
191 offset: usize,
192 ratio: usize,
193 c: &IndexerCfg,
194 w: &IndexerWeights,
195 fc: &[(f32, f32)],
196 k_cache: Option<&mut Vec<f32>>,
197 shared: &mut SharedRuntime,
198 is_candidate_source: bool,
199 uses_candidates: bool,
200) -> (Vec<i32>, usize) {
201 let (nh, ihd, rd) = (c.n_heads, c.index_hd, c.rope_dim);
202 let end_pos = start_pos + seqlen;
203
204 if let (Some(lat), Some(cache)) = (latent, k_cache) {
205 let groups = lat.len() / c.hd;
206 let mut k = rms_norm(
207 &linear_bf16(lat, w.wk.expect("wk"), groups, c.hd, ihd),
208 w.k_norm.expect("k_norm"),
209 groups,
210 ihd,
211 c.eps,
212 );
213 let pos: Vec<usize> = if start_pos == 0 {
214 (0..groups).map(|g| g * ratio).collect()
215 } else {
216 vec![start_pos + 1 - ratio]
217 };
218 apply_rotary(&mut k, ihd, rd, &pos, fc, false);
219 fp4_quant_e8m0_inplace(&mut k, FP4_BLOCK);
220 let at = start_pos / ratio;
221 cache[at * ihd..(at + groups) * ihd].copy_from_slice(&k);
222 shared.index_k = cache.clone();
223 }
224
225 let mut q = linear_bf16(qr, w.wq_b, seqlen, c.q_rank, nh * ihd);
226 let head_pos: Vec<usize> = (0..seqlen)
227 .flat_map(|t| std::iter::repeat_n(start_pos + t, nh))
228 .collect();
229 apply_rotary(&mut q, ihd, rd, &head_pos, fc, false);
230 fp4_quant_e8m0_inplace(&mut q, FP4_BLOCK);
231
232 let width = end_pos / ratio;
233 let index_k = &shared.index_k[..width * ihd];
234 let wscale = (ihd as f32).powf(-0.5) * (nh as f32).powf(-0.5);
235 let weights: Vec<f32> = linear_bf16(x, w.weights_proj, seqlen, c.dim, nh)
236 .into_iter()
237 .map(|v| to_bf16_rne(v * wscale))
238 .collect();
239
240 let mut score = vec![0f32; seqlen * width];
242 for t in 0..seqlen {
243 for p in 0..width {
244 let kr = &index_k[p * ihd..(p + 1) * ihd];
245 let mut acc = 0f32;
246 for h in 0..nh {
247 let qv = &q[(t * nh + h) * ihd..(t * nh + h + 1) * ihd];
248 let dot = to_bf16_rne(qv.iter().zip(kr).map(|(a, b)| a * b).sum::<f32>());
249 acc += to_bf16_rne(dot.max(0.0) * weights[t * nh + h]);
250 }
251 score[t * width + p] = to_bf16_rne(acc);
252 }
253 }
254 let compress_lens: Vec<usize> = if start_pos == 0 {
255 (0..seqlen).map(|t| (t + 1) / ratio).collect()
256 } else {
257 vec![end_pos / ratio; seqlen]
258 };
259 if start_pos == 0 {
260 for t in 0..seqlen {
261 for p in compress_lens[t]..width {
262 score[t * width + p] = f32::NEG_INFINITY;
263 }
264 }
265 }
266 if is_candidate_source {
267 shared.candidates = select_candidate_blocks(
268 &score,
269 seqlen,
270 width,
271 &compress_lens,
272 c.cand_topk_blocks,
273 c.cand_block,
274 );
275 shared.cand_width = width;
276 } else if uses_candidates {
277 for i in 0..seqlen * width {
278 if !shared.candidates[i] {
279 score[i] = f32::NEG_INFINITY;
280 }
281 }
282 }
283 let topk = c.index_topk.min(end_pos / ratio);
284 let mut out = Vec::with_capacity(seqlen * topk);
285 for t in 0..seqlen {
286 let row = &score[t * width..(t + 1) * width];
287 let mut picked = torch_cpu_topk_set(row, topk);
288 picked.sort_unstable();
289 for i in picked {
290 out.push(if i < compress_lens[t] {
291 (i + offset) as i32
292 } else {
293 -1
294 });
295 }
296 }
297 (out, topk)
298}