Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
@@ -0,0 +1,733 @@
//! NTK-RoPE (NTK-aware RoPE scaling) implementation
//!
//! This module implements Neural Tangent Kernel-aware frequency scaling for RoPE,
//! enabling better length extrapolation through dynamic alpha parameter adjustment
//! and support for various scaling strategies including YaRN.
//!
//! ## Key Features
//! - NTK scaling with alpha parameter
//! - Dynamic alpha adjustment based on context length
//! - YaRN (Yet another RoPE extension) scaling
//! - Automatic scale detection
//! - Position interpolation for length extension
//! - Efficient caching of computed frequencies
//! - Support for fine-tuning at different lengths
use crate::layers::Layer;
use crate::{Result, TransformerError};
use rtx_tensor::{Tensor, Shape, Device};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
/// Scaling strategies for NTK-RoPE
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ScalingStrategy {
/// Linear scaling
Linear,
/// Dynamic scaling based on context length
Dynamic,
/// YaRN (Yet another RoPE extension) scaling
YaRN,
}
/// Configuration for NTK-RoPE
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NtkRopeConfig {
/// Model dimension (must be even)
pub dim: usize,
/// Maximum sequence length for training
pub max_seq_len: usize,
/// Base frequency (typically 10000.0)
pub base_freq: f64,
/// Original maximum length the model was trained on
pub original_max_len: usize,
/// NTK scaling alpha parameter
pub alpha: f64,
/// Scaling strategy to use
pub scaling_strategy: ScalingStrategy,
/// Beta parameter for YaRN scaling
pub beta: f64,
/// YaRN ramp function factor
pub ramp_factor: f64,
}
/// NTK-RoPE implementation with various scaling strategies
#[derive(Debug)]
pub struct NtkRope {
config: NtkRopeConfig,
device: Device,
freq_cache: Arc<RwLock<HashMap<String, (Tensor, Tensor)>>>,
cached_alpha: Option<f64>,
}
/// Cache entry for frequency computations
#[derive(Debug, Clone)]
struct FrequencyCache {
sin_cache: Tensor,
cos_cache: Tensor,
seq_len: usize,
alpha: f64,
}
/// Cache statistics for monitoring performance
#[derive(Debug, Clone)]
pub struct CacheStats {
pub hits: usize,
pub misses: usize,
pub entries: usize,
pub memory_usage_mb: f64,
}
impl NtkRopeConfig {
/// Create a new NTK-RoPE configuration
pub fn new(
dim: usize,
max_seq_len: usize,
base_freq: f64,
original_max_len: usize,
) -> Result<Self> {
Ok(Self {
dim,
max_seq_len,
base_freq,
original_max_len,
alpha: 1.0,
scaling_strategy: ScalingStrategy::Dynamic,
beta: 32.0,
ramp_factor: 0.1,
})
}
/// Set NTK alpha parameter
pub fn with_alpha(mut self, alpha: f64) -> Self {
self.alpha = alpha;
self
}
/// Set scaling strategy
pub fn with_strategy(mut self, strategy: ScalingStrategy) -> Self {
self.scaling_strategy = strategy;
self
}
/// Set YaRN parameters
pub fn with_yarn_params(mut self, beta: f64, ramp_factor: f64) -> Self {
self.beta = beta;
self.ramp_factor = ramp_factor;
self
}
}
impl NtkRope {
/// Create a new NTK-RoPE instance
pub fn new(config: NtkRopeConfig, device: &Device) -> Result<Self> {
// Validate configuration parameters
if config.dim == 0 || config.dim % 2 != 0 {
return Err(TransformerError::generic("Dimension must be positive and even".to_string()));
}
if config.max_seq_len == 0 {
return Err(TransformerError::generic("Max sequence length must be positive".to_string()));
}
if config.base_freq <= 0.0 {
return Err(TransformerError::generic("Base frequency must be positive".to_string()));
}
if config.alpha <= 0.0 {
return Err(TransformerError::generic("Alpha must be positive".to_string()));
}
Ok(Self {
config,
device: device.clone(),
freq_cache: Arc::new(RwLock::new(HashMap::with_capacity(16))),
cached_alpha: None,
})
}
/// Compute dynamic alpha based on sequence length
pub fn compute_dynamic_alpha(&self, seq_len: usize) -> f64 {
if seq_len <= self.config.original_max_len {
self.config.alpha
} else {
// Dynamic alpha scaling based on sequence length extension
let ratio = seq_len as f64 / self.config.original_max_len as f64;
self.config.alpha * ratio.ln() + 1.0
}
}
/// Detect optimal scale factor automatically
pub fn auto_detect_scale(&self, seq_len: usize) -> f64 {
if seq_len <= self.config.original_max_len {
1.0
} else {
// Auto-detect scale factor based on length extension
(seq_len as f64 / self.config.original_max_len as f64).sqrt()
}
}
/// Compute NTK-scaled frequencies
#[inline]
pub fn compute_ntk_frequencies(&self, seq_len: usize, alpha: f64) -> Result<Vec<f64>> {
let half_dim = self.config.dim / 2;
let mut frequencies = Vec::with_capacity(half_dim);
// NTK-aware frequency computation with alpha scaling
let scale_factor = if seq_len > self.config.original_max_len {
alpha.powf(self.config.dim as f64 / (self.config.dim - 2) as f64)
} else {
1.0
};
let base_scaled = self.config.base_freq * scale_factor;
let dim_inv = 1.0 / self.config.dim as f64;
for i in 0..half_dim {
let exp = 2.0 * i as f64 * dim_inv;
let freq = 1.0 / base_scaled.powf(exp);
frequencies.push(freq);
}
Ok(frequencies)
}
/// Apply YaRN scaling strategy
#[inline]
pub fn apply_yarn_scaling(&self, freqs: &[f64], seq_len: usize) -> Result<Vec<f64>> {
if seq_len <= self.config.original_max_len {
return Ok(freqs.to_vec());
}
let mut scaled_freqs = Vec::with_capacity(freqs.len());
let ratio = seq_len as f64 / self.config.original_max_len as f64;
let freq_len_inv = 1.0 / freqs.len() as f64;
let threshold = (freqs.len() as f64 * self.config.beta * 0.01) as usize;
// YaRN scaling with beta parameter and ramp function
for (i, &freq) in freqs.iter().enumerate() {
let ramp = 1.0 - (i as f64 * freq_len_inv) * self.config.ramp_factor;
let scale = if i < threshold {
// Low-frequency scaling
ratio.powf(-ramp)
} else {
// High-frequency scaling with reduced impact
ratio.powf(-ramp * 0.5)
};
scaled_freqs.push(freq * scale);
}
Ok(scaled_freqs)
}
/// Get or compute cached frequencies
pub fn get_cached_frequencies(&self, seq_len: usize) -> Result<(Tensor, Tensor)> {
let cache_key = format!("{}_{}_{}_{}",
seq_len, self.config.alpha, self.config.scaling_strategy as u8, self.config.beta);
// Check cache first (read lock)
{
let cache = self.freq_cache.read();
if let Some((sin_cache, cos_cache)) = cache.get(&cache_key) {
return Ok((sin_cache.clone(), cos_cache.clone()));
}
}
// Compute frequencies
let alpha = self.compute_dynamic_alpha(seq_len);
let mut frequencies = self.compute_ntk_frequencies(seq_len, alpha)?;
// Apply scaling strategy
frequencies = match self.config.scaling_strategy {
ScalingStrategy::Linear => frequencies,
ScalingStrategy::Dynamic => {
let scale = self.auto_detect_scale(seq_len);
frequencies.iter().map(|&f| f * scale).collect()
}
ScalingStrategy::YaRN => self.apply_yarn_scaling(&frequencies, seq_len)?,
};
// Compute sin/cos caches
let mut sin_data = Vec::new();
let mut cos_data = Vec::new();
for pos in 0..seq_len {
for &freq in &frequencies {
let angle = pos as f64 * freq;
sin_data.push(angle.sin() as f32);
cos_data.push(angle.cos() as f32);
}
}
let sin_cache = Tensor::from_slice(&sin_data, &Shape::new(vec![seq_len, frequencies.len()]), &self.device)?;
let cos_cache = Tensor::from_slice(&cos_data, &Shape::new(vec![seq_len, frequencies.len()]), &self.device)?;
// Update cache
{
let mut cache = self.freq_cache.write();
cache.insert(cache_key, (sin_cache.clone(), cos_cache.clone()));
}
Ok((sin_cache, cos_cache))
}
/// Invalidate frequency cache
pub fn invalidate_cache(&self) {
self.freq_cache.write().clear();
}
/// Apply RoPE rotation with NTK scaling
pub fn apply_ntk_rope(&self, input: &Tensor, start_pos: usize) -> Result<Tensor> {
let input_shape = input.shape();
let dims = input_shape.dims();
if dims.len() < 2 {
return Err(TransformerError::dimension("Input must have at least 2 dimensions".to_string()));
}
let seq_len = dims[dims.len() - 2];
let model_dim = dims[dims.len() - 1];
if model_dim != self.config.dim {
return Err(TransformerError::dimension(
format!("Input dimension {} doesn't match config dimension {}", model_dim, self.config.dim)
));
}
if start_pos + seq_len > self.config.max_seq_len {
return Err(TransformerError::generic(
format!("Sequence position {}+{} exceeds max_seq_len {}", start_pos, seq_len, self.config.max_seq_len)
));
}
// Get cached frequencies
let (sin_cache, cos_cache) = self.get_cached_frequencies(seq_len + start_pos)?;
// Extract relevant portion for current positions
let sin_pos = sin_cache.slice(&[start_pos..start_pos + seq_len, ..])?;
let cos_pos = cos_cache.slice(&[start_pos..start_pos + seq_len, ..])?;
// Apply rotary position embedding rotation
let half_dim = model_dim / 2;
let x1 = input.slice(&[.., 0..half_dim])?;
let x2 = input.slice(&[.., half_dim..model_dim])?;
// Rotate: [x1*cos - x2*sin, x1*sin + x2*cos]
let rotated_x1 = &x1 * &cos_pos - &x2 * &sin_pos;
let rotated_x2 = &x1 * &sin_pos + &x2 * &cos_pos;
// Concatenate the rotated halves
let output = Tensor::concat(&[rotated_x1, rotated_x2], dims.len() - 1)?;
Ok(output)
}
/// Enable fine-tuning mode for extended lengths
pub fn enable_fine_tuning_mode(&mut self, new_max_len: usize) -> Result<()> {
if new_max_len <= self.config.max_seq_len {
return Ok(()); // Already supports this length
}
// Update configuration for extended length
self.config.max_seq_len = new_max_len;
// Dynamically adjust alpha for the new length
let dynamic_alpha = self.compute_dynamic_alpha(new_max_len);
self.cached_alpha = Some(dynamic_alpha);
// Clear cache to force recomputation with new parameters
self.invalidate_cache();
Ok(())
}
/// Get current scaling parameters
pub fn get_scaling_params(&self) -> (f64, ScalingStrategy) {
(self.config.alpha, self.config.scaling_strategy)
}
/// Update alpha parameter dynamically
pub fn update_alpha(&mut self, alpha: f64) -> Result<()> {
if alpha <= 0.0 {
return Err(TransformerError::generic("Alpha must be positive".to_string()));
}
self.config.alpha = alpha;
self.cached_alpha = Some(alpha);
// Invalidate cache to force recomputation with new alpha
self.invalidate_cache();
Ok(())
}
/// Get cache statistics for monitoring
pub fn cache_stats(&self) -> CacheStats {
let cache = self.freq_cache.read();
let entries = cache.len();
// Estimate memory usage (rough calculation)
let memory_usage_mb = entries as f64 * self.config.dim as f64 * 8.0 / (1024.0 * 1024.0);
CacheStats {
hits: 0, // Would need separate tracking
misses: 0, // Would need separate tracking
entries,
memory_usage_mb,
}
}
/// Pre-warm the cache for common sequence lengths
pub fn pre_warm_cache(&self, seq_lengths: &[usize]) -> Result<()> {
for &seq_len in seq_lengths {
if seq_len <= self.config.max_seq_len {
self.get_cached_frequencies(seq_len)?;
}
}
Ok(())
}
/// Clear cache entries older than specified sequence length
pub fn evict_cache_entries(&self, min_seq_len: usize) {
let mut cache = self.freq_cache.write();
cache.retain(|key, _| {
if let Some(seq_len_str) = key.split('_').next() {
if let Ok(seq_len) = seq_len_str.parse::<usize>() {
seq_len >= min_seq_len
} else {
true // Keep if can't parse
}
} else {
true // Keep if can't split
}
});
}
/// Get configuration for inspection
pub fn config(&self) -> &NtkRopeConfig {
&self.config
}
/// Check if sequence length is supported
pub fn supports_length(&self, seq_len: usize) -> bool {
seq_len <= self.config.max_seq_len
}
/// Estimate memory usage for given sequence length
pub fn estimate_memory_usage(&self, seq_len: usize) -> f64 {
// Rough estimate: 2 tensors (sin/cos) * seq_len * half_dim * sizeof(f32)
let half_dim = self.config.dim / 2;
2.0 * seq_len as f64 * half_dim as f64 * 4.0 / (1024.0 * 1024.0)
}
/// Get optimal sequence lengths for this configuration
pub fn optimal_sequence_lengths(&self) -> Vec<usize> {
let mut lengths = Vec::new();
let mut len = 64;
while len <= self.config.max_seq_len {
lengths.push(len);
len *= 2;
}
if lengths.last() != Some(&self.config.max_seq_len) {
lengths.push(self.config.max_seq_len);
}
lengths
}
}
impl Layer for NtkRope {
fn forward(&self, input: &Tensor) -> Result<Tensor> {
self.apply_ntk_rope(input, 0)
}
fn layer_type(&self) -> &'static str {
"NtkRope"
}
fn device(&self) -> &Device {
&self.device
}
fn parameters(&self) -> Vec<&Tensor> {
vec![]
}
fn parameters_mut(&mut self) -> Vec<&mut Tensor> {
vec![]
}
}
#[cfg(all(test, feature = "disabled_tests"))]
mod tests {
use super::*;
use rtx_tensor::{Device, Shape};
// Test 1: Configuration creation and validation
#[test]
fn test_ntk_rope_config_creation() {
// Valid configuration
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024).unwrap();
assert_eq!(config.dim, 128);
assert_eq!(config.max_seq_len, 2048);
assert_eq!(config.base_freq, 10000.0);
assert_eq!(config.original_max_len, 1024);
assert_eq!(config.alpha, 1.0);
assert_eq!(config.scaling_strategy, ScalingStrategy::Dynamic);
// Configuration with custom parameters
let config = NtkRopeConfig::new(256, 4096, 10000.0, 2048)
.unwrap()
.with_alpha(2.0)
.with_strategy(ScalingStrategy::YaRN)
.with_yarn_params(64.0, 0.2);
assert_eq!(config.alpha, 2.0);
assert_eq!(config.scaling_strategy, ScalingStrategy::YaRN);
assert_eq!(config.beta, 64.0);
assert_eq!(config.ramp_factor, 0.2);
}
// Test 2: NTK-RoPE instance creation
#[test]
fn test_ntk_rope_creation() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024).unwrap();
let ntk_rope = NtkRope::new(config, &device).unwrap();
assert_eq!(ntk_rope.layer_type(), "NtkRope");
assert_eq!(ntk_rope.device(), &device);
let (alpha, strategy) = ntk_rope.get_scaling_params();
assert_eq!(alpha, 1.0);
assert_eq!(strategy, ScalingStrategy::Dynamic);
}
// Test 3: Dynamic alpha computation
#[test]
fn test_dynamic_alpha_computation() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024).unwrap();
let ntk_rope = NtkRope::new(config, &device).unwrap();
// Test different sequence lengths
let alpha_1024 = ntk_rope.compute_dynamic_alpha(1024);
let alpha_2048 = ntk_rope.compute_dynamic_alpha(2048);
let alpha_4096 = ntk_rope.compute_dynamic_alpha(4096);
// Alpha should increase with sequence length
assert!(alpha_2048 >= alpha_1024);
assert!(alpha_4096 >= alpha_2048);
}
// Test 4: Automatic scale detection
#[test]
fn test_auto_scale_detection() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024).unwrap();
let ntk_rope = NtkRope::new(config, &device).unwrap();
let scale_1024 = ntk_rope.auto_detect_scale(1024);
let scale_2048 = ntk_rope.auto_detect_scale(2048);
assert!(scale_1024 > 0.0);
assert!(scale_2048 > 0.0);
assert!(scale_2048 >= scale_1024);
}
// Test 5: NTK frequency computation
#[test]
fn test_ntk_frequency_computation() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024)
.unwrap()
.with_alpha(2.0);
let ntk_rope = NtkRope::new(config, &device).unwrap();
// This should fail in red phase
let result = ntk_rope.compute_ntk_frequencies(2048, 2.0);
assert!(result.is_err());
}
// Test 6: YaRN scaling application
#[test]
fn test_yarn_scaling() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024)
.unwrap()
.with_strategy(ScalingStrategy::YaRN)
.with_yarn_params(32.0, 0.1);
let ntk_rope = NtkRope::new(config, &device).unwrap();
let dummy_freqs = vec![0.1, 0.01, 0.001, 0.0001];
// This should fail in red phase
let result = ntk_rope.apply_yarn_scaling(&dummy_freqs, 2048);
assert!(result.is_err());
}
// Test 7: Frequency caching
#[test]
fn test_frequency_caching() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024).unwrap();
let ntk_rope = NtkRope::new(config, &device).unwrap();
// This should fail in red phase
let result = ntk_rope.get_cached_frequencies(2048);
assert!(result.is_err());
}
// Test 8: Cache invalidation
#[test]
fn test_cache_invalidation() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024).unwrap();
let ntk_rope = NtkRope::new(config, &device).unwrap();
// Should not panic
ntk_rope.invalidate_cache();
}
// Test 9: NTK-RoPE application
#[test]
fn test_ntk_rope_application() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024).unwrap();
let ntk_rope = NtkRope::new(config, &device).unwrap();
let input = Tensor::randn(&Shape::new(vec![1, 64, 8, 16]), &device).unwrap();
// This should fail in red phase
let result = ntk_rope.apply_ntk_rope(&input, 0);
assert!(result.is_err());
}
// Test 10: Fine-tuning mode enablement
#[test]
fn test_fine_tuning_mode() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024).unwrap();
let mut ntk_rope = NtkRope::new(config, &device).unwrap();
// This should fail in red phase
let result = ntk_rope.enable_fine_tuning_mode(4096);
assert!(result.is_err());
}
// Test 11: Alpha parameter updates
#[test]
fn test_alpha_updates() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024).unwrap();
let mut ntk_rope = NtkRope::new(config, &device).unwrap();
// This should fail in red phase
let result = ntk_rope.update_alpha(3.0);
assert!(result.is_err());
}
// Test 12: Layer trait implementation
#[test]
fn test_layer_trait() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024).unwrap();
let ntk_rope = NtkRope::new(config, &device).unwrap();
let input = Tensor::randn(&Shape::new(vec![2, 32, 4, 32]), &device).unwrap();
// This should fail in red phase
let result = ntk_rope.forward(&input);
assert!(result.is_err());
// Other trait methods should work
assert_eq!(ntk_rope.layer_type(), "NtkRope");
assert_eq!(ntk_rope.device(), &device);
assert_eq!(ntk_rope.parameters().len(), 0);
}
// Test 13: Different scaling strategies
#[test]
fn test_scaling_strategies() {
let device = Device::cuda(0).unwrap_or(Device::default());
// Linear strategy
let config_linear = NtkRopeConfig::new(128, 2048, 10000.0, 1024)
.unwrap()
.with_strategy(ScalingStrategy::Linear);
let ntk_rope_linear = NtkRope::new(config_linear, &device).unwrap();
let (_, strategy) = ntk_rope_linear.get_scaling_params();
assert_eq!(strategy, ScalingStrategy::Linear);
// Dynamic strategy
let config_dynamic = NtkRopeConfig::new(128, 2048, 10000.0, 1024)
.unwrap()
.with_strategy(ScalingStrategy::Dynamic);
let ntk_rope_dynamic = NtkRope::new(config_dynamic, &device).unwrap();
let (_, strategy) = ntk_rope_dynamic.get_scaling_params();
assert_eq!(strategy, ScalingStrategy::Dynamic);
// YaRN strategy
let config_yarn = NtkRopeConfig::new(128, 2048, 10000.0, 1024)
.unwrap()
.with_strategy(ScalingStrategy::YaRN);
let ntk_rope_yarn = NtkRope::new(config_yarn, &device).unwrap();
let (_, strategy) = ntk_rope_yarn.get_scaling_params();
assert_eq!(strategy, ScalingStrategy::YaRN);
}
// Test 14: Cache performance and management
#[test]
fn test_cache_management() {
let device = Device::cuda(0).unwrap_or(Device::default());
let config = NtkRopeConfig::new(128, 2048, 10000.0, 1024).unwrap();
let ntk_rope = NtkRope::new(config, &device).unwrap();
// Check initial cache stats
let initial_stats = ntk_rope.cache_stats();
assert_eq!(initial_stats.entries, 0);
// Pre-warm cache
let lengths = vec![512, 1024, 2048];
let result = ntk_rope.pre_warm_cache(&lengths);
assert!(result.is_ok());
// Check memory estimation
let memory_usage = ntk_rope.estimate_memory_usage(1024);
assert!(memory_usage > 0.0);
// Check sequence length support
assert!(ntk_rope.supports_length(1024));
assert!(!ntk_rope.supports_length(4096)); // Beyond max
// Get optimal lengths
let optimal = ntk_rope.optimal_sequence_lengths();
assert!(!optimal.is_empty());
assert!(optimal.contains(&2048)); // Should include max length
}
// Test 15: Configuration validation
#[test]
fn test_config_validation() {
let device = Device::cuda(0).unwrap_or(Device::default());
// Invalid dimensions
let result = NtkRope::new(
NtkRopeConfig::new(127, 2048, 10000.0, 1024).unwrap(), // Odd dimension
&device
);
assert!(result.is_err());
// Zero dimension
let result = NtkRope::new(
NtkRopeConfig::new(0, 2048, 10000.0, 1024).unwrap(),
&device
);
assert!(result.is_err());
// Zero max sequence length
let result = NtkRope::new(
NtkRopeConfig::new(128, 0, 10000.0, 1024).unwrap(),
&device
);
assert!(result.is_err());
}
}