use candle_core::{DType, Device, Tensor};
use crate::config::RopeScaling;
use crate::error::Result;
fn apply_llama3_scaling(
inv_freq: &mut [f64],
factor: f64,
low_freq_factor: f64,
high_freq_factor: f64,
original_max_position_embeddings: usize,
) {
use std::f64::consts::PI;
#[allow(clippy::cast_precision_loss, clippy::as_conversions)]
let orig_max = original_max_position_embeddings as f64;
let low_freq_wavelen = orig_max / low_freq_factor;
let high_freq_wavelen = orig_max / high_freq_factor;
for freq in inv_freq.iter_mut() {
let wavelen = 2.0 * PI / *freq;
if wavelen > low_freq_wavelen {
*freq /= factor;
} else if wavelen >= high_freq_wavelen {
let smooth =
(orig_max / wavelen - low_freq_factor) / (high_freq_factor - low_freq_factor);
let scaled = (1.0 - smooth) * *freq / factor;
let unscaled = smooth * *freq;
*freq = scaled + unscaled;
}
}
}
fn divide_per_dim(base_inv_freq: &[f64], factor: &[f64]) -> Result<Vec<f64>> {
if base_inv_freq.len() != factor.len() {
return Err(crate::error::MIError::Config(format!(
"longrope factor length {} does not match head_dim/2 = {}",
factor.len(),
base_inv_freq.len()
)));
}
Ok(base_inv_freq
.iter()
.zip(factor)
.map(|(b, f)| b / f)
.collect())
}
fn build_cos_sin(
inv_freq: &[f64],
max_position: usize,
mscale: f64,
device: &Device,
dtype: DType,
) -> Result<(Tensor, Tensor)> {
let half_dim = inv_freq.len();
#[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
let inv_freq_f32: Vec<f32> = inv_freq.iter().map(|&f| f as f32).collect();
let inv_freq_tensor = Tensor::from_vec(inv_freq_f32, (1, half_dim), device)?.to_dtype(dtype)?;
let positions: Vec<f32> = (0..max_position)
.map(|p| {
#[allow(clippy::cast_precision_loss, clippy::as_conversions)]
let pos = p as f32;
pos
})
.collect();
let pos_tensor = Tensor::from_vec(positions, (max_position, 1), device)?.to_dtype(dtype)?;
let freqs = pos_tensor.matmul(&inv_freq_tensor)?;
let cos = freqs.cos()?;
let sin = freqs.sin()?;
if (mscale - 1.0).abs() > f64::EPSILON {
Ok((cos.affine(mscale, 0.0)?, sin.affine(mscale, 0.0)?))
} else {
Ok((cos, sin))
}
}
struct LongRegime {
cos: Tensor,
sin: Tensor,
original_max_position: usize,
}
pub struct RopeCache {
cos: Tensor,
sin: Tensor,
long: Option<LongRegime>,
}
impl RopeCache {
#[allow(clippy::needless_pass_by_value)]
pub fn new(
head_dim: usize,
max_position: usize,
theta: f64,
scaling: Option<RopeScaling>,
device: &Device,
dtype: DType,
) -> Result<Self> {
let half_dim = head_dim / 2;
let base_inv_freq: Vec<f64> = (0..half_dim)
.map(|i| {
#[allow(clippy::cast_precision_loss, clippy::as_conversions)]
let exponent = 2.0 * i as f64 / head_dim as f64;
1.0 / theta.powf(exponent)
})
.collect();
if let Some(RopeScaling::Longrope {
short_factor,
long_factor,
original_max_position_embeddings,
attention_factor,
}) = &scaling
{
let short_inv = divide_per_dim(&base_inv_freq, short_factor)?;
let short_max = max_position.min(*original_max_position_embeddings);
let (cos, sin) =
build_cos_sin(&short_inv, short_max, *attention_factor, device, dtype)?;
let long_inv = divide_per_dim(&base_inv_freq, long_factor)?;
let (long_cos, long_sin) =
build_cos_sin(&long_inv, max_position, *attention_factor, device, dtype)?;
return Ok(Self {
cos,
sin,
long: Some(LongRegime {
cos: long_cos,
sin: long_sin,
original_max_position: *original_max_position_embeddings,
}),
});
}
let mut inv_freq = base_inv_freq;
if let Some(RopeScaling::Llama3 {
factor,
low_freq_factor,
high_freq_factor,
original_max_position_embeddings,
}) = &scaling
{
apply_llama3_scaling(
&mut inv_freq,
*factor,
*low_freq_factor,
*high_freq_factor,
*original_max_position_embeddings,
);
}
#[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
let inv_freq_f32: Vec<f32> = inv_freq.iter().map(|&f| f as f32).collect();
let inv_freq_tensor =
Tensor::from_vec(inv_freq_f32, (1, half_dim), device)?.to_dtype(dtype)?;
let positions: Vec<f32> = if let Some(RopeScaling::Linear { factor }) = &scaling {
(0..max_position)
.map(|p| {
#[allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::as_conversions
)]
let scaled = (p as f64 / factor) as f32;
scaled
})
.collect()
} else {
(0..max_position)
.map(|p| {
#[allow(clippy::cast_precision_loss, clippy::as_conversions)]
let pos = p as f32;
pos
})
.collect()
};
let pos_tensor = Tensor::from_vec(positions, (max_position, 1), device)?.to_dtype(dtype)?;
let freqs = pos_tensor.matmul(&inv_freq_tensor)?;
let cos = freqs.cos()?;
let sin = freqs.sin()?;
Ok(Self {
cos,
sin,
long: None,
})
}
pub fn apply(&self, x: &Tensor, start_pos: usize) -> Result<Tensor> {
let (_, _, seq_len, _) = x.dims4()?;
let (cos_full, sin_full) = self.long.as_ref().map_or((&self.cos, &self.sin), |lr| {
if start_pos + seq_len > lr.original_max_position {
(&lr.cos, &lr.sin)
} else {
(&self.cos, &self.sin)
}
});
let cos = cos_full.narrow(0, start_pos, seq_len)?;
let sin = sin_full.narrow(0, start_pos, seq_len)?;
Ok(candle_nn::rotary_emb::rope(&x.contiguous()?, &cos, &sin)?)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
mod tests {
use super::*;
#[test]
fn longrope_short_inv_freq_matches_phi35_ground_truth() {
let head_dim = 96usize;
let theta = 10_000.0f64;
let half = head_dim / 2;
let base: Vec<f64> = (0..half)
.map(|i| 1.0 / theta.powf(2.0 * i as f64 / head_dim as f64))
.collect();
let short_factor = [1.0_f64, 1.02];
assert!((base[0] / short_factor[0] - 1.0).abs() < 1e-12);
let inv1 = base[1] / short_factor[1];
assert!(
(inv1 - 0.809_219_78).abs() < 1e-6,
"inv_freq[1] = {inv1}, expected ~0.80921978"
);
}
#[test]
fn divide_per_dim_rejects_length_mismatch() {
let base = vec![1.0, 0.5, 0.25];
assert!(divide_per_dim(&base, &[2.0, 2.0]).is_err());
assert_eq!(
divide_per_dim(&base, &[2.0, 1.0, 0.5]).unwrap(),
vec![0.5, 0.5, 0.5]
);
}
}