1#[cfg(feature = "dtype-bf16")]
4use half::bf16;
5#[cfg(feature = "dtype-f16")]
6use half::f16;
7
8#[derive(Debug, Clone, Copy, Eq, PartialEq)]
9pub 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)]
25pub 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
47pub 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
63pub 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}