1#![allow(clippy::needless_range_loop)]
23
24use metrale_config::ModelConfig;
25
26#[inline]
27fn sigmoid(x: f32) -> f32 {
28 1.0 / (1.0 + (-x).exp())
29}
30
31pub const KDA_L2_EPS: f32 = 1e-6;
33
34#[derive(Clone, Copy, Debug)]
36pub struct KdaConfig {
37 pub heads: usize,
38 pub head_dim: usize,
39 pub conv_kernel: usize,
40 pub gate_lower_bound: Option<f32>,
42 pub use_full_rank_gate: bool,
43}
44
45impl KdaConfig {
46 pub fn production() -> Self {
48 Self {
49 heads: 96,
50 head_dim: 128,
51 conv_kernel: 4,
52 gate_lower_bound: Some(-5.0),
53 use_full_rank_gate: true,
54 }
55 }
56
57 pub fn twin_0_40b() -> Self {
59 Self {
60 heads: 8,
61 head_dim: 32,
62 conv_kernel: 4,
63 gate_lower_bound: None,
64 use_full_rank_gate: true,
65 }
66 }
67
68 pub fn qkv_dim(&self) -> usize {
69 self.heads * self.head_dim
70 }
71
72 pub fn conv_dim(&self) -> usize {
73 3 * self.qkv_dim()
74 }
75
76 pub fn recurrent_elems(&self) -> usize {
77 self.heads * self.head_dim * self.head_dim
78 }
79
80 pub fn conv_elems(&self) -> usize {
81 self.conv_dim() * self.conv_kernel
82 }
83}
84
85pub fn kda_from(c: &ModelConfig) -> KdaConfig {
90 KdaConfig {
91 heads: c.linear_num_key_heads,
92 head_dim: c.linear_key_head_dim,
93 conv_kernel: c.linear_conv_kernel_dim.max(1),
94 gate_lower_bound: (c.linear_gate_lower_bound != 0.0).then_some(c.linear_gate_lower_bound),
95 use_full_rank_gate: c.use_full_rank_gate,
96 }
97}
98
99pub fn cuda_kda_enabled() -> bool {
102 !matches!(std::env::var("K3_CUDA_KDA").as_deref(), Ok("0"))
103}
104
105#[derive(Clone, Debug)]
107pub struct KdaState {
108 pub conv: Vec<f32>,
110 pub recurrent: Vec<f32>,
112}
113
114impl KdaState {
115 pub fn new(cfg: &KdaConfig) -> Self {
116 Self {
117 conv: vec![0.0; cfg.conv_elems()],
118 recurrent: vec![0.0; cfg.recurrent_elems()],
119 }
120 }
121}
122
123pub fn conv_update(
126 state: &mut [f32],
127 x: &[f32],
128 w: &[f32],
129 channels: usize,
130 kernel: usize,
131) -> Vec<f32> {
132 assert_eq!(x.len(), channels);
133 assert_eq!(state.len(), channels * kernel);
134 assert_eq!(w.len(), channels * kernel);
135 let mut y = vec![0.0f32; channels];
136 for c in 0..channels {
137 let row = c * kernel;
138 for k in 0..kernel - 1 {
139 state[row + k] = state[row + k + 1];
140 }
141 state[row + kernel - 1] = x[c];
142 let mut acc = 0.0f32;
143 for k in 0..kernel {
144 acc += w[row + k] * state[row + k];
145 }
146 y[c] = acc * sigmoid(acc); }
148 y
149}
150
151fn softplus(x: f32) -> f32 {
153 let ax = x.abs();
154 x.max(0.0) + (-ax).exp().ln_1p()
155}
156
157pub fn bounded_gate(
162 z: &[f32],
163 dt_bias: &[f32],
164 a_log: &[f32],
165 heads: usize,
166 head_dim: usize,
167 lower_bound: Option<f32>,
168) -> Vec<f32> {
169 let mut out = vec![0.0f32; heads * head_dim];
170 for h in 0..heads {
171 let decay = a_log[h].exp();
172 for d in 0..head_dim {
173 let ch = h * head_dim + d;
174 let x = z[ch] + dt_bias[ch];
175 out[ch] = match lower_bound {
176 Some(lb) => lb * sigmoid(decay * x),
177 None => -decay * softplus(x),
178 };
179 }
180 }
181 out
182}
183
184fn l2norm_rows(x: &[f32], d: usize, eps: f32) -> Vec<f32> {
185 let mut out = vec![0.0f32; x.len()];
186 for (row_in, row_out) in x.chunks_exact(d).zip(out.chunks_exact_mut(d)) {
187 let inv = 1.0 / (row_in.iter().map(|v| v * v).sum::<f32>() + eps).sqrt();
188 for (o, i) in row_out.iter_mut().zip(row_in) {
189 *o = i * inv;
190 }
191 }
192 out
193}
194
195pub fn kda_recurrent_step(
198 qkv: &[f32],
199 gate: &[f32],
200 beta: &[f32],
201 cfg: &KdaConfig,
202 recurrent: &mut [f32],
203) -> Vec<f32> {
204 let (h_n, d) = (cfg.heads, cfg.head_dim);
205 let qkv_dim = h_n * d;
206 let q = l2norm_rows(&qkv[..qkv_dim], d, KDA_L2_EPS);
207 let k = l2norm_rows(&qkv[qkv_dim..2 * qkv_dim], d, KDA_L2_EPS);
208 let v = &qkv[2 * qkv_dim..3 * qkv_dim];
209 let scale = 1.0 / (d as f32).sqrt();
210 let mut out = vec![0.0f32; qkv_dim];
211 let mut delta = vec![0.0f32; d];
212 for h in 0..h_n {
213 let base = h * d;
214 let s = &mut recurrent[h * d * d..(h + 1) * d * d];
215 for kd in 0..d {
216 let decay = gate[base + kd].exp();
217 for vd in 0..d {
218 s[kd * d + vd] *= decay;
219 }
220 }
221 let b = sigmoid(beta[h]);
222 for vd in 0..d {
223 let mut kv = 0.0f32;
224 for kd in 0..d {
225 kv += s[kd * d + vd] * k[base + kd];
226 }
227 delta[vd] = (v[base + vd] - kv) * b;
228 }
229 for kd in 0..d {
230 let kk = k[base + kd];
231 for vd in 0..d {
232 s[kd * d + vd] += kk * delta[vd];
233 }
234 }
235 for vd in 0..d {
236 let mut acc = 0.0f32;
237 for kd in 0..d {
238 acc += s[kd * d + vd] * q[base + kd] * scale;
239 }
240 out[base + vd] = acc;
241 }
242 }
243 out
244}
245
246pub fn full_rank_output_gate(core: &[f32], g: &[f32], head_dim: usize, eps: f32) -> Vec<f32> {
248 assert_eq!(core.len(), g.len());
249 let mut out = vec![0.0f32; core.len()];
250 for (row_c, (row_g, row_o)) in core
251 .chunks_exact(head_dim)
252 .zip(g.chunks_exact(head_dim).zip(out.chunks_exact_mut(head_dim)))
253 {
254 let mean_sq = row_c.iter().map(|v| v * v).sum::<f32>() / head_dim as f32;
255 let inv = 1.0 / (mean_sq + eps).sqrt();
256 for i in 0..head_dim {
257 row_o[i] = sigmoid(row_g[i]) * row_c[i] * inv;
258 }
259 }
260 out
261}
262
263pub fn kda_decode_token(
265 x_qkv: &[f32],
266 conv_w: &[f32],
267 gate: &[f32],
268 beta: &[f32],
269 cfg: &KdaConfig,
270 state: &mut KdaState,
271) -> Vec<f32> {
272 let conv_out = conv_update(
273 &mut state.conv,
274 x_qkv,
275 conv_w,
276 cfg.conv_dim(),
277 cfg.conv_kernel,
278 );
279 kda_recurrent_step(&conv_out, gate, beta, cfg, &mut state.recurrent)
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 fn tiny() -> KdaConfig {
287 KdaConfig {
288 heads: 1,
289 head_dim: 2,
290 conv_kernel: 4,
291 gate_lower_bound: Some(-5.0),
292 use_full_rank_gate: true,
293 }
294 }
295
296 #[test]
297 fn cuda_kda_env_default_on() {
298 if std::env::var_os("K3_CUDA_KDA").is_some() {
299 return;
300 }
301 assert!(
302 cuda_kda_enabled(),
303 "LinearAttention default is CUDA KDA; K3_CUDA_KDA=0 is the CPU escape"
304 );
305 }
306
307 #[test]
308 fn cuda_kda_env_opt_out() {
309 let this = format!(
310 "{}::cuda_kda_env_opt_out",
311 module_path!()
312 .split_once("::")
313 .map_or(module_path!(), |(_, p)| p)
314 );
315 const MARKER: &str = "K3_CUDA_KDA_CHILD";
316 if std::env::var_os(MARKER).is_some() {
317 assert!(!cuda_kda_enabled(), "K3_CUDA_KDA=0 must keep the CPU mixer");
318 return;
319 }
320 let output = std::process::Command::new(std::env::current_exe().unwrap())
321 .args(["--exact", this.as_str()])
322 .env(MARKER, "1")
323 .env("K3_CUDA_KDA", "0")
324 .output()
325 .unwrap();
326 assert!(
327 output.status.success(),
328 "K3_CUDA_KDA child failed:\n{}",
329 String::from_utf8_lossy(&output.stderr)
330 );
331 assert!(
332 String::from_utf8_lossy(&output.stdout).contains("1 passed"),
333 "the child must run exactly this test: {}",
334 String::from_utf8_lossy(&output.stdout)
335 );
336 }
337
338 #[test]
339 fn production_geometry() {
340 let p = KdaConfig::production();
341 assert_eq!(p.head_dim, 128);
342 assert_eq!(p.conv_kernel, 4);
343 assert!(p.use_full_rank_gate);
344 assert_eq!(p.gate_lower_bound, Some(-5.0));
345 }
346
347 #[test]
348 fn kda_from_twin_json_is_unbounded() {
349 let twin = include_str!("../../../../docs/k3/fixtures/Kimi-K3-0.40B-config.json");
350 let official = include_str!("../../../../docs/k3/fixtures/moonshotai-Kimi-K3-config.json");
351 let t = metrale_config::parse_config(twin).expect("twin json");
352 let k = kda_from(&t);
353 assert_eq!(
354 k.gate_lower_bound, None,
355 "twin omits the key → FLA unbounded"
356 );
357 assert_eq!(k.heads, 8);
358 assert_eq!(k.head_dim, 32);
359 assert_eq!(k.conv_kernel, 4);
360 assert!(k.use_full_rank_gate);
361 let p = metrale_config::parse_config(official).expect("official json");
362 assert_eq!(kda_from(&p).gate_lower_bound, Some(-5.0));
363 assert_eq!(kda_from(&p).heads, 96);
364 assert_eq!(kda_from(&p).head_dim, 128);
365 }
366
367 #[test]
368 fn omitted_lower_bound_is_neg_exp_a_softplus() {
369 let z = [0.5f32, -0.25];
370 let dt = [0.1, 0.0];
371 let a_log = [0.0f32]; let g = bounded_gate(&z, &dt, &a_log, 1, 2, None);
373 let sp = |x: f32| x.max(0.0) + (-x.abs()).exp().ln_1p();
374 assert!((g[0] - (-sp(0.6))).abs() < 1e-6);
375 assert!((g[1] - (-sp(-0.25))).abs() < 1e-6);
376 let g5 = bounded_gate(&z, &dt, &a_log, 1, 2, Some(-5.0));
377 assert!(
378 (g5[0] - g[0]).abs() > 0.1,
379 "safe-gate -5 must diverge from unbounded FLA default"
380 );
381 }
382
383 #[test]
384 fn conv_kernel_4_state_advances() {
385 let cfg = tiny();
386 let ch = cfg.conv_dim(); let k = cfg.conv_kernel;
388 let mut state = vec![0.0f32; ch * k];
389 let w = vec![1.0f32; ch * k];
390 for t in 0..4u32 {
391 let x = vec![(t + 1) as f32; ch];
392 let _y = conv_update(&mut state, &x, &w, ch, k);
393 for c in 0..ch {
395 assert_eq!(state[c * k + (k - 1)], x[c], "t={t} last slot");
396 }
397 }
398 for c in 0..ch {
400 let row = &state[c * k..(c + 1) * k];
401 assert_eq!(row, &[1.0, 2.0, 3.0, 4.0]);
402 }
403 }
404
405 #[test]
406 fn prefix_hit_wrong_slot_diverges() {
407 let cfg = tiny();
410 let ch = cfg.conv_dim();
411 let mut correct = KdaState::new(&cfg);
412 let conv_w = vec![0.25f32; cfg.conv_elems()];
413 let gate = bounded_gate(
414 &[0.1, -0.2],
415 &[0.0, 0.0],
416 &[-1.0],
417 cfg.heads,
418 cfg.head_dim,
419 cfg.gate_lower_bound,
420 );
421 let beta = [0.5f32];
422 let mut snapshots = Vec::new();
423 for t in 0..2u32 {
424 let x = vec![(t + 1) as f32 * 0.1; ch];
425 let _ = kda_decode_token(&x, &conv_w, &gate, &beta, &cfg, &mut correct);
426 snapshots.push(correct.clone());
427 }
428 let x3 = vec![0.4f32; ch];
430 let y_seq = kda_decode_token(&x3, &conv_w, &gate, &beta, &cfg, &mut correct);
431 let mut from_prefix = snapshots[1].clone();
433 let y_hit = kda_decode_token(&x3, &conv_w, &gate, &beta, &cfg, &mut from_prefix);
434 assert_eq!(y_hit, y_seq, "correct slot must match sequential decode");
435 assert_eq!(from_prefix.conv, correct.conv);
436 let mut wrong = snapshots[0].clone();
438 let y_wrong = kda_decode_token(&x3, &conv_w, &gate, &beta, &cfg, &mut wrong);
439 let err: f32 = y_hit
440 .iter()
441 .zip(&y_wrong)
442 .map(|(a, b)| (a - b).abs())
443 .fold(0.0, f32::max);
444 assert!(
445 err > 1e-4,
446 "wrong-slot restore must diverge (max abs {err}), hit={y_hit:?} wrong={y_wrong:?}"
447 );
448 }
449
450 #[test]
451 fn beta_zero_is_sigmoid_half_not_zero() {
452 let cfg = tiny();
455 let qkv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.5];
456 let gate = [-1.0f32, -1.0];
457 let mut rec_zero = vec![0.0f32; cfg.recurrent_elems()];
458 let mut rec_raw = rec_zero.clone();
459 let o_sig = kda_recurrent_step(&qkv, &gate, &[0.0], &cfg, &mut rec_zero);
460 let o_raw_one = kda_recurrent_step(&qkv, &gate, &[20.0], &cfg, &mut rec_raw);
461 let err: f32 = o_sig.iter().map(|v| v.abs()).fold(0.0, f32::max);
462 assert!(
463 err > 1e-6,
464 "sigmoid(0)=0.5 must update the state, got {o_sig:?}"
465 );
466 assert_ne!(o_sig, o_raw_one, "saturated beta must differ from beta=0");
467 }
468}