metrale_model_arch/deepseek_v41_ref/
attn.rs1use super::engram::{e4m3_to_f32, f32_to_e4m3_rne};
30use super::hc::rms_norm;
31use super::moe::linear_bf16;
32use super::to_bf16_rne;
33
34const FP8_MAX: f32 = 448.0;
35pub const FP8_BLOCK: usize = 32;
37
38pub fn pow2_ceil(v: f32) -> f32 {
41 let bits = v.to_bits();
42 let e = ((bits >> 23) & 0xFF) as i32 - 127;
43 let man = bits & 0x7F_FFFF;
44 let n = e + if man != 0 { 1 } else { 0 };
45 f32::from_bits(((n + 127) as u32) << 23)
46}
47
48pub fn act_quant_inplace(x: &mut [f32]) {
53 for blk in x.chunks_mut(FP8_BLOCK) {
54 let amax = blk.iter().fold(0f32, |a, v| a.max(v.abs())).max(1e-4);
55 let s = pow2_ceil(amax / FP8_MAX);
56 for v in blk.iter_mut() {
57 let q = f32_to_e4m3_rne((*v / s).clamp(-FP8_MAX, FP8_MAX));
58 *v = to_bf16_rne(e4m3_to_f32(q) * s);
59 }
60 }
61}
62
63pub fn freqs_cis(dim: usize, seqlen: usize, base: f32) -> Vec<(f32, f32)> {
66 let half = dim / 2;
67 let freqs: Vec<f32> = (0..half)
68 .map(|k| 1.0 / base.powf((2 * k) as f32 / dim as f32))
69 .collect();
70 let mut out = Vec::with_capacity(seqlen * half);
71 for p in 0..seqlen {
72 for &f in &freqs {
73 let a = p as f32 * f;
74 out.push((a.cos(), a.sin()));
75 }
76 }
77 out
78}
79
80pub fn apply_rotary(
84 x: &mut [f32],
85 row_len: usize,
86 rope_dim: usize,
87 pos: &[usize],
88 fc: &[(f32, f32)],
89 inverse: bool,
90) {
91 let half = rope_dim / 2;
92 for (r, &p) in pos.iter().enumerate() {
93 let row = &mut x[r * row_len + row_len - rope_dim..(r + 1) * row_len];
94 for k in 0..half {
95 let (c, s) = fc[p * half + k];
96 let s = if inverse { -s } else { s };
97 let (a, b) = (row[2 * k], row[2 * k + 1]);
98 row[2 * k] = to_bf16_rne(a * c - b * s);
99 row[2 * k + 1] = to_bf16_rne(a * s + b * c);
100 }
101 }
102}
103
104pub fn window_topk_idxs(win: usize, seqlen: usize, start_pos: usize) -> (Vec<i32>, usize) {
108 if start_pos == 0 {
109 let cols = seqlen.min(win);
110 let mut idx = Vec::with_capacity(seqlen * cols);
111 for t in 0..seqlen {
112 let base = t.saturating_sub(win - 1);
113 for j in 0..cols {
114 let i = base + j;
115 idx.push(if i > t { -1 } else { i as i32 });
116 }
117 }
118 (idx, cols)
119 } else {
120 let oldest = start_pos % win + 1;
121 let order = (oldest..win).chain(0..oldest);
122 (
123 order
124 .map(|i| if i > start_pos { -1 } else { i as i32 })
125 .collect(),
126 win,
127 )
128 }
129}
130
131pub fn sparse_attn(
136 q: &[f32],
137 kv: &[f32],
138 sink: &[f32],
139 idx: &[i32],
140 s: usize,
141 h: usize,
142 d: usize,
143 topk: usize,
144 scale: f32,
145) -> Vec<f32> {
146 let mut o = vec![0f32; s * h * d];
147 for t in 0..s {
148 let ids = &idx[t * topk..(t + 1) * topk];
149 for hh in 0..h {
150 let qv = &q[(t * h + hh) * d..(t * h + hh + 1) * d];
151 let mut scores: Vec<f32> = ids
152 .iter()
153 .map(|&i| {
154 if i < 0 {
155 f32::NEG_INFINITY
156 } else {
157 let kr = &kv[i as usize * d..(i as usize + 1) * d];
158 qv.iter().zip(kr).map(|(a, b)| a * b).sum::<f32>() * scale
159 }
160 })
161 .collect();
162 let m = scores.iter().cloned().fold(sink[hh], f32::max);
163 let mut denom = (sink[hh] - m).exp();
164 for v in scores.iter_mut() {
165 *v = (*v - m).exp();
166 denom += *v;
167 }
168 let ov = &mut o[(t * h + hh) * d..(t * h + hh + 1) * d];
169 for (j, &i) in ids.iter().enumerate() {
170 if i < 0 {
171 continue;
172 }
173 let p = scores[j] / denom;
174 let kr = &kv[i as usize * d..(i as usize + 1) * d];
175 for dd in 0..d {
176 ov[dd] += p * kr[dd];
177 }
178 }
179 for v in ov.iter_mut() {
180 *v = to_bf16_rne(*v);
181 }
182 }
183 }
184 o
185}
186
187pub struct AttnWeights<'a> {
190 pub sink: &'a [f32],
191 pub wq_a: &'a [f32],
192 pub q_norm: &'a [f32],
193 pub wq_b: &'a [f32],
194 pub wkv: &'a [f32],
195 pub kv_norm: &'a [f32],
196 pub wo_a: &'a [f32],
197 pub wo_b: &'a [f32],
198}
199
200pub struct AttnCfg {
201 pub dim: usize,
202 pub n_heads: usize,
203 pub head_dim: usize,
204 pub rope_dim: usize,
205 pub q_rank: usize,
206 pub o_rank: usize,
207 pub groups: usize,
208 pub window: usize,
209 pub eps: f32,
210}
211
212pub struct WindowCache {
214 pub slots: Vec<f32>,
215}
216
217impl WindowCache {
218 pub fn new(c: &AttnCfg) -> Self {
219 WindowCache {
220 slots: vec![0f32; c.window * c.head_dim],
221 }
222 }
223}
224
225pub struct AttnRun {
228 pub q: Vec<f32>,
229 pub kv_rows: Vec<f32>,
230 pub idx: Vec<i32>,
231 pub topk: usize,
232 pub o: Vec<f32>,
233 pub out: Vec<f32>,
234}
235
236pub fn attention(
240 x: &[f32],
241 seqlen: usize,
242 start_pos: usize,
243 w: &AttnWeights,
244 c: &AttnCfg,
245 fc: &[(f32, f32)],
246 cache: &mut WindowCache,
247) -> AttnRun {
248 let (hd, rd, nh) = (c.head_dim, c.rope_dim, c.n_heads);
249 let pos: Vec<usize> = (0..seqlen).map(|t| start_pos + t).collect();
250
251 let qr = rms_norm(
252 &linear_bf16(x, w.wq_a, seqlen, c.dim, c.q_rank),
253 w.q_norm,
254 seqlen,
255 c.q_rank,
256 c.eps,
257 );
258 let mut q = linear_bf16(&qr, w.wq_b, seqlen, c.q_rank, nh * hd);
259 let head_pos: Vec<usize> = pos
260 .iter()
261 .flat_map(|&p| std::iter::repeat_n(p, nh))
262 .collect();
263 apply_rotary(&mut q, hd, rd, &head_pos, fc, false);
264
265 let mut kv = rms_norm(
266 &linear_bf16(x, w.wkv, seqlen, c.dim, hd),
267 w.kv_norm,
268 seqlen,
269 hd,
270 c.eps,
271 );
272 apply_rotary(&mut kv, hd, rd, &pos, fc, false);
273 act_quant_inplace(&mut kv);
274
275 let win = c.window;
276 let kv_rows = if start_pos == 0 {
277 if seqlen <= win {
278 cache.slots[..seqlen * hd].copy_from_slice(&kv);
279 } else {
280 let cutoff = seqlen % win;
281 let tail = &kv[(seqlen - win) * hd..];
282 cache.slots[cutoff * hd..win * hd].copy_from_slice(&tail[..(win - cutoff) * hd]);
283 cache.slots[..cutoff * hd].copy_from_slice(&tail[(win - cutoff) * hd..]);
284 }
285 kv.clone()
286 } else {
287 let slot = start_pos % win;
288 cache.slots[slot * hd..(slot + 1) * hd].copy_from_slice(&kv);
289 cache.slots.clone()
290 };
291 let (idx, topk) = window_topk_idxs(win, seqlen, start_pos);
292
293 let scale = (hd as f32).powf(-0.5);
294 let o_sa = sparse_attn(&q, &kv_rows, w.sink, &idx, seqlen, nh, hd, topk, scale);
297 let mut o = o_sa.clone();
298 apply_rotary(&mut o, hd, rd, &head_pos, fc, true);
299
300 let gw = nh * hd / c.groups;
302 let mut og = vec![0f32; seqlen * c.groups * c.o_rank];
303 for t in 0..seqlen {
304 for g in 0..c.groups {
305 let ov = &o[t * nh * hd + g * gw..t * nh * hd + (g + 1) * gw];
306 for r in 0..c.o_rank {
307 let wr = &w.wo_a[(g * c.o_rank + r) * gw..(g * c.o_rank + r + 1) * gw];
308 og[(t * c.groups + g) * c.o_rank + r] =
309 to_bf16_rne(ov.iter().zip(wr).map(|(a, b)| a * b).sum());
310 }
311 }
312 }
313 let out = linear_bf16(&og, w.wo_b, seqlen, c.groups * c.o_rank, c.dim);
314 AttnRun {
315 q,
316 kv_rows,
317 idx,
318 topk,
319 o: o_sa,
320 out,
321 }
322}
323
324#[cfg(test)]
325#[path = "attn_tests.rs"]
326mod tests;