1use super::attn::{act_quant_inplace, apply_rotary, pow2_ceil, sparse_attn, window_topk_idxs};
18use super::engram::{e4m3_to_f32, f32_to_e4m3_rne};
19use super::hc::rms_norm;
20use super::moe::linear_bf16;
21use super::to_bf16_rne;
22
23mod compressor;
24mod indexer;
25
26pub use compressor::{CompressorState, CompressorWeights, compressor};
27pub use indexer::{
28 IndexerCfg, IndexerWeights, SharedRuntime, indexer, select_candidate_blocks, torch_cpu_topk_set,
29};
30
31pub const FP4_BLOCK: usize = 32;
33pub const LATENT_BLOCK: usize = 16;
35const FP4_MAX: f32 = 6.0;
36const E2M1: [f32; 8] = [0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0];
37
38pub fn to_e2m1_rne(y: f32) -> f32 {
40 let a = y.abs();
41 let mut lo = 0usize;
42 for (i, &v) in E2M1.iter().enumerate() {
43 if v <= a {
44 lo = i;
45 }
46 }
47 let hi = (lo + 1).min(7);
48 let (dlo, dhi) = (a - E2M1[lo], E2M1[hi] - a);
49 let pick_hi = dhi < dlo || (dhi == dlo && hi.is_multiple_of(2) && lo % 2 == 1);
50 let v = if pick_hi { E2M1[hi] } else { E2M1[lo] };
51 v.copysign(y)
52}
53
54pub fn fp4_quant_e8m0_inplace(x: &mut [f32], block: usize) {
57 for blk in x.chunks_mut(block) {
58 let amax = blk
59 .iter()
60 .fold(0f32, |a, v| a.max(v.abs()))
61 .max(6.0 * 2f32.powi(-126));
62 let s = pow2_ceil(amax / FP4_MAX);
63 for v in blk.iter_mut() {
64 *v = to_bf16_rne(to_e2m1_rne((*v / s).clamp(-FP4_MAX, FP4_MAX)) * s);
65 }
66 }
67}
68
69pub fn fp4_quant_e4m3_inplace(x: &mut [f32], block: usize) {
72 for blk in x.chunks_mut(block) {
73 let amax = blk
74 .iter()
75 .fold(0f32, |a, v| a.max(v.abs()))
76 .max(6.0 * 2f32.powi(-9));
77 let s = e4m3_to_f32(f32_to_e4m3_rne(amax / FP4_MAX));
78 for v in blk.iter_mut() {
79 *v = to_bf16_rne(to_e2m1_rne((*v / s).clamp(-FP4_MAX, FP4_MAX)) * s);
80 }
81 }
82}
83
84pub fn yarn_freqs_cis(
87 dim: usize,
88 seqlen: usize,
89 original_seq_len: usize,
90 base: f32,
91 factor: f32,
92 beta_fast: f32,
93 beta_slow: f32,
94) -> Vec<(f32, f32)> {
95 let half = dim / 2;
96 let mut freqs: Vec<f32> = (0..half)
97 .map(|k| 1.0 / base.powf((2 * k) as f32 / dim as f32))
98 .collect();
99 let corrected = |rot: f64| -> f64 {
100 dim as f64 * (original_seq_len as f64 / (rot * 2.0 * std::f64::consts::PI)).ln()
101 / (2.0 * (base as f64).ln())
102 };
103 let low = corrected(beta_fast as f64).floor().max(0.0) as f32;
104 let high = (corrected(beta_slow as f64).ceil()).min((dim - 1) as f64) as f32;
105 for (i, f) in freqs.iter_mut().enumerate() {
106 let ramp = ((i as f32 - low) / (high - low).max(1e-3)).clamp(0.0, 1.0);
107 let smooth = 1.0 - ramp;
108 *f = *f / factor * (1.0 - smooth) + *f * smooth;
109 }
110 let mut out = Vec::with_capacity(seqlen * half);
111 for p in 0..seqlen {
112 for &f in &freqs {
113 let a = p as f32 * f;
114 out.push((a.cos(), a.sin()));
115 }
116 }
117 out
118}
119
120pub fn linear_f32(x: &[f32], w: &[f32], rows: usize, in_dim: usize, out_dim: usize) -> Vec<f32> {
122 let mut y = vec![0f32; rows * out_dim];
123 for i in 0..rows {
124 for o in 0..out_dim {
125 y[i * out_dim + o] = x[i * in_dim..(i + 1) * in_dim]
126 .iter()
127 .zip(&w[o * in_dim..(o + 1) * in_dim])
128 .map(|(a, b)| a * b)
129 .sum();
130 }
131 }
132 y
133}
134
135pub struct CompAttnCfg {
136 pub dim: usize,
137 pub n_heads: usize,
138 pub head_dim: usize,
139 pub rope_dim: usize,
140 pub q_rank: usize,
141 pub o_rank: usize,
142 pub groups: usize,
143 pub window: usize,
144 pub eps: f32,
145 pub ratio: usize,
146 pub is_kv_source: bool,
147 pub is_index_source: bool,
148 pub is_candidate_source: bool,
149 pub uses_candidates: bool,
150}
151
152pub struct LayerAttnState {
153 pub window: Vec<f32>,
154 pub compressor: Option<CompressorState>,
155 pub compress_kv_cache: Option<Vec<f32>>,
156 pub k_cache: Option<Vec<f32>>,
157}
158
159impl LayerAttnState {
160 pub fn new(c: &CompAttnCfg, max_seq: usize, index_hd: usize) -> Self {
161 let groups = if c.ratio > 0 { max_seq / c.ratio } else { 0 };
162 LayerAttnState {
163 window: vec![0f32; c.window * c.head_dim],
164 compressor: if c.is_kv_source {
165 Some(CompressorState::new(c.ratio.max(1), c.head_dim))
166 } else {
167 None
168 },
169 compress_kv_cache: if c.is_kv_source {
170 Some(vec![0f32; groups * c.head_dim])
171 } else {
172 None
173 },
174 k_cache: if c.is_kv_source {
175 Some(vec![0f32; groups * index_hd])
176 } else {
177 None
178 },
179 }
180 }
181}
182
183pub struct CompAttnRun {
184 pub q: Vec<f32>,
185 pub kv_rows: Vec<f32>,
186 pub idx: Vec<i32>,
187 pub topk: usize,
188 pub o: Vec<f32>,
189 pub out: Vec<f32>,
190}
191
192#[allow(clippy::too_many_arguments)]
194pub fn attention_any(
195 x: &[f32],
196 seqlen: usize,
197 start_pos: usize,
198 w: &super::attn::AttnWeights,
199 comp: Option<&CompressorWeights>,
200 idxw: Option<&IndexerWeights>,
201 icfg: &IndexerCfg,
202 c: &CompAttnCfg,
203 fc: &[(f32, f32)],
204 st: &mut LayerAttnState,
205 shared: &mut SharedRuntime,
206) -> CompAttnRun {
207 let (hd, rd, nh) = (c.head_dim, c.rope_dim, c.n_heads);
208 let pos: Vec<usize> = (0..seqlen).map(|t| start_pos + t).collect();
209 let head_pos: Vec<usize> = pos
210 .iter()
211 .flat_map(|&p| std::iter::repeat_n(p, nh))
212 .collect();
213
214 let qr = rms_norm(
215 &linear_bf16(x, w.wq_a, seqlen, c.dim, c.q_rank),
216 w.q_norm,
217 seqlen,
218 c.q_rank,
219 c.eps,
220 );
221 let mut q = linear_bf16(&qr, w.wq_b, seqlen, c.q_rank, nh * hd);
222 apply_rotary(&mut q, hd, rd, &head_pos, fc, false);
223
224 let mut kv = rms_norm(
225 &linear_bf16(x, w.wkv, seqlen, c.dim, hd),
226 w.kv_norm,
227 seqlen,
228 hd,
229 c.eps,
230 );
231 apply_rotary(&mut kv, hd, rd, &pos, fc, false);
232 act_quant_inplace(&mut kv);
233 let win = c.window;
234 let mut kv_rows = if start_pos == 0 {
235 if seqlen <= win {
236 st.window[..seqlen * hd].copy_from_slice(&kv);
237 } else {
238 let cutoff = seqlen % win;
239 let tail = &kv[(seqlen - win) * hd..];
240 st.window[cutoff * hd..win * hd].copy_from_slice(&tail[..(win - cutoff) * hd]);
241 st.window[..cutoff * hd].copy_from_slice(&tail[(win - cutoff) * hd..]);
242 }
243 kv.clone()
244 } else {
245 let slot = start_pos % win;
246 st.window[slot * hd..(slot + 1) * hd].copy_from_slice(&kv);
247 st.window.clone()
248 };
249 let (mut idx, mut topk) = window_topk_idxs(win, seqlen, start_pos);
250
251 if c.ratio > 0 {
252 let ratio = c.ratio;
253 let offset = kv_rows.len() / hd;
254 let compress_len = (start_pos + seqlen) / ratio;
255 let latent = if c.is_kv_source {
256 let r = compressor(
257 x,
258 seqlen,
259 start_pos,
260 ratio,
261 c.dim,
262 hd,
263 comp.expect("compressor weights"),
264 st.compressor.as_mut().expect("compressor state"),
265 c.eps,
266 );
267 shared.compress_kv = st.compress_kv_cache.as_ref().expect("cache").clone();
268 r
269 } else {
270 None
271 };
272 let (cidx, ctopk) = if !c.is_index_source {
274 (shared.topk_idxs.clone(), shared.topk)
275 } else if compress_len == 0 {
276 (Vec::new(), 0)
277 } else {
278 let r = indexer(
279 x,
280 &qr,
281 latent.as_deref(),
282 seqlen,
283 start_pos,
284 offset,
285 ratio,
286 icfg,
287 idxw.expect("indexer weights"),
288 fc,
289 st.k_cache.as_mut(),
290 shared,
291 c.is_candidate_source,
292 c.uses_candidates,
293 );
294 shared.topk_idxs = r.0.clone();
295 shared.topk = r.1;
296 r
297 };
298 if let Some(mut lat) = latent {
299 let groups = lat.len() / hd;
300 let lpos: Vec<usize> = if start_pos == 0 {
301 (0..groups).map(|g| g * ratio).collect()
302 } else {
303 vec![start_pos + 1 - ratio]
304 };
305 apply_rotary(&mut lat, hd, rd, &lpos, fc, false);
306 fp4_quant_e4m3_inplace(&mut lat, LATENT_BLOCK);
307 let cache = st.compress_kv_cache.as_mut().expect("cache");
308 let at = start_pos / ratio;
309 cache[at * hd..(at + groups) * hd].copy_from_slice(&lat);
310 shared.compress_kv = cache.clone();
311 }
312 kv_rows.extend_from_slice(&shared.compress_kv[..compress_len * hd]);
314 let mut merged = Vec::with_capacity(seqlen * (topk + ctopk));
315 for t in 0..seqlen {
316 merged.extend_from_slice(&idx[t * topk..(t + 1) * topk]);
317 merged.extend_from_slice(&cidx[t * ctopk..(t + 1) * ctopk]);
318 }
319 idx = merged;
320 topk += ctopk;
321 }
322
323 let scale = (hd as f32).powf(-0.5);
324 let o_sa = sparse_attn(&q, &kv_rows, w.sink, &idx, seqlen, nh, hd, topk, scale);
325 let mut o = o_sa.clone();
326 apply_rotary(&mut o, hd, rd, &head_pos, fc, true);
327 let gw = nh * hd / c.groups;
328 let mut og = vec![0f32; seqlen * c.groups * c.o_rank];
329 for t in 0..seqlen {
330 for g in 0..c.groups {
331 let ov = &o[t * nh * hd + g * gw..t * nh * hd + (g + 1) * gw];
332 for r in 0..c.o_rank {
333 let wr = &w.wo_a[(g * c.o_rank + r) * gw..(g * c.o_rank + r + 1) * gw];
334 og[(t * c.groups + g) * c.o_rank + r] =
335 to_bf16_rne(ov.iter().zip(wr).map(|(a, b)| a * b).sum());
336 }
337 }
338 }
339 let out = linear_bf16(&og, w.wo_b, seqlen, c.groups * c.o_rank, c.dim);
340 CompAttnRun {
341 q,
342 kv_rows,
343 idx,
344 topk,
345 o: o_sa,
346 out,
347 }
348}
349
350#[cfg(test)]
351#[path = "compress_tests.rs"]
352mod tests;