Skip to main content

singe_kernel/cpu/
positional.rs

1//! RoPE and positional embedding variants.
2
3#[cfg(feature = "dtype-bf16")]
4use half::bf16;
5#[cfg(feature = "dtype-f16")]
6use half::f16;
7
8#[derive(Debug, Clone, Copy, Eq, PartialEq)]
9/// Three-axis half-split RoPE config for Q/K tensors.
10///
11/// The cosine and sine tables are split into temporal, height, and width sections.
12/// Each section rotates dimensions from the first half of the head with the corresponding dimensions in the second half.
13pub struct MultiaxisRopeConfig {
14    pub batch: usize,
15    pub seq_len: usize,
16    pub query_heads: usize,
17    pub key_heads: usize,
18    pub head_dim: usize,
19    pub section_t: usize,
20    pub section_h: usize,
21    pub section_w: usize,
22}
23
24#[derive(Debug, Clone, Copy, Eq, PartialEq)]
25/// Interleaved complex-frequency RoPE config for Q/K tensors.
26///
27/// Adjacent dimensions are treated as real/imaginary pairs and multiplied by a precomputed complex frequency table laid out as `[seq_len, head_dim]`.
28pub struct InterleavedComplexRopeConfig {
29    pub batch: usize,
30    pub seq_len: usize,
31    pub query_heads: usize,
32    pub key_heads: usize,
33    pub head_dim: usize,
34}
35
36#[derive(Debug, Clone, Copy, Eq, PartialEq)]
37pub struct BnsdQkRopeConfig {
38    pub batch: usize,
39    pub seq_len: usize,
40    pub query_heads: usize,
41    pub key_heads: usize,
42    pub head_dim: usize,
43    pub trig_batch: usize,
44    pub rotary_pairs: usize,
45}
46
47/// Applies three-axis half-split RoPE to query and key tensors.
48///
49/// Query and key use `[batch, seq_len, heads, head_dim]` layout.
50/// The cosine and sine tables contain temporal, height, and width sections.
51/// Each section rotates first-half head dimensions with their matching second-half dimensions.
52pub fn multiaxis_rope_qk(
53    query: &mut [f32],
54    key: &mut [f32],
55    cos: &[f32],
56    sin: &[f32],
57    config: MultiaxisRopeConfig,
58) {
59    rotate_multiaxis_rope_tensor(query, cos, sin, config, config.query_heads);
60    rotate_multiaxis_rope_tensor(key, cos, sin, config, config.key_heads);
61}
62
63/// Applies interleaved complex-frequency RoPE to query and key tensors.
64///
65/// Query and key use `[batch, seq_len, heads, head_dim]` layout.
66/// Adjacent dimensions `(0, 1)`, `(2, 3)`, and so on are treated as real/imaginary pairs
67/// and multiplied by the `[seq_len, head_dim]` complex frequency table.
68pub fn interleaved_complex_rope_qk(
69    query: &mut [f32],
70    key: &mut [f32],
71    freqs: &[f32],
72    config: InterleavedComplexRopeConfig,
73) {
74    rotate_interleaved_complex_rope_tensor(query, freqs, config, config.query_heads);
75    rotate_interleaved_complex_rope_tensor(key, freqs, config, config.key_heads);
76}
77
78fn rotate_interleaved_complex_rope_tensor(
79    values: &mut [f32],
80    freqs: &[f32],
81    config: InterleavedComplexRopeConfig,
82    heads: usize,
83) {
84    let head_dim_half = config.head_dim / 2;
85    for batch in 0..config.batch {
86        for seq in 0..config.seq_len {
87            for head in 0..heads {
88                for pair in 0..head_dim_half {
89                    let real_dim = pair * 2;
90                    let imag_dim = real_dim + 1;
91                    let base = ((batch * config.seq_len + seq) * heads + head) * config.head_dim;
92                    let real_index = base + real_dim;
93                    let imag_index = base + imag_dim;
94                    let freq_base = seq * config.head_dim;
95                    let freq_real = freqs[freq_base + real_dim];
96                    let freq_imag = freqs[freq_base + imag_dim];
97                    let real = values[real_index];
98                    let imag = values[imag_index];
99                    values[real_index] = real * freq_real - imag * freq_imag;
100                    values[imag_index] = real * freq_imag + imag * freq_real;
101                }
102            }
103        }
104    }
105}
106
107fn rotate_multiaxis_rope_tensor(
108    values: &mut [f32],
109    cos: &[f32],
110    sin: &[f32],
111    config: MultiaxisRopeConfig,
112    heads: usize,
113) {
114    let head_dim_half = config.head_dim / 2;
115    for batch in 0..config.batch {
116        for seq in 0..config.seq_len {
117            for head in 0..heads {
118                for dim in 0..head_dim_half {
119                    let section = if dim < config.section_t {
120                        0
121                    } else if dim < config.section_t + config.section_h {
122                        1
123                    } else {
124                        2
125                    };
126                    let trig_index = ((section * config.batch + batch) * config.seq_len + seq)
127                        * head_dim_half
128                        + dim;
129                    let base =
130                        ((batch * config.seq_len + seq) * heads + head) * config.head_dim + dim;
131                    let real = values[base];
132                    let imag_index = base + head_dim_half;
133                    let imag = values[imag_index];
134                    values[base] = real * cos[trig_index] - imag * sin[trig_index];
135                    values[imag_index] = imag * cos[trig_index] + real * sin[trig_index];
136                }
137            }
138        }
139    }
140}
141
142pub fn rotate_bnsd_qk_rope_reference(
143    query: &mut [f32],
144    key: &mut [f32],
145    cos: &[f32],
146    sin: &[f32],
147    config: BnsdQkRopeConfig,
148) {
149    rotate_bnsd_rope_tensor(query, cos, sin, config, config.query_heads);
150    rotate_bnsd_rope_tensor(key, cos, sin, config, config.key_heads);
151}
152
153fn rotate_bnsd_rope_tensor(
154    values: &mut [f32],
155    cos: &[f32],
156    sin: &[f32],
157    config: BnsdQkRopeConfig,
158    heads: usize,
159) {
160    for batch in 0..config.batch {
161        let trig_batch = if config.trig_batch == 1 { 0 } else { batch };
162        for head in 0..heads {
163            for seq in 0..config.seq_len {
164                for pair in 0..config.rotary_pairs {
165                    let trig_index =
166                        (trig_batch * config.seq_len + seq) * config.rotary_pairs + pair;
167                    let base = ((batch * heads + head) * config.seq_len + seq) * config.head_dim;
168                    let real_index = base + pair;
169                    let imag_index = base + config.rotary_pairs + pair;
170                    let real = values[real_index];
171                    let imag = values[imag_index];
172                    values[real_index] = real * cos[trig_index] - imag * sin[trig_index];
173                    values[imag_index] = imag * cos[trig_index] + real * sin[trig_index];
174                }
175            }
176        }
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn rope_conventions_match_reference_values() {
186        let mut interleaved = vec![1.0f32, 2.0, 3.0, 4.0];
187        let mut half_split = interleaved.clone();
188        let mut unused_key = Vec::new();
189        let cos = [0.5f32, 0.25];
190        let sin = [0.75f32, -0.5];
191
192        rotate_interleaved_complex_rope_tensor(
193            &mut interleaved,
194            &[cos[0], sin[0], cos[1], sin[1]],
195            InterleavedComplexRopeConfig {
196                batch: 1,
197                seq_len: 1,
198                query_heads: 1,
199                key_heads: 0,
200                head_dim: 4,
201            },
202            1,
203        );
204        rotate_bnsd_qk_rope_reference(
205            &mut half_split,
206            &mut unused_key,
207            &cos,
208            &sin,
209            BnsdQkRopeConfig {
210                batch: 1,
211                seq_len: 1,
212                query_heads: 1,
213                key_heads: 0,
214                head_dim: 4,
215                trig_batch: 1,
216                rotary_pairs: 2,
217            },
218        );
219
220        singe_core::assert_close!(&interleaved, &[-1.0, 1.75, 2.75, -0.5], 1e-6);
221        singe_core::assert_close!(&half_split, &[-1.75, 2.5, 2.25, 0.0], 1e-6);
222    }
223
224    #[test]
225    fn rms_norm_offset_then_rope_sequence_matches_reference_values() {
226        let input = [1.0f32, 2.0, 3.0, 4.0];
227        let weight = [0.5f32, 1.5, 2.0, 0.25];
228        let cos = [0.5f32, 0.25];
229        let sin = [0.75f32, -0.5];
230        let eps = 1e-5f32;
231        let mut query = crate::cpu::fused::rms_norm_weight_offset(&input, &weight, 1, 4, eps, 1.0);
232        let mut unused_key = Vec::new();
233        let rms = ((input.iter().map(|value| value * value).sum::<f32>() / 4.0) + eps).sqrt();
234        let normalized = [
235            input[0] / rms * (weight[0] + 1.0),
236            input[1] / rms * (weight[1] + 1.0),
237            input[2] / rms * (weight[2] + 1.0),
238            input[3] / rms * (weight[3] + 1.0),
239        ];
240        let expected = [
241            normalized[0] * cos[0] - normalized[2] * sin[0],
242            normalized[1] * cos[1] - normalized[3] * sin[1],
243            normalized[2] * cos[0] + normalized[0] * sin[0],
244            normalized[3] * cos[1] + normalized[1] * sin[1],
245        ];
246
247        rotate_bnsd_qk_rope_reference(
248            &mut query,
249            &mut unused_key,
250            &cos,
251            &sin,
252            BnsdQkRopeConfig {
253                batch: 1,
254                seq_len: 1,
255                query_heads: 1,
256                key_heads: 0,
257                head_dim: 4,
258                trig_batch: 1,
259                rotary_pairs: 2,
260            },
261        );
262
263        singe_core::assert_close!(&query, &expected, 1e-6);
264    }
265
266    #[test]
267    fn rope_interleaved_and_half_split_conventions_differ() {
268        let mut interleaved = vec![1.0f32, 2.0, 3.0, 4.0];
269        let mut half_split = interleaved.clone();
270        let mut unused_key = Vec::new();
271
272        rotate_interleaved_complex_rope_tensor(
273            &mut interleaved,
274            &[0.0, 1.0, 0.0, 1.0],
275            InterleavedComplexRopeConfig {
276                batch: 1,
277                seq_len: 1,
278                query_heads: 1,
279                key_heads: 0,
280                head_dim: 4,
281            },
282            1,
283        );
284        rotate_bnsd_qk_rope_reference(
285            &mut half_split,
286            &mut unused_key,
287            &[0.0, 0.0],
288            &[1.0, 1.0],
289            BnsdQkRopeConfig {
290                batch: 1,
291                seq_len: 1,
292                query_heads: 1,
293                key_heads: 0,
294                head_dim: 4,
295                trig_batch: 1,
296                rotary_pairs: 2,
297            },
298        );
299
300        assert_eq!(interleaved, vec![-2.0, 1.0, -4.0, 3.0]);
301        assert_eq!(half_split, vec![-3.0, -4.0, 1.0, 2.0]);
302        assert_ne!(interleaved, half_split);
303    }
304}
305
306#[cfg(feature = "dtype-f16")]
307pub fn half_vec(values: &[f32]) -> Vec<f16> {
308    values.iter().copied().map(f16::from_f32).collect()
309}
310
311#[cfg(feature = "dtype-f16")]
312pub fn half_to_f32(values: &[f16]) -> Vec<f32> {
313    values.iter().map(|value| value.to_f32()).collect()
314}
315
316#[cfg(feature = "dtype-bf16")]
317pub fn bfloat_vec(values: &[f32]) -> Vec<bf16> {
318    values.iter().copied().map(bf16::from_f32).collect()
319}
320
321#[cfg(feature = "dtype-bf16")]
322pub fn bfloat_to_f32(values: &[bf16]) -> Vec<f32> {
323    values.iter().map(|value| value.to_f32()).collect()
324}