1use crate::linalg::{jacobi_svd, symmetric_eigen, DenseMatrix};
44use anyhow::Result;
45use std::collections::HashMap;
46use trustformers_core::tensor::Tensor;
47
48fn dense_from_tensor(tensor: &Tensor) -> Result<DenseMatrix> {
50 let shape = tensor.shape();
51 if shape.len() != 2 {
52 return Err(anyhow::anyhow!(
53 "expected a 2-D matrix for the LoRA factorisation, got shape {shape:?}"
54 ));
55 }
56 Ok(DenseMatrix::from_f32(
57 shape[0],
58 shape[1],
59 &tensor.data_f32()?,
60 )?)
61}
62
63#[derive(Debug, Clone)]
65pub struct LoRARITEConfig {
66 pub learning_rate: f32,
68 pub lora_rank: usize,
70 pub beta1: f32,
72 pub beta2: f32,
74 pub epsilon: f32,
76 pub weight_decay: f32,
78 pub preconditioning_strength: f32,
80 pub bias_correction: bool,
82 pub transformation_invariance: bool,
84 pub adaptation_frequency: u64,
86 pub min_singular_value: f32,
88 pub max_condition_number: f32,
90 pub adaptive_rank: bool,
92 pub factorization_reg: f32,
94}
95
96impl Default for LoRARITEConfig {
97 fn default() -> Self {
98 Self {
99 learning_rate: 1e-3,
100 lora_rank: 16,
101 beta1: 0.9,
102 beta2: 0.999,
103 epsilon: 1e-8,
104 weight_decay: 0.0,
105 preconditioning_strength: 0.1,
106 bias_correction: true,
107 transformation_invariance: true,
108 adaptation_frequency: 10,
109 min_singular_value: 1e-6,
110 max_condition_number: 1e6,
111 adaptive_rank: false,
112 factorization_reg: 1e-6,
113 }
114 }
115}
116
117impl LoRARITEConfig {
118 pub fn new() -> Self {
120 Self::default()
121 }
122
123 pub fn learning_rate(mut self, lr: f32) -> Self {
125 self.learning_rate = lr;
126 self
127 }
128
129 pub fn lora_rank(mut self, rank: usize) -> Self {
131 self.lora_rank = rank;
132 self
133 }
134
135 pub fn beta1(mut self, beta1: f32) -> Self {
137 self.beta1 = beta1;
138 self
139 }
140
141 pub fn beta2(mut self, beta2: f32) -> Self {
143 self.beta2 = beta2;
144 self
145 }
146
147 pub fn preconditioning_strength(mut self, strength: f32) -> Self {
149 self.preconditioning_strength = strength;
150 self
151 }
152
153 pub fn weight_decay(mut self, decay: f32) -> Self {
155 self.weight_decay = decay;
156 self
157 }
158
159 pub fn transformation_invariance(mut self, enable: bool) -> Self {
161 self.transformation_invariance = enable;
162 self
163 }
164
165 pub fn bias_correction(mut self, enable: bool) -> Self {
168 self.bias_correction = enable;
169 self
170 }
171
172 pub fn build(self) -> Self {
173 self
174 }
175}
176
177#[derive(Debug, Clone, Default)]
179pub struct LoRARITEState {
180 pub step: u64,
182 pub m_a: HashMap<String, Tensor>,
184 pub m_b: HashMap<String, Tensor>,
186 pub v_a: HashMap<String, Tensor>,
188 pub v_b: HashMap<String, Tensor>,
190 pub precond_a: HashMap<String, Tensor>,
192 pub precond_b: HashMap<String, Tensor>,
194 pub singular_values: HashMap<String, Tensor>,
196 pub condition_numbers: HashMap<String, f32>,
198 pub effective_ranks: HashMap<String, usize>,
200 pub transformation_stats: TransformationStats,
202}
203
204#[derive(Debug, Clone)]
206pub struct TransformationStats {
207 pub num_transformations: u64,
209 pub condition_improvement: f32,
211 pub rank_stability: f32,
213 pub preconditioning_gain: f32,
215}
216
217impl Default for TransformationStats {
218 fn default() -> Self {
219 Self {
220 num_transformations: 0,
221 condition_improvement: 0.0,
222 rank_stability: 1.0,
223 preconditioning_gain: 1.0,
224 }
225 }
226}
227
228pub struct LoRARITE {
233 config: LoRARITEConfig,
234 state: LoRARITEState,
235}
236
237impl LoRARITE {
238 pub fn new(config: LoRARITEConfig) -> Self {
240 Self {
241 config,
242 state: LoRARITEState::default(),
243 }
244 }
245
246 pub fn learning_rate(&self) -> f32 {
248 self.config.learning_rate
249 }
250
251 pub fn set_learning_rate(&mut self, lr: f32) {
253 self.config.learning_rate = lr;
254 }
255
256 fn is_lora_a_matrix(&self, param_name: &str) -> bool {
258 param_name.ends_with("_a") || param_name.contains("lora_a") || param_name.contains("lora_A")
259 }
260
261 fn is_lora_b_matrix(&self, param_name: &str) -> bool {
263 param_name.ends_with("_b") || param_name.contains("lora_b") || param_name.contains("lora_B")
264 }
265
266 fn get_lora_base_name(&self, param_name: &str) -> String {
268 if param_name.ends_with("_a") {
269 param_name.trim_end_matches("_a").to_string()
270 } else if param_name.ends_with("_b") {
271 param_name.trim_end_matches("_b").to_string()
272 } else if param_name.contains("lora_a") {
273 param_name.replace("lora_a", "lora")
274 } else if param_name.contains("lora_b") {
275 param_name.replace("lora_b", "lora")
276 } else {
277 param_name.to_string()
278 }
279 }
280
281 fn compute_svd(
291 &self,
292 matrix_a: &Tensor,
293 matrix_b: &Tensor,
294 ) -> Result<(Tensor, Tensor, Tensor)> {
295 let a = dense_from_tensor(matrix_a)?;
296 let b = dense_from_tensor(matrix_b)?;
297 let product = b.matmul(&a)?;
299 let svd = jacobi_svd(&product)?;
300
301 let u = Tensor::from_vec(svd.u.to_f32(), &[svd.u.rows(), svd.u.cols()])?;
302 let singular_values = Tensor::from_vec(
303 svd.s.iter().map(|&v| v as f32).collect::<Vec<f32>>(),
304 &[svd.s.len()],
305 )?;
306 let v = Tensor::from_vec(svd.v.to_f32(), &[svd.v.rows(), svd.v.cols()])?;
307
308 Ok((u, singular_values, v))
309 }
310
311 pub fn compute_eigenvalues(&self, matrix: &Tensor) -> Result<Tensor> {
320 let dense = dense_from_tensor(matrix)?;
321 let eigen = symmetric_eigen(&dense)?;
322 Ok(Tensor::from_vec(
323 eigen.values.iter().map(|&v| v as f32).collect::<Vec<f32>>(),
324 &[eigen.values.len()],
325 )?)
326 }
327
328 fn compute_lora_preconditioning(&self, _param_name: &str, gradient: &Tensor) -> Result<Tensor> {
330 let grad_squared = gradient.pow(2.0)?;
332
333 let preconditioner = grad_squared.add_scalar(self.config.factorization_reg)?;
335
336 if self.config.transformation_invariance {
338 self.apply_transformation_invariance(&preconditioner)
339 } else {
340 Ok(preconditioner.sqrt()?.reciprocal()?)
341 }
342 }
343
344 fn apply_transformation_invariance(&self, preconditioner: &Tensor) -> Result<Tensor> {
351 let min_val = self.config.min_singular_value;
352 let max_val = self.config.min_singular_value * self.config.max_condition_number;
353 let clamped = preconditioner.clamp(min_val, max_val)?;
354
355 let sqrt_values = clamped.sqrt()?;
357 Ok(sqrt_values.reciprocal()?)
358 }
359
360 fn update_moments(&mut self, param_name: &str, gradient: &Tensor) -> Result<(Tensor, Tensor)> {
362 let beta1 = self.config.beta1;
363 let beta2 = self.config.beta2;
364
365 let (m_map, v_map) = if self.is_lora_a_matrix(param_name) {
367 (&mut self.state.m_a, &mut self.state.v_a)
368 } else {
369 (&mut self.state.m_b, &mut self.state.v_b)
370 };
371
372 let m = if let Some(prev_m) = m_map.get(param_name) {
374 let beta1_tensor = Tensor::scalar(beta1)?;
375 let one_minus_beta1 = Tensor::scalar(1.0 - beta1)?;
376
377 let weighted_prev = prev_m.mul(&beta1_tensor)?;
378 let weighted_grad = gradient.mul(&one_minus_beta1)?;
379 weighted_prev.add(&weighted_grad)?
380 } else {
381 gradient.mul(&Tensor::scalar(1.0 - beta1)?)?
382 };
383
384 let grad_squared = gradient.pow(2.0)?;
386 let v = if let Some(prev_v) = v_map.get(param_name) {
387 let beta2_tensor = Tensor::scalar(beta2)?;
388 let one_minus_beta2 = Tensor::scalar(1.0 - beta2)?;
389
390 let weighted_prev = prev_v.mul(&beta2_tensor)?;
391 let weighted_grad_sq = grad_squared.mul(&one_minus_beta2)?;
392 weighted_prev.add(&weighted_grad_sq)?
393 } else {
394 grad_squared.mul(&Tensor::scalar(1.0 - beta2)?)?
395 };
396
397 m_map.insert(param_name.to_string(), m.clone());
399 v_map.insert(param_name.to_string(), v.clone());
400
401 Ok((m, v))
402 }
403
404 fn apply_bias_correction(&self, moment: &Tensor, beta: f32) -> Result<Tensor> {
406 if !self.config.bias_correction {
407 return Ok(moment.clone());
408 }
409
410 let step = self.state.step as f32;
411 let correction_factor = 1.0 - beta.powf(step);
412 Ok(moment.div_scalar(correction_factor)?)
413 }
414
415 fn compute_effective_rank(&self, singular_values: &Tensor) -> Result<usize> {
417 let sv_data = singular_values.data()?;
418 let total_variance: f32 = sv_data.iter().sum();
419 let threshold = 0.95 * total_variance; let mut cumulative_variance = 0.0;
422 let mut effective_rank = 0;
423
424 for &sv in sv_data.iter() {
425 cumulative_variance += sv;
426 effective_rank += 1;
427 if cumulative_variance >= threshold {
428 break;
429 }
430 }
431
432 Ok(effective_rank.min(self.config.lora_rank))
433 }
434
435 fn update_lora_stats(
437 &mut self,
438 base_name: &str,
439 matrix_a: &Tensor,
440 matrix_b: &Tensor,
441 ) -> Result<()> {
442 let (_, singular_values, _) = self.compute_svd(matrix_a, matrix_b)?;
444
445 let sv_data = singular_values.data()?;
447 let max_sv = sv_data.iter().fold(0.0f32, |a, &b| a.max(b));
448 let min_sv = sv_data.iter().fold(f32::INFINITY, |a, &b| a.min(b));
449 let condition_number = max_sv / (min_sv + self.config.epsilon);
450
451 let effective_rank = self.compute_effective_rank(&singular_values)?;
453
454 self.state.singular_values.insert(base_name.to_string(), singular_values);
456 self.state.condition_numbers.insert(base_name.to_string(), condition_number);
457 self.state.effective_ranks.insert(base_name.to_string(), effective_rank);
458
459 Ok(())
460 }
461
462 pub fn step(
464 &mut self,
465 parameters: &mut HashMap<String, Tensor>,
466 gradients: &HashMap<String, Tensor>,
467 ) -> Result<()> {
468 self.state.step += 1;
469
470 let mut processed_pairs: std::collections::HashSet<String> =
472 std::collections::HashSet::new();
473
474 for (param_name, gradient) in gradients.iter() {
475 if let Some(parameter) = parameters.get_mut(param_name) {
476 let base_name = self.get_lora_base_name(param_name);
477
478 let mut effective_gradient = gradient.clone();
480 if self.config.weight_decay > 0.0 {
481 let weight_decay_term =
482 parameter.mul(&Tensor::scalar(self.config.weight_decay)?)?;
483 effective_gradient = effective_gradient.add(&weight_decay_term)?;
484 }
485
486 let (m, v) = self.update_moments(param_name, &effective_gradient)?;
488
489 let corrected_m = self.apply_bias_correction(&m, self.config.beta1)?;
491 let corrected_v = self.apply_bias_correction(&v, self.config.beta2)?;
492
493 let preconditioner =
495 self.compute_lora_preconditioning(param_name, &effective_gradient)?;
496
497 let v_sqrt = corrected_v.sqrt()?;
499 let v_sqrt_eps = v_sqrt.add(&Tensor::scalar(self.config.epsilon)?)?;
500 let adam_update = corrected_m.div(&v_sqrt_eps)?;
501
502 let strength = Tensor::scalar(self.config.preconditioning_strength)?;
504 let one_minus_strength =
505 Tensor::scalar(1.0 - self.config.preconditioning_strength)?;
506
507 let preconditioned_update = adam_update
508 .mul(&strength)?
509 .mul(&preconditioner)?
510 .add(&adam_update.mul(&one_minus_strength)?)?;
511
512 let lr_tensor = Tensor::scalar(self.config.learning_rate)?;
514 let param_update = preconditioned_update.mul(&lr_tensor)?;
515
516 *parameter = parameter.sub(¶m_update)?;
517
518 if !processed_pairs.contains(&base_name)
524 && (self.is_lora_a_matrix(param_name) || self.is_lora_b_matrix(param_name))
525 {
526 let a_name = format!("{}_a", base_name);
527 let b_name = format!("{}_b", base_name);
528
529 if let (Some(matrix_a), Some(matrix_b)) =
530 (parameters.get(&a_name), parameters.get(&b_name))
531 {
532 self.update_lora_stats(&base_name, matrix_a, matrix_b)?;
533 processed_pairs.insert(base_name);
534 }
535 }
536 }
537 }
538
539 if self.state.step.is_multiple_of(self.config.adaptation_frequency) {
541 self.update_transformation_stats()?;
542 }
543
544 Ok(())
545 }
546
547 fn update_transformation_stats(&mut self) -> Result<()> {
549 let mut total_condition_improvement = 0.0;
550 let mut count = 0;
551
552 for &condition_number in self.state.condition_numbers.values() {
553 if condition_number < self.config.max_condition_number {
554 total_condition_improvement += 1.0 / condition_number;
555 count += 1;
556 }
557 }
558
559 if count > 0 {
560 self.state.transformation_stats.condition_improvement =
561 total_condition_improvement / count as f32;
562 self.state.transformation_stats.num_transformations += 1;
563 }
564
565 let rank_variance;
567 let ranks: Vec<f32> = self.state.effective_ranks.values().map(|&r| r as f32).collect();
568 if !ranks.is_empty() {
569 let mean_rank: f32 = ranks.iter().sum::<f32>() / ranks.len() as f32;
570 rank_variance =
571 ranks.iter().map(|&r| (r - mean_rank).powi(2)).sum::<f32>() / ranks.len() as f32;
572 self.state.transformation_stats.rank_stability = 1.0 / (1.0 + rank_variance.sqrt());
573 }
574
575 Ok(())
576 }
577
578 pub fn get_lora_stats(&self) -> LoRARITEStats {
580 let avg_condition_number = if self.state.condition_numbers.is_empty() {
581 1.0
582 } else {
583 self.state.condition_numbers.values().sum::<f32>()
584 / self.state.condition_numbers.len() as f32
585 };
586
587 let avg_effective_rank = if self.state.effective_ranks.is_empty() {
588 self.config.lora_rank
589 } else {
590 self.state.effective_ranks.values().sum::<usize>() / self.state.effective_ranks.len()
591 };
592
593 LoRARITEStats {
594 step: self.state.step,
595 avg_condition_number,
596 avg_effective_rank,
597 num_lora_pairs: self.state.singular_values.len(),
598 transformation_invariance_score: self.state.transformation_stats.condition_improvement,
599 rank_stability: self.state.transformation_stats.rank_stability,
600 preconditioning_effectiveness: self.state.transformation_stats.preconditioning_gain,
601 }
602 }
603
604 pub fn reset_state(&mut self) {
606 self.state = LoRARITEState::default();
607 }
608
609 pub fn get_condition_numbers(&self) -> &HashMap<String, f32> {
611 &self.state.condition_numbers
612 }
613
614 pub fn get_effective_ranks(&self) -> &HashMap<String, usize> {
616 &self.state.effective_ranks
617 }
618}
619
620#[derive(Debug, Clone)]
622pub struct LoRARITEStats {
623 pub step: u64,
625 pub avg_condition_number: f32,
627 pub avg_effective_rank: usize,
629 pub num_lora_pairs: usize,
631 pub transformation_invariance_score: f32,
633 pub rank_stability: f32,
635 pub preconditioning_effectiveness: f32,
637}
638
639#[cfg(test)]
640mod tests {
641 use super::*;
642 use trustformers_core::tensor::Tensor;
643
644 #[test]
645 fn test_lora_rite_creation() {
646 let config = LoRARITEConfig::new().learning_rate(1e-3).lora_rank(16).beta1(0.9).build();
647
648 let optimizer = LoRARITE::new(config);
649 assert_eq!(optimizer.learning_rate(), 1e-3);
650 }
651
652 #[test]
653 fn test_lora_rite_config_builder() {
654 let config = LoRARITEConfig::new()
655 .learning_rate(2e-3)
656 .lora_rank(32)
657 .beta1(0.95)
658 .beta2(0.999)
659 .preconditioning_strength(0.2)
660 .weight_decay(1e-4)
661 .build();
662
663 assert_eq!(config.learning_rate, 2e-3);
664 assert_eq!(config.lora_rank, 32);
665 assert_eq!(config.beta1, 0.95);
666 assert_eq!(config.beta2, 0.999);
667 assert_eq!(config.preconditioning_strength, 0.2);
668 assert_eq!(config.weight_decay, 1e-4);
669 }
670
671 #[test]
672 fn test_lora_matrix_detection() {
673 let config = LoRARITEConfig::new().build();
674 let optimizer = LoRARITE::new(config);
675
676 assert!(optimizer.is_lora_a_matrix("layer1_a"));
677 assert!(optimizer.is_lora_b_matrix("layer1_b"));
678 assert!(optimizer.is_lora_a_matrix("attention.lora_a"));
679 assert!(optimizer.is_lora_b_matrix("attention.lora_b"));
680 assert!(!optimizer.is_lora_a_matrix("layer1.weight"));
681 }
682
683 #[test]
684 fn test_lora_base_name_extraction() {
685 let config = LoRARITEConfig::new().build();
686 let optimizer = LoRARITE::new(config);
687
688 assert_eq!(optimizer.get_lora_base_name("layer1_a"), "layer1");
689 assert_eq!(optimizer.get_lora_base_name("layer1_b"), "layer1");
690 assert_eq!(
691 optimizer.get_lora_base_name("attention.lora_a"),
692 "attention.lora"
693 );
694 assert_eq!(
695 optimizer.get_lora_base_name("attention.lora_b"),
696 "attention.lora"
697 );
698 }
699
700 #[test]
701 fn test_lora_rite_step() -> Result<()> {
702 let config = LoRARITEConfig::new().learning_rate(1e-2).lora_rank(4).build();
703 let mut optimizer = LoRARITE::new(config);
704
705 let mut parameters = HashMap::new();
707 parameters.insert("layer1_a".to_string(), Tensor::ones(&[4, 8])?); parameters.insert("layer1_b".to_string(), Tensor::ones(&[2, 4])?); let mut gradients = HashMap::new();
711 gradients.insert(
712 "layer1_a".to_string(),
713 Tensor::ones(&[4, 8])?.mul_scalar(0.1)?,
714 );
715 gradients.insert(
716 "layer1_b".to_string(),
717 Tensor::ones(&[2, 4])?.mul_scalar(0.1)?,
718 );
719
720 let orig_a = parameters.get("layer1_a").expect("Key not found").clone();
722 let orig_b = parameters.get("layer1_b").expect("Key not found").clone();
723
724 optimizer.step(&mut parameters, &gradients)?;
726
727 let updated_a = parameters.get("layer1_a").expect("Key not found");
729 let updated_b = parameters.get("layer1_b").expect("Key not found");
730
731 assert_ne!(updated_a.mean()?.to_scalar()?, orig_a.mean()?.to_scalar()?);
732 assert_ne!(updated_b.mean()?.to_scalar()?, orig_b.mean()?.to_scalar()?);
733
734 Ok(())
735 }
736
737 #[test]
738 fn test_moment_updates() -> Result<()> {
739 let config = LoRARITEConfig::new().build();
740 let mut optimizer = LoRARITE::new(config);
741
742 let gradient = Tensor::ones(&[2, 2])?.mul_scalar(0.5)?;
743
744 let (m1, v1) = optimizer.update_moments("test_a", &gradient)?;
746
747 let (m2, v2) = optimizer.update_moments("test_a", &gradient)?;
749
750 assert_ne!(m1.mean()?.to_scalar()?, m2.mean()?.to_scalar()?);
752 assert_ne!(v1.mean()?.to_scalar()?, v2.mean()?.to_scalar()?);
753
754 Ok(())
755 }
756
757 #[test]
758 fn test_bias_correction() -> Result<()> {
759 let config = LoRARITEConfig::new().bias_correction(true).build();
760 let optimizer = LoRARITE::new(config);
761
762 let moment = Tensor::ones(&[2, 2])?.mul_scalar(0.5)?;
763 let beta = 0.9;
764
765 let corrected = optimizer.apply_bias_correction(&moment, beta)?;
766
767 assert!(corrected.mean()?.to_scalar()? > moment.mean()?.to_scalar()?);
769
770 Ok(())
771 }
772
773 #[test]
774 fn test_lora_stats() -> Result<()> {
775 let config = LoRARITEConfig::new().lora_rank(4).build();
776 let mut optimizer = LoRARITE::new(config);
777
778 optimizer.state.condition_numbers.insert("layer1".to_string(), 2.5);
780 optimizer.state.condition_numbers.insert("layer2".to_string(), 3.0);
781 optimizer.state.effective_ranks.insert("layer1".to_string(), 3);
782 optimizer.state.effective_ranks.insert("layer2".to_string(), 4);
783
784 let stats = optimizer.get_lora_stats();
785 assert_eq!(stats.num_lora_pairs, 0); assert_eq!(stats.avg_condition_number, 2.75); assert_eq!(stats.avg_effective_rank, 3); Ok(())
790 }
791
792 #[test]
793 fn test_learning_rate_methods() {
794 let config = LoRARITEConfig::new().learning_rate(1e-3).build();
795 let mut optimizer = LoRARITE::new(config);
796
797 assert_eq!(optimizer.learning_rate(), 1e-3);
798
799 optimizer.set_learning_rate(2e-3);
800 assert_eq!(optimizer.learning_rate(), 2e-3);
801 }
802
803 #[test]
804 fn test_weight_decay() -> Result<()> {
805 let config = LoRARITEConfig::new().learning_rate(1e-2).weight_decay(1e-2).build();
806 let mut optimizer = LoRARITE::new(config);
807
808 let mut parameters = HashMap::new();
809 parameters.insert("layer1_a".to_string(), Tensor::ones(&[2, 2])?);
810
811 let mut gradients = HashMap::new();
812 gradients.insert("layer1_a".to_string(), Tensor::zeros(&[2, 2])?);
813
814 let initial_param_value =
815 parameters.get("layer1_a").expect("Key not found").mean()?.to_scalar()?;
816
817 optimizer.step(&mut parameters, &gradients)?;
818
819 let final_param_value =
820 parameters.get("layer1_a").expect("Key not found").mean()?.to_scalar()?;
821
822 assert!(final_param_value < initial_param_value);
824
825 Ok(())
826 }
827
828 #[test]
829 fn test_transformation_invariance() -> Result<()> {
830 let config = LoRARITEConfig::new().transformation_invariance(true).build();
831 let optimizer = LoRARITE::new(config);
832
833 let preconditioner = Tensor::ones(&[2, 2])?.mul_scalar(2.0)?;
834 let transformed = optimizer.apply_transformation_invariance(&preconditioner)?;
835
836 let result_value = transformed.mean()?.to_scalar()?;
838 assert!(result_value > 0.0);
839 assert!(result_value.is_finite());
840
841 Ok(())
842 }
843}