1use burn::tensor::{Tensor, TensorData, backend::Backend, Device};
7use combs_formats::RopeScaling;
8
9pub struct RotaryEmbedding<B: Backend> {
16 cos: Tensor<B, 2>,
17 sin: Tensor<B, 2>,
18}
19
20pub fn build_tables(
23 head_dim: usize,
24 theta: f64,
25 max_position: usize,
26) -> (Vec<f32>, Vec<f32>) {
27 build_tables_scaled(head_dim, theta, max_position, &RopeScaling::None)
28}
29
30pub fn scaled_inv_freq(head_dim: usize, theta: f64, scaling: &RopeScaling) -> (Vec<f64>, f64) {
34 let half = head_dim / 2;
35 let base: Vec<f64> = (0..half)
36 .map(|i| theta.powf(-2.0 * i as f64 / head_dim as f64))
37 .collect();
38 match scaling {
39 RopeScaling::None => (base, 1.0),
40 RopeScaling::Linear { factor } => (base.iter().map(|f| f / factor).collect(), 1.0),
41 RopeScaling::Llama3 {
42 factor,
43 low_freq_factor,
44 high_freq_factor,
45 original_max_position_embeddings,
46 } => {
47 let orig = *original_max_position_embeddings as f64;
48 let low_wavelen = orig / low_freq_factor;
49 let high_wavelen = orig / high_freq_factor;
50 let scaled = base
51 .iter()
52 .map(|&f| {
53 let wavelen = 2.0 * std::f64::consts::PI / f;
54 if wavelen < high_wavelen {
55 f
56 } else if wavelen > low_wavelen {
57 f / factor
58 } else {
59 let smooth = (orig / wavelen - low_freq_factor)
60 / (high_freq_factor - low_freq_factor);
61 (1.0 - smooth) * f / factor + smooth * f
62 }
63 })
64 .collect();
65 (scaled, 1.0)
66 }
67 RopeScaling::Yarn {
68 factor,
69 original_max_position_embeddings,
70 beta_fast,
71 beta_slow,
72 attention_factor,
73 } => {
74 let dim = head_dim as f64;
75 let orig = *original_max_position_embeddings as f64;
76 let corr_dim = |rotations: f64| {
77 dim * (orig / (rotations * 2.0 * std::f64::consts::PI)).ln()
78 / (2.0 * theta.ln())
79 };
80 let low = corr_dim(*beta_fast).floor().max(0.0);
81 let mut high = corr_dim(*beta_slow).ceil().min(dim - 1.0);
82 if (high - low).abs() < f64::EPSILON {
83 high += 0.001; }
85 let scaled = (0..half)
86 .map(|i| {
87 let pos_freq = theta.powf(2.0 * i as f64 / dim);
88 let extrapolation = 1.0 / pos_freq;
89 let interpolation = 1.0 / (factor * pos_freq);
90 let ramp = ((i as f64 - low) / (high - low)).clamp(0.0, 1.0);
91 let extrapolation_factor = 1.0 - ramp;
92 interpolation * (1.0 - extrapolation_factor)
93 + extrapolation * extrapolation_factor
94 })
95 .collect();
96 let mscale = attention_factor.unwrap_or(0.1 * factor.ln() + 1.0);
97 (scaled, mscale)
98 }
99 RopeScaling::LongRope {
100 short_factor,
101 long_factor: _,
102 original_max_position_embeddings,
103 factor,
104 attention_factor,
105 } => {
106 let scaled = base
111 .iter()
112 .enumerate()
113 .map(|(i, f)| f / short_factor.get(i).copied().unwrap_or(1.0))
114 .collect();
115 let mscale = attention_factor.unwrap_or_else(|| {
116 if *factor <= 1.0 {
117 1.0
118 } else {
119 (1.0 + factor.ln() / (*original_max_position_embeddings as f64).ln())
120 .sqrt()
121 }
122 });
123 (scaled, mscale)
124 }
125 }
126}
127
128pub fn build_tables_scaled(
131 head_dim: usize,
132 theta: f64,
133 max_position: usize,
134 scaling: &RopeScaling,
135) -> (Vec<f32>, Vec<f32>) {
136 let (inv_freq, mscale) = scaled_inv_freq(head_dim, theta, scaling);
137 let mut cos = Vec::with_capacity(max_position * head_dim);
138 let mut sin = Vec::with_capacity(max_position * head_dim);
139 for pos in 0..max_position {
140 for _ in 0..2 {
143 for f in &inv_freq {
144 let angle = pos as f64 * f;
145 cos.push((angle.cos() * mscale) as f32);
146 sin.push((angle.sin() * mscale) as f32);
147 }
148 }
149 }
150 (cos, sin)
151}
152
153impl<B: Backend> RotaryEmbedding<B> {
154 pub fn new(head_dim: usize, theta: f64, max_position: usize, device: &Device<B>) -> Self {
156 Self::new_scaled(head_dim, theta, max_position, &RopeScaling::None, device)
157 }
158
159 pub fn new_scaled(
161 head_dim: usize,
162 theta: f64,
163 max_position: usize,
164 scaling: &RopeScaling,
165 device: &Device<B>,
166 ) -> Self {
167 let (cos, sin) = build_tables_scaled(head_dim, theta, max_position, scaling);
168 RotaryEmbedding {
169 cos: Tensor::from_data(
170 TensorData::new(cos, [max_position, head_dim]),
171 device,
172 ),
173 sin: Tensor::from_data(
174 TensorData::new(sin, [max_position, head_dim]),
175 device,
176 ),
177 }
178 }
179
180 pub fn apply(&self, x: Tensor<B, 4>, pos: usize) -> Tensor<B, 4> {
183 let [batch, heads, seq, dim] = x.dims();
184 let half = dim / 2;
185 let cos = self
186 .cos
187 .clone()
188 .narrow(0, pos, seq)
189 .reshape([1, 1, seq, dim]);
190 let sin = self
191 .sin
192 .clone()
193 .narrow(0, pos, seq)
194 .reshape([1, 1, seq, dim]);
195
196 let x1 = x.clone().narrow(3, 0, half);
198 let x2 = x.clone().narrow(3, half, half);
199 let rotated = Tensor::cat(vec![x2.neg(), x1], 3);
200
201 let out = x * cos + rotated * sin;
202 debug_assert_eq!(out.dims(), [batch, heads, seq, dim]);
203 out
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn tables_match_known_math() {
213 let head_dim = 8;
214 let theta = 10000.0f64;
215 let max_pos = 4;
216 let (cos, sin) = build_tables(head_dim, theta, max_pos);
217 let inv = [1.0f64, 0.1, 0.01, 0.001];
220 for pos in 0..max_pos {
221 for i in 0..4 {
222 let angle = pos as f64 * inv[i];
223 for idx in [pos * head_dim + i, pos * head_dim + i + 4] {
225 assert!(
226 (cos[idx] as f64 - angle.cos()).abs() < 1e-5,
227 "cos mismatch at pos {pos} i {i}"
228 );
229 assert!(
230 (sin[idx] as f64 - angle.sin()).abs() < 1e-5,
231 "sin mismatch at pos {pos} i {i}"
232 );
233 }
234 }
235 }
236 for i in 0..head_dim {
238 assert!((cos[i] - 1.0).abs() < 1e-6);
239 assert!(sin[i].abs() < 1e-6);
240 }
241 }
242
243 #[test]
246 fn linear_scaling_divides_frequencies() {
247 let (inv, mscale) = scaled_inv_freq(8, 10_000.0, &RopeScaling::Linear { factor: 2.0 });
248 let expected = [0.5, 0.05, 0.005, 0.0005];
249 for (i, e) in expected.iter().enumerate() {
250 assert!((inv[i] - e).abs() < 1e-12, "linear inv[{i}]");
251 }
252 assert_eq!(mscale, 1.0);
253 }
254
255 #[test]
256 fn llama3_scaling_matches_reference() {
257 let scaling = RopeScaling::Llama3 {
260 factor: 32.0,
261 low_freq_factor: 1.0,
262 high_freq_factor: 4.0,
263 original_max_position_embeddings: 8192,
264 };
265 let (inv, mscale) = scaled_inv_freq(64, 500_000.0, &scaling);
266 let expected = [
267 (0usize, 1.0),
268 (8, 0.037606030931),
269 (16, 0.000429556797),
270 (24, 1.661967e-06),
271 (31, 9.4183e-08),
272 ];
273 for (i, e) in expected {
274 let rel = ((inv[i] - e) / e).abs();
275 assert!(rel < 1e-6, "llama3 inv[{i}]: {} vs {e}", inv[i]);
276 }
277 assert_eq!(mscale, 1.0);
278 }
279
280 #[test]
281 fn yarn_scaling_matches_reference() {
282 let scaling = RopeScaling::Yarn {
285 factor: 4.0,
286 original_max_position_embeddings: 32768,
287 beta_fast: 32.0,
288 beta_slow: 1.0,
289 attention_factor: None,
290 };
291 let (inv, mscale) = scaled_inv_freq(128, 1_000_000.0, &scaling);
292 let expected = [
293 (0usize, 1.0),
294 (16, 0.03162277660168379),
295 (32, 0.0006029411764705882),
296 (48, 7.905694150420949e-06),
297 (63, 3.102344401879299e-07),
298 ];
299 for (i, e) in expected {
300 let rel = ((inv[i] - e) / e).abs();
301 assert!(rel < 1e-6, "yarn inv[{i}]: {} vs {e}", inv[i]);
302 }
303 assert!((mscale - 1.138629436112).abs() < 1e-9, "mscale {mscale}");
304 }
305
306 #[test]
307 fn longrope_scaling_matches_reference() {
308 let scaling = RopeScaling::LongRope {
314 short_factor: vec![1.0, 2.0, 4.0, 8.0],
315 long_factor: vec![1.0; 4],
316 original_max_position_embeddings: 4096,
317 factor: 32.0,
318 attention_factor: None,
319 };
320 let (inv, mscale) = scaled_inv_freq(8, 10_000.0, &scaling);
321 let expected = [1.0, 0.05, 0.0025, 0.000125];
322 for (i, e) in expected.iter().enumerate() {
323 assert!((inv[i] - e).abs() < 1e-15, "longrope inv[{i}]: {}", inv[i]);
324 }
325 assert!(
326 (mscale - (17.0f64 / 12.0).sqrt()).abs() < 1e-12,
327 "mscale {mscale}"
328 );
329 let scaling = RopeScaling::LongRope {
331 short_factor: vec![1.0; 4],
332 long_factor: vec![1.0; 4],
333 original_max_position_embeddings: 4096,
334 factor: 1.0,
335 attention_factor: None,
336 };
337 let (_, mscale) = scaled_inv_freq(8, 10_000.0, &scaling);
338 assert_eq!(mscale, 1.0);
339 }
340
341 #[test]
342 fn scaled_tables_apply_mscale() {
343 let scaling = RopeScaling::Yarn {
345 factor: 4.0,
346 original_max_position_embeddings: 32768,
347 beta_fast: 32.0,
348 beta_slow: 1.0,
349 attention_factor: Some(1.25),
350 };
351 let (cos, sin) = build_tables_scaled(8, 10_000.0, 2, &scaling);
352 assert!((cos[0] - 1.25).abs() < 1e-6);
353 assert!(sin[0].abs() < 1e-6);
354 }
355}