1#![allow(clippy::needless_range_loop)]
23
24use crate::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 const THIS: &str = "kimi_k3::kda::tests::cuda_kda_env_opt_out";
310 const MARKER: &str = "K3_CUDA_KDA_CHILD";
311 if std::env::var_os(MARKER).is_some() {
312 assert!(!cuda_kda_enabled(), "K3_CUDA_KDA=0 must keep the CPU mixer");
313 return;
314 }
315 let output = std::process::Command::new(std::env::current_exe().unwrap())
316 .args(["--exact", THIS])
317 .env(MARKER, "1")
318 .env("K3_CUDA_KDA", "0")
319 .output()
320 .unwrap();
321 assert!(
322 output.status.success(),
323 "K3_CUDA_KDA child failed:\n{}",
324 String::from_utf8_lossy(&output.stderr)
325 );
326 }
327
328 #[test]
329 fn production_geometry() {
330 let p = KdaConfig::production();
331 assert_eq!(p.head_dim, 128);
332 assert_eq!(p.conv_kernel, 4);
333 assert!(p.use_full_rank_gate);
334 assert_eq!(p.gate_lower_bound, Some(-5.0));
335 }
336
337 #[test]
338 fn kda_from_twin_json_is_unbounded() {
339 let twin = include_str!("../../../../docs/k3/fixtures/Kimi-K3-0.40B-config.json");
340 let official = include_str!("../../../../docs/k3/fixtures/moonshotai-Kimi-K3-config.json");
341 let t = crate::config::parse_config(twin).expect("twin json");
342 let k = kda_from(&t);
343 assert_eq!(
344 k.gate_lower_bound, None,
345 "twin omits the key → FLA unbounded"
346 );
347 assert_eq!(k.heads, 8);
348 assert_eq!(k.head_dim, 32);
349 assert_eq!(k.conv_kernel, 4);
350 assert!(k.use_full_rank_gate);
351 let p = crate::config::parse_config(official).expect("official json");
352 assert_eq!(kda_from(&p).gate_lower_bound, Some(-5.0));
353 assert_eq!(kda_from(&p).heads, 96);
354 assert_eq!(kda_from(&p).head_dim, 128);
355 }
356
357 #[test]
358 fn omitted_lower_bound_is_neg_exp_a_softplus() {
359 let z = [0.5f32, -0.25];
360 let dt = [0.1, 0.0];
361 let a_log = [0.0f32]; let g = bounded_gate(&z, &dt, &a_log, 1, 2, None);
363 let sp = |x: f32| x.max(0.0) + (-x.abs()).exp().ln_1p();
364 assert!((g[0] - (-sp(0.6))).abs() < 1e-6);
365 assert!((g[1] - (-sp(-0.25))).abs() < 1e-6);
366 let g5 = bounded_gate(&z, &dt, &a_log, 1, 2, Some(-5.0));
367 assert!(
368 (g5[0] - g[0]).abs() > 0.1,
369 "safe-gate -5 must diverge from unbounded FLA default"
370 );
371 }
372
373 #[test]
374 fn conv_kernel_4_state_advances() {
375 let cfg = tiny();
376 let ch = cfg.conv_dim(); let k = cfg.conv_kernel;
378 let mut state = vec![0.0f32; ch * k];
379 let w = vec![1.0f32; ch * k];
380 for t in 0..4u32 {
381 let x = vec![(t + 1) as f32; ch];
382 let _y = conv_update(&mut state, &x, &w, ch, k);
383 for c in 0..ch {
385 assert_eq!(state[c * k + (k - 1)], x[c], "t={t} last slot");
386 }
387 }
388 for c in 0..ch {
390 let row = &state[c * k..(c + 1) * k];
391 assert_eq!(row, &[1.0, 2.0, 3.0, 4.0]);
392 }
393 }
394
395 #[test]
396 fn prefix_hit_wrong_slot_diverges() {
397 let cfg = tiny();
400 let ch = cfg.conv_dim();
401 let mut correct = KdaState::new(&cfg);
402 let conv_w = vec![0.25f32; cfg.conv_elems()];
403 let gate = bounded_gate(
404 &[0.1, -0.2],
405 &[0.0, 0.0],
406 &[-1.0],
407 cfg.heads,
408 cfg.head_dim,
409 cfg.gate_lower_bound,
410 );
411 let beta = [0.5f32];
412 let mut snapshots = Vec::new();
413 for t in 0..2u32 {
414 let x = vec![(t + 1) as f32 * 0.1; ch];
415 let _ = kda_decode_token(&x, &conv_w, &gate, &beta, &cfg, &mut correct);
416 snapshots.push(correct.clone());
417 }
418 let x3 = vec![0.4f32; ch];
420 let y_seq = kda_decode_token(&x3, &conv_w, &gate, &beta, &cfg, &mut correct);
421 let mut from_prefix = snapshots[1].clone();
423 let y_hit = kda_decode_token(&x3, &conv_w, &gate, &beta, &cfg, &mut from_prefix);
424 assert_eq!(y_hit, y_seq, "correct slot must match sequential decode");
425 assert_eq!(from_prefix.conv, correct.conv);
426 let mut wrong = snapshots[0].clone();
428 let y_wrong = kda_decode_token(&x3, &conv_w, &gate, &beta, &cfg, &mut wrong);
429 let err: f32 = y_hit
430 .iter()
431 .zip(&y_wrong)
432 .map(|(a, b)| (a - b).abs())
433 .fold(0.0, f32::max);
434 assert!(
435 err > 1e-4,
436 "wrong-slot restore must diverge (max abs {err}), hit={y_hit:?} wrong={y_wrong:?}"
437 );
438 }
439
440 #[test]
441 fn beta_zero_is_sigmoid_half_not_zero() {
442 let cfg = tiny();
445 let qkv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.5];
446 let gate = [-1.0f32, -1.0];
447 let mut rec_zero = vec![0.0f32; cfg.recurrent_elems()];
448 let mut rec_raw = rec_zero.clone();
449 let o_sig = kda_recurrent_step(&qkv, &gate, &[0.0], &cfg, &mut rec_zero);
450 let o_raw_one = kda_recurrent_step(&qkv, &gate, &[20.0], &cfg, &mut rec_raw);
451 let err: f32 = o_sig.iter().map(|v| v.abs()).fold(0.0, f32::max);
452 assert!(
453 err > 1e-6,
454 "sigmoid(0)=0.5 must update the state, got {o_sig:?}"
455 );
456 assert_ne!(o_sig, o_raw_one, "saturated beta must differ from beta=0");
457 }
458}