Skip to main content

singe_kernel/cpu/
recurrent.rs

1//! Recurrent/state-space preprocessing helpers.
2
3#[cfg(feature = "dtype-bf16")]
4use half::bf16;
5#[cfg(feature = "dtype-f16")]
6use half::f16;
7
8/// Prepares Gated Delta Rule recurrent inputs after projection.
9///
10/// `b` and `a` are `[rows, hidden]`; `a_log` and `dt_bias` are `[hidden]`.
11/// The returned beta is `sigmoid(b)`.
12/// The returned gate is `-exp(a_log) * softplus(a + dt_bias)`, using the same stable softplus threshold as the Triton reference.
13pub fn gated_delta_rule_preprocess(
14    b: &[f32],
15    a: &[f32],
16    a_log: &[f32],
17    dt_bias: &[f32],
18    rows: usize,
19    hidden: usize,
20) -> (Vec<f32>, Vec<f32>) {
21    let mut beta = vec![0.0f32; rows * hidden];
22    let mut g = vec![0.0f32; rows * hidden];
23    for row in 0..rows {
24        for column in 0..hidden {
25            let offset = row * hidden + column;
26            beta[offset] = 1.0 / (1.0 + (-b[offset]).exp());
27            g[offset] = -a_log[column].exp() * stable_softplus(a[offset] + dt_bias[column]);
28        }
29    }
30    (beta, g)
31}
32
33fn stable_softplus(value: f32) -> f32 {
34    if value > 20.0 {
35        value
36    } else {
37        value.exp().ln_1p()
38    }
39}
40
41#[cfg(feature = "dtype-f16")]
42pub fn half_vec(values: &[f32]) -> Vec<f16> {
43    values.iter().copied().map(f16::from_f32).collect()
44}
45
46#[cfg(feature = "dtype-f16")]
47pub fn half_to_f32(values: &[f16]) -> Vec<f32> {
48    values.iter().map(|value| value.to_f32()).collect()
49}
50
51#[cfg(feature = "dtype-f16")]
52pub fn round_half_vec(values: &[f32]) -> Vec<f32> {
53    values
54        .iter()
55        .copied()
56        .map(f16::from_f32)
57        .map(|value| value.to_f32())
58        .collect()
59}
60
61#[cfg(feature = "dtype-bf16")]
62pub fn bfloat_vec(values: &[f32]) -> Vec<bf16> {
63    values.iter().copied().map(bf16::from_f32).collect()
64}
65
66#[cfg(feature = "dtype-bf16")]
67pub fn bfloat_to_f32(values: &[bf16]) -> Vec<f32> {
68    values.iter().map(|value| value.to_f32()).collect()
69}
70
71#[cfg(feature = "dtype-bf16")]
72pub fn round_bfloat_vec(values: &[f32]) -> Vec<f32> {
73    values
74        .iter()
75        .copied()
76        .map(bf16::from_f32)
77        .map(|value| value.to_f32())
78        .collect()
79}
80
81pub fn recurrent_gated_delta_rule(
82    query: &[f32],
83    key: &[f32],
84    value: &[f32],
85    gate: &[f32],
86    beta: &[f32],
87    initial_state: Option<&[f32]>,
88    batch: usize,
89    time: usize,
90    query_heads: usize,
91    value_heads: usize,
92    qk_dim: usize,
93    value_dim: usize,
94    use_qk_l2norm: bool,
95) -> (Vec<f32>, Vec<f32>) {
96    let mut out = vec![0.0f32; batch * time * value_heads * value_dim];
97    let mut final_state = vec![0.0f32; batch * value_heads * qk_dim * value_dim];
98    let heads_per_group = value_heads / query_heads;
99    for b in 0..batch {
100        for hv in 0..value_heads {
101            let h = hv / heads_per_group;
102            for v in 0..value_dim {
103                let mut state = vec![0.0f32; qk_dim];
104                if let Some(initial_state) = initial_state {
105                    for k in 0..qk_dim {
106                        state[k] =
107                            initial_state[((b * value_heads + hv) * qk_dim + k) * value_dim + v];
108                    }
109                }
110                for t in 0..time {
111                    let mut query_t = vec![0.0f32; qk_dim];
112                    let mut key_t = vec![0.0f32; qk_dim];
113                    for k in 0..qk_dim {
114                        let offset = ((b * time + t) * query_heads + h) * qk_dim + k;
115                        query_t[k] = query[offset];
116                        key_t[k] = key[offset];
117                    }
118                    if use_qk_l2norm {
119                        normalize(&mut query_t);
120                        normalize(&mut key_t);
121                    }
122                    let scale = 1.0 / (qk_dim as f32).sqrt();
123                    for value in &mut query_t {
124                        *value *= scale;
125                    }
126                    let gate_offset = (b * time + t) * value_heads + hv;
127                    let gamma = gate[gate_offset].exp();
128                    let beta_t = beta[gate_offset];
129                    for value in &mut state {
130                        *value *= gamma;
131                    }
132                    let kv_memory = dot(&state, &key_t);
133                    let value_t = value[((b * time + t) * value_heads + hv) * value_dim + v];
134                    let delta = (value_t - kv_memory) * beta_t;
135                    for k in 0..qk_dim {
136                        state[k] += key_t[k] * delta;
137                    }
138                    out[((b * time + t) * value_heads + hv) * value_dim + v] =
139                        dot(&state, &query_t);
140                }
141                for k in 0..qk_dim {
142                    final_state[((b * value_heads + hv) * qk_dim + k) * value_dim + v] = state[k];
143                }
144            }
145        }
146    }
147    (out, final_state)
148}
149
150fn normalize(values: &mut [f32]) {
151    let norm = values
152        .iter()
153        .map(|value| value * value)
154        .sum::<f32>()
155        .sqrt()
156        .max(1e-6);
157    for value in values {
158        *value /= norm;
159    }
160}
161
162fn dot(lhs: &[f32], rhs: &[f32]) -> f32 {
163    lhs.iter().zip(rhs).map(|(lhs, rhs)| lhs * rhs).sum()
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn gdr_preprocess_uses_stable_softplus_for_large_values() {
172        let b = vec![0.0; 4];
173        let a = vec![-50.0, 0.0, 25.0, 100.0];
174        let a_log = vec![0.0, 0.25, -0.5, 0.5];
175        let dt_bias = vec![0.0; 4];
176
177        let (_, g) = gated_delta_rule_preprocess(&b, &a, &a_log, &dt_bias, 1, 4);
178
179        let expected = a
180            .iter()
181            .zip(&a_log)
182            .map(|(a, a_log)| -a_log.exp() * stable_softplus(*a))
183            .collect::<Vec<_>>();
184        singe_core::assert_close!(&g, &expected, 1e-5);
185        assert!(g.iter().all(|value| value.is_finite()));
186    }
187}