1#![allow(dead_code)]
17
18use crate::common::{BiasCorrection, ParameterUpdate};
19use std::collections::HashMap;
20use trustformers_core::errors::{Result, TrustformersError};
21use trustformers_core::tensor::Tensor;
22use trustformers_core::traits::Optimizer;
23
24#[derive(Debug, Clone)]
26pub struct CacheConfig {
27 pub l1_cache_size: usize,
29 pub l2_cache_size: usize,
31 pub l3_cache_size: usize,
33 pub cache_line_size: usize,
35 pub block_size: usize,
37 pub enable_prefetching: bool,
39 pub prefetch_distance: usize,
41}
42
43impl Default for CacheConfig {
44 fn default() -> Self {
45 Self {
46 l1_cache_size: 32 * 1024, l2_cache_size: 256 * 1024, l3_cache_size: 8 * 1024 * 1024, cache_line_size: 64, block_size: 1024, enable_prefetching: true,
52 prefetch_distance: 4,
53 }
54 }
55}
56
57impl CacheConfig {
58 pub fn detect_system() -> Self {
60 Self::default()
63 }
64
65 pub fn l1_optimized() -> Self {
67 Self {
68 block_size: 512, ..Default::default()
70 }
71 }
72
73 pub fn l2_optimized() -> Self {
75 Self {
76 block_size: 2048, ..Default::default()
78 }
79 }
80
81 pub fn l3_optimized() -> Self {
83 Self {
84 block_size: 8192, ..Default::default()
86 }
87 }
88
89 pub fn optimal_block_size_for_arrays(&self, num_arrays: usize) -> usize {
91 let available_cache = self.l2_cache_size / num_arrays;
93 let elements_per_cache = available_cache / std::mem::size_of::<f32>();
94
95 let mut block_size = 64;
97 while block_size * 2 <= elements_per_cache && block_size < 16384 {
98 block_size *= 2;
99 }
100
101 block_size.min(self.block_size)
102 }
103}
104
105#[derive(Debug)]
110pub struct CacheFriendlyState {
111 pub interleaved_buffers: HashMap<usize, Vec<f32>>,
114 pub param_metadata: HashMap<usize, ParameterMetadata>,
116 pub step: usize,
118 pub cache_config: CacheConfig,
120}
121
122#[derive(Debug, Clone)]
124pub struct ParameterMetadata {
125 pub offset: usize,
127 pub size: usize,
129 pub block_size: usize,
131 pub last_access: usize,
133}
134
135impl CacheFriendlyState {
136 pub fn new(cache_config: CacheConfig) -> Self {
138 Self {
139 interleaved_buffers: HashMap::new(),
140 param_metadata: HashMap::new(),
141 step: 0,
142 cache_config,
143 }
144 }
145
146 pub fn allocate_parameter(&mut self, param_id: usize, size: usize) -> Result<()> {
148 let buffer_size = size * 2; let buffer = vec![0.0; buffer_size];
152
153 let metadata = ParameterMetadata {
154 offset: 0,
155 size,
156 block_size: self.cache_config.optimal_block_size_for_arrays(3), last_access: self.step,
158 };
159
160 self.interleaved_buffers.insert(param_id, buffer);
161 self.param_metadata.insert(param_id, metadata);
162
163 Ok(())
164 }
165
166 pub fn get_interleaved_buffer_mut(&mut self, param_id: usize) -> Option<(&mut [f32], usize)> {
168 if let (Some(buffer), Some(metadata)) = (
169 self.interleaved_buffers.get_mut(¶m_id),
170 self.param_metadata.get_mut(¶m_id),
171 ) {
172 metadata.last_access = self.step;
173 Some((buffer.as_mut_slice(), metadata.size))
174 } else {
175 None
176 }
177 }
178
179 pub fn get_buffers_mut(&mut self, param_id: usize) -> Option<(Vec<f32>, Vec<f32>)> {
182 if let (Some(buffer), Some(metadata)) = (
183 self.interleaved_buffers.get(¶m_id),
184 self.param_metadata.get_mut(¶m_id),
185 ) {
186 metadata.last_access = self.step;
187
188 let mut momentum = Vec::with_capacity(metadata.size);
190 let mut variance = Vec::with_capacity(metadata.size);
191
192 for i in 0..metadata.size {
193 momentum.push(buffer[i * 2]);
194 variance.push(buffer[i * 2 + 1]);
195 }
196
197 Some((momentum, variance))
198 } else {
199 None
200 }
201 }
202
203 pub fn update_buffers(
205 &mut self,
206 param_id: usize,
207 momentum: &[f32],
208 variance: &[f32],
209 ) -> Result<()> {
210 if let Some(buffer) = self.interleaved_buffers.get_mut(¶m_id) {
211 if momentum.len() != variance.len() || momentum.len() * 2 != buffer.len() {
212 return Err(TrustformersError::tensor_op_error(
213 "Buffer size mismatch",
214 "update_buffers",
215 ));
216 }
217
218 for i in 0..momentum.len() {
220 buffer[i * 2] = momentum[i];
221 buffer[i * 2 + 1] = variance[i];
222 }
223
224 Ok(())
225 } else {
226 Err(TrustformersError::tensor_op_error(
227 "Parameter not found",
228 "update_buffers",
229 ))
230 }
231 }
232
233 pub fn garbage_collect(&mut self, access_threshold: usize) {
235 let current_step = self.step;
236 let stale_params: Vec<usize> = self
237 .param_metadata
238 .iter()
239 .filter(|(_, metadata)| current_step - metadata.last_access > access_threshold)
240 .map(|(id, _)| *id)
241 .collect();
242
243 for param_id in stale_params {
244 self.interleaved_buffers.remove(¶m_id);
245 self.param_metadata.remove(¶m_id);
246 }
247 }
248}
249
250#[derive(Debug)]
255pub struct CacheFriendlyAdam {
256 lr: f32,
258 betas: (f32, f32),
260 eps: f32,
262 weight_decay: f32,
264 state: CacheFriendlyState,
266}
267
268impl CacheFriendlyAdam {
269 pub fn new(lr: f32, betas: (f32, f32), eps: f32, weight_decay: f32) -> Self {
271 Self::with_cache_config(lr, betas, eps, weight_decay, CacheConfig::default())
272 }
273
274 pub fn with_cache_config(
276 lr: f32,
277 betas: (f32, f32),
278 eps: f32,
279 weight_decay: f32,
280 cache_config: CacheConfig,
281 ) -> Self {
282 Self {
283 lr,
284 betas,
285 eps,
286 weight_decay,
287 state: CacheFriendlyState::new(cache_config),
288 }
289 }
290
291 fn update_parameter_blocked(
293 &mut self,
294 param: &mut [f32],
295 grad: &[f32],
296 param_id: String,
297 ) -> Result<()> {
298 let numeric_id = param_id.as_ptr() as usize;
300 self.update_parameter_blocked_fast(param, grad, numeric_id)
301 }
302
303 fn update_parameter_blocked_fast(
305 &mut self,
306 param: &mut [f32],
307 grad: &[f32],
308 param_id: usize,
309 ) -> Result<()> {
310 let size = param.len();
311 if grad.len() != size {
312 return Err(TrustformersError::tensor_op_error(
313 "Parameter and gradient size mismatch",
314 "update_parameter_blocked_fast",
315 ));
316 }
317
318 if !self.state.param_metadata.contains_key(¶m_id) {
320 self.state.allocate_parameter(param_id, size)?;
321 } else {
322 let current_size =
324 self.state.param_metadata.get(¶m_id).map(|meta| meta.size).unwrap_or(0);
325 if current_size != size {
326 self.state.allocate_parameter(param_id, size)?;
327 }
328 }
329
330 let step = self.state.step + 1;
332 let block_size = self
333 .state
334 .param_metadata
335 .get(¶m_id)
336 .map(|meta| meta.block_size)
337 .unwrap_or(1024);
338 let _enable_prefetching = self.state.cache_config.enable_prefetching;
339
340 let (bias_correction1, bias_correction2) =
341 BiasCorrection::compute_adam_corrections(self.betas.0, self.betas.1, step);
342
343 let (interleaved_buffer, _param_size) =
345 self.state.get_interleaved_buffer_mut(param_id).ok_or_else(|| {
346 TrustformersError::tensor_op_error(
347 "Failed to get parameter buffers",
348 "update_parameter_blocked_fast",
349 )
350 })?;
351
352 if size < 4096 {
354 for i in 0..size {
356 let grad_val = grad[i] + self.weight_decay * param[i];
357
358 let momentum_idx = i * 2;
360 let variance_idx = i * 2 + 1;
361
362 interleaved_buffer[momentum_idx] = self.betas.0 * interleaved_buffer[momentum_idx]
364 + (1.0 - self.betas.0) * grad_val;
365 interleaved_buffer[variance_idx] = self.betas.1 * interleaved_buffer[variance_idx]
366 + (1.0 - self.betas.1) * grad_val * grad_val;
367
368 let m_hat = interleaved_buffer[momentum_idx] / bias_correction1;
370 let v_hat = interleaved_buffer[variance_idx] / bias_correction2;
371
372 param[i] -= self.lr * m_hat / (v_hat.sqrt() + self.eps);
373 }
374 } else {
375 let num_blocks = size.div_ceil(block_size);
377
378 for block_idx in 0..num_blocks {
379 let start = block_idx * block_size;
380 let end = (start + block_size).min(size);
381
382 for i in start..end {
385 let grad_val = grad[i] + self.weight_decay * param[i];
386
387 let momentum_idx = i * 2;
389 let variance_idx = i * 2 + 1;
390
391 interleaved_buffer[momentum_idx] = self.betas.0
392 * interleaved_buffer[momentum_idx]
393 + (1.0 - self.betas.0) * grad_val;
394 interleaved_buffer[variance_idx] = self.betas.1
395 * interleaved_buffer[variance_idx]
396 + (1.0 - self.betas.1) * grad_val * grad_val;
397
398 let m_hat = interleaved_buffer[momentum_idx] / bias_correction1;
399 let v_hat = interleaved_buffer[variance_idx] / bias_correction2;
400
401 param[i] -= self.lr * m_hat / (v_hat.sqrt() + self.eps);
402 }
403 }
404 }
405
406 Ok(())
408 }
409
410 #[inline]
412 fn process_block_fused(
413 &self,
414 param_block: &mut [f32],
415 grad_block: &[f32],
416 momentum_block: &mut [f32],
417 variance_block: &mut [f32],
418 bias_correction1: f32,
419 bias_correction2: f32,
420 ) {
421 for i in 0..param_block.len() {
423 let grad_val = grad_block[i] + self.weight_decay * param_block[i];
424
425 ParameterUpdate::update_ema(&mut momentum_block[i], grad_val, self.betas.0);
427 ParameterUpdate::update_ema(&mut variance_block[i], grad_val * grad_val, self.betas.1);
428
429 let m_hat = momentum_block[i] / bias_correction1;
431 let v_hat = variance_block[i] / bias_correction2;
432
433 ParameterUpdate::adam_update(&mut param_block[i], self.lr, m_hat, v_hat, self.eps);
434 }
435 }
436
437 #[inline]
439 fn prefetch_block(&self, block: &[f32]) {
440 if block.is_empty() {
442 return;
443 }
444
445 let ptr = block.as_ptr();
447
448 #[cfg(target_arch = "x86_64")]
450 {
451 unsafe {
455 std::arch::x86_64::_mm_prefetch(ptr as *const i8, std::arch::x86_64::_MM_HINT_T0);
456
457 if block.len() > 16 {
459 let mid_ptr = ptr.wrapping_add(block.len() / 2);
461 std::arch::x86_64::_mm_prefetch(
462 mid_ptr as *const i8,
463 std::arch::x86_64::_MM_HINT_T0,
464 );
465 }
466 }
467 }
468
469 #[cfg(target_arch = "aarch64")]
470 {
471 unsafe {
473 std::arch::asm!(
474 "prfm pldl1keep, [{}]",
475 in(reg) ptr,
476 options(nostack, preserves_flags)
477 );
478 }
479 }
480
481 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
482 {
483 let _ = unsafe { std::ptr::read_volatile(ptr) };
487 }
488 }
489
490 pub fn cache_stats(&self) -> CacheStats {
492 let buffer_memory: usize = self
493 .state
494 .interleaved_buffers
495 .values()
496 .map(|buffer| buffer.len() * std::mem::size_of::<f32>())
497 .sum();
498
499 let num_params = self.state.param_metadata.len();
500 let total_elements: usize = self.state.param_metadata.values().map(|meta| meta.size).sum();
501
502 CacheStats {
503 buffer_memory_bytes: buffer_memory,
504 num_parameters: num_params,
505 total_elements,
506 cache_config: self.state.cache_config.clone(),
507 estimated_l1_utilization: self
508 .estimate_cache_utilization(buffer_memory, self.state.cache_config.l1_cache_size),
509 estimated_l2_utilization: self
510 .estimate_cache_utilization(buffer_memory, self.state.cache_config.l2_cache_size),
511 }
512 }
513
514 fn estimate_cache_utilization(&self, working_set_size: usize, cache_size: usize) -> f32 {
516 if cache_size == 0 {
517 return 1.0;
518 }
519 (working_set_size as f32 / cache_size as f32).min(1.0)
520 }
521
522 pub fn cleanup_unused_params(&mut self, steps_threshold: usize) {
524 self.state.garbage_collect(steps_threshold);
525 }
526}
527
528impl Optimizer for CacheFriendlyAdam {
529 fn update(&mut self, parameter: &mut Tensor, grad: &Tensor) -> Result<()> {
530 match (parameter, grad) {
531 (Tensor::F32(param), Tensor::F32(grad_arr)) => {
532 let param_id = param.as_ptr() as usize;
534 let param_slice = param.as_slice_mut().ok_or_else(|| {
535 TrustformersError::tensor_op_error(
536 "Parameter tensor is not contiguous",
537 "update",
538 )
539 })?;
540 let grad_slice = grad_arr.as_slice().ok_or_else(|| {
541 TrustformersError::tensor_op_error(
542 "Gradient tensor is not contiguous",
543 "update",
544 )
545 })?;
546 self.update_parameter_blocked_fast(param_slice, grad_slice, param_id)
547 },
548 _ => Err(TrustformersError::tensor_op_error(
549 "Unsupported tensor types for CacheFriendlyAdam",
550 "update",
551 )),
552 }
553 }
554
555 fn zero_grad(&mut self) {
556 }
558
559 fn step(&mut self) {
560 self.state.step += 1;
561 }
562
563 fn get_lr(&self) -> f32 {
564 self.lr
565 }
566
567 fn set_lr(&mut self, lr: f32) {
568 self.lr = lr;
569 }
570}
571
572#[derive(Debug, Clone)]
574pub struct CacheStats {
575 pub buffer_memory_bytes: usize,
577 pub num_parameters: usize,
579 pub total_elements: usize,
581 pub cache_config: CacheConfig,
583 pub estimated_l1_utilization: f32,
585 pub estimated_l2_utilization: f32,
587}
588
589impl CacheStats {
590 pub fn optimization_suggestions(&self) -> Vec<String> {
592 let mut suggestions = Vec::new();
593
594 if self.estimated_l1_utilization > 0.8 {
595 suggestions.push("Consider reducing block size for better L1 cache fit".to_string());
596 }
597
598 if self.estimated_l2_utilization > 0.9 {
599 suggestions
600 .push("Working set exceeds L2 cache; consider parameter partitioning".to_string());
601 }
602
603 if self.cache_config.block_size > 8192 {
604 suggestions.push("Large block size may cause cache thrashing".to_string());
605 }
606
607 if !self.cache_config.enable_prefetching {
608 suggestions.push("Enable prefetching for potential performance gains".to_string());
609 }
610
611 if suggestions.is_empty() {
612 suggestions.push("Cache utilization appears optimal".to_string());
613 }
614
615 suggestions
616 }
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 #[test]
624 fn test_cache_config_creation() {
625 let config = CacheConfig::default();
626 assert_eq!(config.l1_cache_size, 32 * 1024);
627 assert_eq!(config.cache_line_size, 64);
628 assert!(config.enable_prefetching);
629
630 let l1_config = CacheConfig::l1_optimized();
631 assert_eq!(l1_config.block_size, 512);
632 }
633
634 #[test]
635 fn test_optimal_block_size() {
636 let config = CacheConfig::default();
637 let block_size = config.optimal_block_size_for_arrays(3);
638 assert!(block_size > 0);
639 assert!(block_size <= config.block_size);
640 assert_eq!(block_size & (block_size - 1), 0); }
642
643 #[test]
644 fn test_cache_friendly_state() {
645 let mut state = CacheFriendlyState::new(CacheConfig::default());
646
647 let param_id = 12345usize;
649 state.allocate_parameter(param_id, 100).expect("Operation failed in test");
650
651 assert!(state.param_metadata.contains_key(¶m_id));
652 assert!(state.interleaved_buffers.contains_key(¶m_id));
653
654 let (momentum, variance) =
656 state.get_buffers_mut(param_id).expect("Operation failed in test");
657 assert_eq!(momentum.len(), 100);
658 assert_eq!(variance.len(), 100);
659 }
660
661 #[test]
662 fn test_cache_friendly_adam() {
663 let optimizer = CacheFriendlyAdam::new(1e-3, (0.9, 0.999), 1e-8, 0.01);
664 assert_eq!(optimizer.get_lr(), 1e-3);
665 assert_eq!(optimizer.betas, (0.9, 0.999));
666 assert_eq!(optimizer.eps, 1e-8);
667 assert_eq!(optimizer.weight_decay, 0.01);
668 }
669
670 #[test]
671 fn test_cache_stats() {
672 let optimizer = CacheFriendlyAdam::new(1e-3, (0.9, 0.999), 1e-8, 0.01);
673 let stats = optimizer.cache_stats();
674
675 assert_eq!(stats.num_parameters, 0);
676 assert_eq!(stats.total_elements, 0);
677 assert_eq!(stats.buffer_memory_bytes, 0);
678
679 let suggestions = stats.optimization_suggestions();
680 assert!(!suggestions.is_empty());
681 }
682
683 #[test]
684 fn test_cache_utilization_estimation() {
685 let optimizer = CacheFriendlyAdam::new(1e-3, (0.9, 0.999), 1e-8, 0.01);
686
687 let utilization = optimizer.estimate_cache_utilization(16 * 1024, 32 * 1024);
688 assert_eq!(utilization, 0.5);
689
690 let over_utilization = optimizer.estimate_cache_utilization(64 * 1024, 32 * 1024);
691 assert_eq!(over_utilization, 1.0);
692 }
693
694 #[test]
695 fn test_garbage_collection() {
696 let mut state = CacheFriendlyState::new(CacheConfig::default());
697
698 let param1_id = 11111usize;
700 let param2_id = 22222usize;
701 state.allocate_parameter(param1_id, 100).expect("Operation failed in test");
702 state.allocate_parameter(param2_id, 200).expect("Operation failed in test");
703
704 state.step = 1000;
706
707 state.get_buffers_mut(param1_id);
709
710 state.garbage_collect(10);
712
713 assert!(state.param_metadata.contains_key(¶m1_id));
715 assert!(!state.param_metadata.contains_key(¶m2_id));
716 }
717}