1use crate::math;
15use crate::norm::zc_rms_norm_vec;
16use crate::ops::{dot, softmax_inplace};
17use crate::quant::QuantizedWeight;
18use crate::rope::RopeCache;
19use alloc::vec;
20use alloc::vec::Vec;
21
22pub struct KvCache {
23 pub k: Vec<f32>,
25 pub v: Vec<f32>,
27 pub len: usize,
28 pub max_len: usize,
29 pub num_kv_heads: usize,
30 pub head_dim: usize,
31}
32
33impl KvCache {
34 pub fn new(max_len: usize, num_kv_heads: usize, head_dim: usize) -> Self {
35 Self {
36 k: vec![0.0; max_len * num_kv_heads * head_dim],
37 v: vec![0.0; max_len * num_kv_heads * head_dim],
38 len: 0,
39 max_len,
40 num_kv_heads,
41 head_dim,
42 }
43 }
44
45 pub fn reset(&mut self) {
46 self.len = 0;
47 }
48
49 fn kv_stride(&self) -> usize {
50 self.num_kv_heads * self.head_dim
51 }
52
53 pub fn push_kv(&mut self, k_vec: &[f32], v_vec: &[f32]) {
54 if self.len >= self.max_len {
55 #[cfg(feature = "std")]
56 eprintln!(
57 "[needle-core] KV cache full at {} steps — output will degrade",
58 self.max_len
59 );
60 return;
61 }
62 let stride = self.kv_stride();
63 let base = self.len * stride;
64 self.k[base..base + stride].copy_from_slice(k_vec);
65 self.v[base..base + stride].copy_from_slice(v_vec);
66 self.len += 1;
67 }
68}
69
70pub struct AttnWeights {
72 pub wq: QuantizedWeight, pub wk: QuantizedWeight, pub wv: QuantizedWeight, pub wo: QuantizedWeight, pub q_norm: Vec<f32>, pub k_norm: Vec<f32>, }
79
80pub struct AttnConfig {
81 pub num_heads: usize,
82 pub num_kv_heads: usize,
83 pub head_dim: usize,
84 pub d_model: usize,
85}
86
87impl AttnConfig {
88 pub fn kv_repeat(&self) -> usize {
89 self.num_heads / self.num_kv_heads
90 }
91}
92
93#[allow(clippy::needless_range_loop)]
100pub fn self_attn_incremental(
101 x: &[f32],
102 out: &mut Vec<f32>,
103 weights: &AttnWeights,
104 cfg: &AttnConfig,
105 rope: &RopeCache,
106 cache: &mut KvCache,
107 attn_bias: Option<&[f32]>, ) {
109 let d = cfg.d_model;
110 let h = cfg.num_heads;
111 let kv_h = cfg.num_kv_heads;
112 let hd = cfg.head_dim;
113 let kv_dim = kv_h * hd;
114 let q_dim = h * hd;
115
116 let mut q = vec![0.0f32; q_dim];
118 let mut k = vec![0.0f32; kv_dim];
119 let mut v = vec![0.0f32; kv_dim];
120
121 weights.wq.matvec(x, &mut q);
122 weights.wk.matvec(x, &mut k);
123 weights.wv.matvec(x, &mut v);
124
125 for hi in 0..h {
127 zc_rms_norm_vec(&mut q[hi * hd..(hi + 1) * hd], &weights.q_norm);
128 }
129 for ki in 0..kv_h {
130 zc_rms_norm_vec(&mut k[ki * hd..(ki + 1) * hd], &weights.k_norm);
131 }
132
133 rope.apply(&mut q, 1, h, hd, cache.len);
135 rope.apply(&mut k, 1, kv_h, hd, cache.len);
136
137 cache.push_kv(&k, &v);
139 let ctx_len = cache.len; let scale = math::powf(hd as f32, -0.5);
143 let mut attn_scores = vec![0.0f32; ctx_len];
144 let kv_stride = kv_h * hd;
145
146 out.clear();
147 out.resize(d, 0.0);
148 let mut head_out = vec![0.0f32; d]; for hi in 0..h {
151 let kv_hi = hi / cfg.kv_repeat(); let q_head = &q[hi * hd..(hi + 1) * hd];
153
154 for t in 0..ctx_len {
156 let k_head = &cache.k[t * kv_stride + kv_hi * hd..t * kv_stride + (kv_hi + 1) * hd];
157 let mut score = dot(q_head, k_head) * scale;
158 if let Some(bias) = attn_bias {
159 score += bias[t];
160 }
161 attn_scores[t] = score;
162 }
163
164 softmax_inplace(&mut attn_scores[..ctx_len]);
166
167 let out_head = &mut head_out[hi * hd..(hi + 1) * hd];
169 for v_el in out_head.iter_mut() {
170 *v_el = 0.0;
171 }
172 for t in 0..ctx_len {
173 let v_head = &cache.v[t * kv_stride + kv_hi * hd..t * kv_stride + (kv_hi + 1) * hd];
174 let a = attn_scores[t];
175 for (o, &vi) in out_head.iter_mut().zip(v_head.iter()) {
176 *o += a * vi;
177 }
178 }
179 }
180
181 weights.wo.matvec(&head_out, out);
183}
184
185pub fn self_attn_full(
190 x: &[f32],
191 out: &mut [f32],
192 weights: &AttnWeights,
193 cfg: &AttnConfig,
194 rope: &RopeCache,
195 seq_len: usize,
196 mask: Option<&[f32]>,
197) {
198 let d = cfg.d_model;
199 let h = cfg.num_heads;
200 let kv_h = cfg.num_kv_heads;
201 let hd = cfg.head_dim;
202 let q_dim = h * hd;
203 let kv_dim = kv_h * hd;
204
205 debug_assert_eq!(x.len(), seq_len * d);
206 debug_assert_eq!(out.len(), seq_len * d);
207
208 let mut q = vec![0.0f32; seq_len * q_dim];
209 let mut k = vec![0.0f32; seq_len * kv_dim];
210 let mut v = vec![0.0f32; seq_len * kv_dim];
211
212 weights.wq.matmul(x, seq_len, &mut q);
214 weights.wk.matmul(x, seq_len, &mut k);
215 weights.wv.matmul(x, seq_len, &mut v);
216
217 for t in 0..seq_len {
219 for hi in 0..h {
220 zc_rms_norm_vec(
221 &mut q[(t * h + hi) * hd..(t * h + hi + 1) * hd],
222 &weights.q_norm,
223 );
224 }
225 for ki in 0..kv_h {
226 zc_rms_norm_vec(
227 &mut k[(t * kv_h + ki) * hd..(t * kv_h + ki + 1) * hd],
228 &weights.k_norm,
229 );
230 }
231 }
232
233 rope.apply(&mut q, seq_len, h, hd, 0);
235 rope.apply(&mut k, seq_len, kv_h, hd, 0);
236
237 let scale = math::powf(hd as f32, -0.5);
238 let mut head_out = vec![0.0f32; seq_len * q_dim];
239 let mut scores = vec![0.0f32; seq_len];
240
241 for t in 0..seq_len {
242 for hi in 0..h {
243 let kv_hi = hi / cfg.kv_repeat();
244 let q_head = &q[(t * h + hi) * hd..(t * h + hi + 1) * hd];
245
246 for s in 0..seq_len {
247 let k_head = &k[(s * kv_h + kv_hi) * hd..(s * kv_h + kv_hi + 1) * hd];
248 let mut score = dot(q_head, k_head) * scale;
249 if let Some(m) = mask {
250 score += m[t * seq_len + s];
251 }
252 scores[s] = score;
253 }
254
255 softmax_inplace(&mut scores[..seq_len]);
256
257 let out_head = &mut head_out[(t * h + hi) * hd..(t * h + hi + 1) * hd];
258 for o in out_head.iter_mut() {
259 *o = 0.0;
260 }
261 for s in 0..seq_len {
262 let v_head = &v[(s * kv_h + kv_hi) * hd..(s * kv_h + kv_hi + 1) * hd];
263 let a = scores[s];
264 for (o, &vi) in out_head.iter_mut().zip(v_head.iter()) {
265 *o += a * vi;
266 }
267 }
268 }
269 }
270
271 weights.wo.matmul(&head_out, seq_len, out);
273}
274
275#[allow(clippy::needless_range_loop)]
281pub fn cross_attn_incremental(
282 q_in: &[f32],
283 out: &mut Vec<f32>,
284 weights: &AttnWeights,
285 cfg: &AttnConfig,
286 enc_kv: &KvCache, ) {
288 let d = cfg.d_model;
289 let h = cfg.num_heads;
290 let kv_h = cfg.num_kv_heads;
291 let hd = cfg.head_dim;
292 let q_dim = h * hd;
293 let ctx_len = enc_kv.len;
294
295 let mut q = vec![0.0f32; q_dim];
296 weights.wq.matvec(q_in, &mut q);
297
298 for hi in 0..h {
299 zc_rms_norm_vec(&mut q[hi * hd..(hi + 1) * hd], &weights.q_norm);
300 }
301
302 let scale = math::powf(hd as f32, -0.5);
306 let kv_stride = kv_h * hd;
307 let mut scores = vec![0.0f32; ctx_len];
308 let mut head_out = vec![0.0f32; q_dim];
309
310 for hi in 0..h {
311 let kv_hi = hi / cfg.kv_repeat();
312 let q_head = &q[hi * hd..(hi + 1) * hd];
313
314 for t in 0..ctx_len {
315 let k_head = &enc_kv.k[t * kv_stride + kv_hi * hd..t * kv_stride + (kv_hi + 1) * hd];
316 scores[t] = dot(q_head, k_head) * scale;
317 }
318
319 softmax_inplace(&mut scores[..ctx_len]);
320
321 let out_head = &mut head_out[hi * hd..(hi + 1) * hd];
322 for o in out_head.iter_mut() {
323 *o = 0.0;
324 }
325 for t in 0..ctx_len {
326 let v_head = &enc_kv.v[t * kv_stride + kv_hi * hd..t * kv_stride + (kv_hi + 1) * hd];
327 let a = scores[t];
328 for (o, &vi) in out_head.iter_mut().zip(v_head.iter()) {
329 *o += a * vi;
330 }
331 }
332 }
333
334 out.clear();
335 out.resize(d, 0.0);
336 weights.wo.matvec(&head_out, out);
337}