1#![allow(dead_code)]
39
40use crate::common::{OptimizerState, StateMemoryStats};
41use crate::traits::StatefulOptimizer;
42use std::collections::HashMap;
43use trustformers_core::errors::{Result, TrustformersError};
44use trustformers_core::tensor::Tensor;
45use trustformers_core::traits::Optimizer;
46
47#[derive(Debug, Clone)]
49pub struct EVAConfig {
50 pub lr: f32,
52 pub beta1: f32,
54 pub beta2: f32,
56 pub eps: f32,
58 pub weight_decay: f32,
60 pub variance_adaptation: bool,
62 pub bias_correction: bool,
64 pub adaptation_strength: f32,
66}
67
68impl Default for EVAConfig {
69 fn default() -> Self {
70 Self {
71 lr: 1e-3,
72 beta1: 0.9,
73 beta2: 0.999,
74 eps: 1e-8,
75 weight_decay: 0.01,
76 variance_adaptation: true,
77 bias_correction: true,
78 adaptation_strength: 1.0,
79 }
80 }
81}
82
83#[derive(Debug)]
85pub struct EVA {
86 config: EVAConfig,
87 state: OptimizerState,
88 exp_avg: HashMap<String, Vec<f32>>,
89 exp_avg_sq: HashMap<String, Vec<f32>>,
90 var_adaptation: HashMap<String, Vec<f32>>,
91 step_count: usize,
92}
93
94impl EVA {
95 pub fn new(
97 lr: f32,
98 beta1: f32,
99 beta2: f32,
100 eps: f32,
101 weight_decay: f32,
102 variance_adaptation: bool,
103 ) -> Self {
104 let config = EVAConfig {
105 lr,
106 beta1,
107 beta2,
108 eps,
109 weight_decay,
110 variance_adaptation,
111 bias_correction: true,
112 adaptation_strength: 1.0,
113 };
114
115 Self::with_config(config)
116 }
117
118 pub fn with_config(config: EVAConfig) -> Self {
120 Self {
121 config,
122 state: OptimizerState::new(),
123 exp_avg: HashMap::new(),
124 exp_avg_sq: HashMap::new(),
125 var_adaptation: HashMap::new(),
126 step_count: 0,
127 }
128 }
129
130 pub fn adamw_like(lr: f32, weight_decay: f32) -> Self {
132 Self::new(lr, 0.9, 0.999, 1e-8, weight_decay, true)
133 }
134
135 pub fn no_variance_adaptation(lr: f32, beta1: f32, beta2: f32, eps: f32) -> Self {
137 Self::new(lr, beta1, beta2, eps, 0.0, false)
138 }
139
140 pub fn get_lr(&self) -> f32 {
142 self.config.lr
143 }
144
145 pub fn set_lr(&mut self, lr: f32) {
147 self.config.lr = lr;
148 }
149
150 pub fn config(&self) -> &EVAConfig {
152 &self.config
153 }
154
155 pub fn memory_stats(&self) -> StateMemoryStats {
157 let mut total_parameters = 0;
158 let mut _total_buffers = 0;
159 for buffer in self.exp_avg.values() {
160 total_parameters += buffer.len();
161 _total_buffers += 1;
162 }
163
164 for buffer in self.exp_avg_sq.values() {
165 total_parameters += buffer.len();
166 _total_buffers += 1;
167 }
168
169 if self.config.variance_adaptation {
170 for buffer in self.var_adaptation.values() {
171 total_parameters += buffer.len();
172 _total_buffers += 1;
173 }
174 }
175
176 StateMemoryStats {
177 momentum_elements: total_parameters,
178 variance_elements: total_parameters,
179 third_moment_elements: if self.config.variance_adaptation {
180 total_parameters
181 } else {
182 0
183 },
184 total_bytes: total_parameters * 4, num_parameters: total_parameters,
186 }
187 }
188
189 fn compute_variance_adaptation(&self, grad_var: f32, step: usize) -> f32 {
191 if !self.config.variance_adaptation || step == 0 {
192 return 1.0;
193 }
194
195 let adaptation = (grad_var + self.config.eps).sqrt();
196 let strength = self.config.adaptation_strength;
197
198 let factor = 1.0 / (1.0 + strength * adaptation);
200 factor.clamp(0.1, 2.0)
201 }
202}
203
204impl Optimizer for EVA {
205 fn update(&mut self, parameter: &mut Tensor, grad: &Tensor) -> Result<()> {
206 self.step_count += 1;
207
208 match (parameter, grad) {
209 (Tensor::F32(param), Tensor::F32(grad_data)) => {
210 let param_id = format!("{:p}", param.as_ptr());
211 let size = grad_data.len();
212
213 let exp_avg =
215 self.exp_avg.entry(param_id.clone()).or_insert_with(|| vec![0.0; size]);
216 let exp_avg_sq =
217 self.exp_avg_sq.entry(param_id.clone()).or_insert_with(|| vec![0.0; size]);
218 let mut var_adapt = if self.config.variance_adaptation {
219 Some(
220 self.var_adaptation
221 .entry(param_id.clone())
222 .or_insert_with(|| vec![0.0; size]),
223 )
224 } else {
225 None
226 };
227
228 if exp_avg.len() != size || exp_avg_sq.len() != size {
230 return Err(TrustformersError::tensor_op_error(
231 "EVA buffer size mismatch",
232 "EVA::update",
233 ));
234 }
235
236 if let Some(ref va) = var_adapt {
237 if va.len() != size {
238 return Err(TrustformersError::tensor_op_error(
239 "EVA variance adaptation buffer size mismatch",
240 "EVA::update",
241 ));
242 }
243 }
244
245 let bias_correction1 = if self.config.bias_correction {
247 1.0 - self.config.beta1.powi(self.step_count as i32)
248 } else {
249 1.0
250 };
251
252 let bias_correction2 = if self.config.bias_correction {
253 1.0 - self.config.beta2.powi(self.step_count as i32)
254 } else {
255 1.0
256 };
257
258 let grad_var = if self.config.variance_adaptation {
260 let mean_grad = grad_data.iter().sum::<f32>() / size as f32;
261 grad_data.iter().map(|&g| (g - mean_grad).powi(2)).sum::<f32>() / size as f32
262 } else {
263 0.0
264 };
265
266 let variance_factor = if self.config.variance_adaptation && self.step_count > 0 {
267 let adaptation = (grad_var + self.config.eps).sqrt();
268 let strength = self.config.adaptation_strength;
269 let factor = 1.0 / (1.0 + strength * adaptation);
270 factor.clamp(0.1, 2.0)
271 } else {
272 1.0
273 };
274
275 for (i, ((&g, p), (m, v))) in grad_data
277 .iter()
278 .zip(param.iter_mut())
279 .zip(exp_avg.iter_mut().zip(exp_avg_sq.iter_mut()))
280 .enumerate()
281 {
282 let grad_with_decay = if self.config.weight_decay > 0.0 {
284 g + self.config.weight_decay * (*p)
285 } else {
286 g
287 };
288
289 *m = self.config.beta1 * (*m) + (1.0 - self.config.beta1) * grad_with_decay;
291
292 *v = self.config.beta2 * (*v)
294 + (1.0 - self.config.beta2) * grad_with_decay * grad_with_decay;
295
296 if let Some(ref mut va) = var_adapt {
298 va[i] = 0.9 * va[i] + 0.1 * grad_with_decay.abs();
299 }
300
301 let m_hat = *m / bias_correction1;
303 let v_hat = *v / bias_correction2;
304
305 let adapted_lr = self.config.lr * variance_factor;
307
308 *p -= adapted_lr * m_hat / (v_hat.sqrt() + self.config.eps);
310 }
311
312 Ok(())
313 },
314 _ => Err(TrustformersError::tensor_op_error(
315 "EVA optimizer only supports F32 tensors",
316 "EVA::update",
317 )),
318 }
319 }
320
321 fn zero_grad(&mut self) {
322 }
324
325 fn step(&mut self) {
326 }
328
329 fn get_lr(&self) -> f32 {
330 self.config.lr
331 }
332
333 fn set_lr(&mut self, lr: f32) {
334 self.config.lr = lr;
335 }
336}
337
338impl StatefulOptimizer for EVA {
339 type Config = EVAConfig;
340 type State = OptimizerState;
341
342 fn state(&self) -> &OptimizerState {
343 &self.state
344 }
345
346 fn state_mut(&mut self) -> &mut OptimizerState {
347 &mut self.state
348 }
349
350 fn config(&self) -> &Self::Config {
351 &self.config
352 }
353
354 fn memory_usage(&self) -> StateMemoryStats {
355 self.memory_stats()
356 }
357
358 fn reset_state(&mut self) {
359 self.exp_avg.clear();
360 self.exp_avg_sq.clear();
361 self.var_adaptation.clear();
362 self.step_count = 0;
363 self.state = OptimizerState::new();
364 }
365
366 fn num_parameters(&self) -> usize {
367 self.exp_avg.values().map(|v| v.len()).sum()
368 }
369
370 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
371 let mut dict = HashMap::new();
372
373 for (key, value) in &self.exp_avg {
374 dict.insert(format!("exp_avg_{}", key), Tensor::new(value.clone())?);
375 }
376
377 for (key, value) in &self.exp_avg_sq {
378 dict.insert(format!("exp_avg_sq_{}", key), Tensor::new(value.clone())?);
379 }
380
381 if self.config.variance_adaptation {
382 for (key, value) in &self.var_adaptation {
383 dict.insert(
384 format!("var_adaptation_{}", key),
385 Tensor::new(value.clone())?,
386 );
387 }
388 }
389
390 dict.insert(
391 "step_count".to_string(),
392 Tensor::new(vec![self.step_count as f32])?,
393 );
394
395 Ok(dict)
396 }
397
398 fn load_state_dict(&mut self, state_dict: HashMap<String, Tensor>) -> Result<()> {
399 if let Some(Tensor::F32(data)) = state_dict.get("step_count") {
401 if !data.is_empty() {
402 self.step_count = data[0] as usize;
403 }
404 }
405
406 for (key, value) in &state_dict {
408 if let Some(param_key) = key.strip_prefix("exp_avg_") {
409 if let Tensor::F32(data) = value {
410 self.exp_avg.insert(
411 param_key.to_string(),
412 data.as_slice()
413 .ok_or_else(|| {
414 TrustformersError::invalid_state(
415 "F32 tensor should have valid slice".to_string(),
416 )
417 })?
418 .to_vec(),
419 );
420 }
421 }
422 }
423
424 for (key, value) in &state_dict {
426 if let Some(param_key) = key.strip_prefix("exp_avg_sq_") {
427 if let Tensor::F32(data) = value {
428 self.exp_avg_sq.insert(
429 param_key.to_string(),
430 data.as_slice()
431 .ok_or_else(|| {
432 TrustformersError::invalid_state(
433 "F32 tensor should have valid slice".to_string(),
434 )
435 })?
436 .to_vec(),
437 );
438 }
439 }
440 }
441
442 if self.config.variance_adaptation {
444 for (key, value) in &state_dict {
445 if let Some(param_key) = key.strip_prefix("var_adaptation_") {
446 if let Tensor::F32(data) = value {
447 self.var_adaptation.insert(
448 param_key.to_string(),
449 data.as_slice()
450 .ok_or_else(|| {
451 TrustformersError::invalid_state(
452 "F32 tensor should have valid slice".to_string(),
453 )
454 })?
455 .to_vec(),
456 );
457 }
458 }
459 }
460 }
461
462 Ok(())
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469 use trustformers_core::tensor::Tensor;
470
471 #[test]
472 fn test_eva_creation() {
473 let optimizer = EVA::new(1e-3, 0.9, 0.999, 1e-8, 0.01, true);
474 assert_eq!(optimizer.get_lr(), 1e-3);
475 assert_eq!(optimizer.config().beta1, 0.9);
476 assert_eq!(optimizer.config().beta2, 0.999);
477 assert_eq!(optimizer.config().eps, 1e-8);
478 assert_eq!(optimizer.config().weight_decay, 0.01);
479 assert!(optimizer.config().variance_adaptation);
480 }
481
482 #[test]
483 fn test_eva_adamw_like() {
484 let optimizer = EVA::adamw_like(1e-3, 0.01);
485 assert_eq!(optimizer.get_lr(), 1e-3);
486 assert_eq!(optimizer.config().weight_decay, 0.01);
487 assert!(optimizer.config().variance_adaptation);
488 }
489
490 #[test]
491 fn test_eva_no_variance_adaptation() {
492 let optimizer = EVA::no_variance_adaptation(1e-3, 0.9, 0.999, 1e-8);
493 assert_eq!(optimizer.get_lr(), 1e-3);
494 assert!(!optimizer.config().variance_adaptation);
495 }
496
497 #[test]
498 fn test_eva_lr_setter() {
499 let mut optimizer = EVA::new(1e-3, 0.9, 0.999, 1e-8, 0.01, true);
500 optimizer.set_lr(2e-3);
501 assert_eq!(optimizer.get_lr(), 2e-3);
502 }
503
504 #[test]
505 fn test_eva_memory_stats() {
506 let optimizer = EVA::new(1e-3, 0.9, 0.999, 1e-8, 0.01, true);
507 let stats = optimizer.memory_stats();
508 assert_eq!(stats.num_parameters, 0);
509 assert_eq!(stats.total_bytes, 0);
510 }
511
512 #[test]
513 fn test_eva_variance_adaptation() {
514 let optimizer = EVA::new(1e-3, 0.9, 0.999, 1e-8, 0.01, true);
515 let factor = optimizer.compute_variance_adaptation(0.1, 1);
516 assert!(factor > 0.1 && factor < 2.0);
517 }
518
519 #[test]
520 fn test_eva_state_dict() {
521 let optimizer = EVA::new(1e-3, 0.9, 0.999, 1e-8, 0.01, true);
522 let state_dict = optimizer.state_dict();
523 assert!(state_dict.expect("Operation failed in test").contains_key("step_count"));
524 }
525
526 #[test]
527 fn test_eva_load_state_dict() {
528 let mut optimizer = EVA::new(1e-3, 0.9, 0.999, 1e-8, 0.01, true);
529 let mut state_dict = HashMap::new();
530 state_dict.insert(
531 "step_count".to_string(),
532 Tensor::new(vec![10.0]).expect("Failed to create tensor"),
533 );
534
535 optimizer.load_state_dict(state_dict).expect("Failed to load state dict");
536 assert_eq!(optimizer.step_count, 10);
537 }
538}