1use scirs2_core::ndarray::Array1;
8use scirs2_core::numeric::Float;
9use std::collections::VecDeque;
10use std::sync::{
11 atomic::{AtomicUsize, Ordering},
12 Arc, Mutex, MutexGuard, PoisonError,
13};
14use std::time::{Duration, Instant};
15
16use crate::error::{OptimError, Result};
17use crate::optimizers::Optimizer;
18
19#[cfg(test)]
20mod regression_tests;
21
22fn lock_recovered<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
29 mutex.lock().unwrap_or_else(PoisonError::into_inner)
30}
31
32#[derive(Debug, Clone)]
34pub struct LowLatencyConfig {
35 pub target_latency_us: u64,
37
38 pub max_latency_us: u64,
40
41 pub enable_precomputation: bool,
43
44 pub precomputation_buffer_size: usize,
46
47 pub enable_lock_free: bool,
49
50 pub use_approximations: bool,
52
53 pub approximation_tolerance: f64,
55
56 pub enable_simd: bool,
58
59 pub batch_threshold: usize,
61
62 pub enable_zero_copy: bool,
64
65 pub memory_pool_size: usize,
67
68 pub enable_quantization: bool,
70
71 pub quantization_bits: u8,
73}
74
75impl Default for LowLatencyConfig {
76 fn default() -> Self {
77 Self {
78 target_latency_us: 100, max_latency_us: 1000, enable_precomputation: true,
81 precomputation_buffer_size: 64,
82 enable_lock_free: true,
83 use_approximations: true,
84 approximation_tolerance: 0.01,
85 enable_simd: true,
86 batch_threshold: 8,
87 enable_zero_copy: true,
88 memory_pool_size: 1024 * 1024, enable_quantization: false,
90 quantization_bits: 8,
91 }
92 }
93}
94
95pub struct LowLatencyOptimizer<O, A>
97where
98 A: Float + Send + Sync + scirs2_core::ndarray::ScalarOperand + std::fmt::Debug,
99 O: Optimizer<A, scirs2_core::ndarray::Ix1> + Send + Sync,
100{
101 base_optimizer: Arc<Mutex<O>>,
103
104 config: LowLatencyConfig,
106
107 parameters: Option<Array1<A>>,
118
119 precomputation_engine: Option<PrecomputationEngine<A>>,
121
122 update_buffer: LockFreeBuffer<A>,
124
125 memory_pool: FastMemoryPool<A>,
127
128 simd_processor: SIMDProcessor<A>,
130
131 quantizer: Option<GradientQuantizer<A>>,
133
134 perf_monitor: LatencyMonitor,
136
137 approximation_controller: ApproximationController<A>,
139
140 step_counter: AtomicUsize,
142}
143
144struct PrecomputationEngine<A: Float + Send + Sync> {
146 precomputed_updates: VecDeque<PrecomputedUpdate<A>>,
148
149 gradient_predictor: GradientPredictor<A>,
151
152 max_buffer_size: usize,
154
155 hits: usize,
157
158 misses: usize,
160
161 min_confidence: A,
170}
171
172#[derive(Debug, Clone)]
174struct PrecomputedUpdate<A: Float + Send + Sync> {
175 gradient: Array1<A>,
177
178 update: Array1<A>,
180
181 valid_until: Instant,
183
184 confidence: A,
186}
187
188struct LockFreeBuffer<A: Float + Send + Sync> {
195 buffer: Vec<Option<Array1<A>>>,
197
198 write_index: AtomicUsize,
200
201 read_index: AtomicUsize,
203
204 capacity: usize,
206}
207
208struct FastMemoryPool<A> {
217 free_blocks: Mutex<Vec<Vec<A>>>,
219
220 elements_per_block: usize,
222
223 total_blocks: usize,
225
226 checked_out: AtomicUsize,
228
229 peak_checked_out: AtomicUsize,
231
232 misses: AtomicUsize,
234}
235
236struct SIMDProcessor<A: Float + Send + Sync> {
238 enabled: bool,
240
241 vector_width: usize,
243
244 _element: std::marker::PhantomData<A>,
246}
247
248struct GradientQuantizer<A: Float + Send + Sync> {
250 bits: u8,
252
253 scale: A,
255
256 zero_point: A,
258
259 error_accumulator: Option<Array1<A>>,
261}
262
263#[derive(Debug)]
265struct LatencyMonitor {
266 latency_samples: VecDeque<Duration>,
268
269 maxsamples: usize,
271
272 p50_latency: Duration,
274 p95_latency: Duration,
275 p99_latency: Duration,
276
277 violations: usize,
279
280 total_operations: usize,
282}
283
284const PERFORMANCE_WINDOW_AGE: Duration = Duration::from_secs(30);
286
287const PERFORMANCE_WINDOW_LEN: usize = 100;
289
290struct ApproximationController<A: Float + Send + Sync> {
292 approximation_level: A,
294
295 performance_history: VecDeque<PerformancePoint<A>>,
297
298 adaptation_rate: A,
300
301 targetlatency: Duration,
303}
304
305#[derive(Debug, Clone)]
307struct PerformancePoint<A: Float + Send + Sync> {
308 latency: Duration,
310
311 accuracy: A,
313
314 timestamp: Instant,
316}
317
318struct GradientPredictor<A: Float + Send + Sync> {
320 gradient_history: VecDeque<Array1<A>>,
322
323 trend_weights: Option<Array1<A>>,
325
326 windowsize: usize,
328
329 confidence: Option<A>,
334
335 pending_prediction: Option<Array1<A>>,
337}
338
339impl<O, A> LowLatencyOptimizer<O, A>
340where
341 A: Float
342 + Send
343 + Sync
344 + Default
345 + Clone
346 + std::fmt::Debug
347 + scirs2_core::ndarray::ScalarOperand
348 + 'static
349 + std::iter::Sum,
350 O: Optimizer<A, scirs2_core::ndarray::Ix1> + Send + Sync + 'static,
351{
352 pub fn new(_baseoptimizer: O, config: LowLatencyConfig) -> Result<Self> {
354 let base_optimizer = Arc::new(Mutex::new(_baseoptimizer));
355
356 let precomputation_engine = if config.enable_precomputation {
357 Some(PrecomputationEngine::new(config.precomputation_buffer_size))
358 } else {
359 None
360 };
361
362 let update_buffer = LockFreeBuffer::new(config.precomputation_buffer_size);
363 let memory_pool = FastMemoryPool::new(config.memory_pool_size, 4096)?; let simd_processor = SIMDProcessor::new(config.enable_simd, config.batch_threshold);
365
366 let quantizer = if config.enable_quantization {
367 Some(GradientQuantizer::new(config.quantization_bits))
368 } else {
369 None
370 };
371
372 let perf_monitor = LatencyMonitor::new(1000); let approximation_controller =
374 ApproximationController::new(Duration::from_micros(config.target_latency_us));
375
376 Ok(Self {
377 base_optimizer,
378 config,
379 parameters: None,
380 precomputation_engine,
381 update_buffer,
382 memory_pool,
383 simd_processor,
384 quantizer,
385 perf_monitor,
386 approximation_controller,
387 step_counter: AtomicUsize::new(0),
388 })
389 }
390
391 pub fn set_parameters(&mut self, parameters: Array1<A>) {
393 self.parameters = Some(parameters);
394 }
395
396 pub fn set_precomputation_min_confidence(&mut self, min_confidence: A) {
403 if let Some(precomp) = self.precomputation_engine.as_mut() {
404 precomp.set_min_confidence(min_confidence);
405 }
406 }
407
408 pub fn parameters(&self) -> Option<&Array1<A>> {
409 self.parameters.as_ref()
410 }
411
412 pub fn low_latency_step(&mut self, gradient: &Array1<A>) -> Result<Array1<A>> {
414 let start_time = Instant::now();
415
416 if gradient.is_empty() {
417 return Err(OptimError::DimensionMismatch(
418 "low_latency_step received an empty gradient".to_string(),
419 ));
420 }
421
422 let previous_params = self.parameters.clone();
423 let learning_rate = self.base_learning_rate();
424 let tolerance = self.config.approximation_tolerance.max(0.0);
425
426 let served = self
431 .precomputation_engine
432 .as_mut()
433 .and_then(|precomp| precomp.try_get_precomputed(gradient, tolerance));
434 if let Some(precomputed) = served {
435 let update = precomputed.update;
436 self.parameters = Some(update.clone());
437 let latency = start_time.elapsed();
438 self.perf_monitor.record_latency(latency);
439 if self.config.enable_lock_free {
440 self.update_buffer.push(update.clone());
441 }
442 let validity = Duration::from_micros(self.config.max_latency_us.max(1));
443 if let Some(ref mut precomp) = self.precomputation_engine {
444 precomp.start_precomputation(gradient, &update, learning_rate, validity);
445 }
446 self.step_counter.fetch_add(1, Ordering::Relaxed);
447 return Ok(update);
448 }
449
450 let quantized = match self.quantizer.as_mut() {
454 Some(quantizer) => Some(quantizer.quantize(gradient)?),
455 None if self.config.enable_zero_copy => None,
456 None => Some(gradient.clone()),
457 };
458 let processed_gradient: &Array1<A> = quantized.as_ref().unwrap_or(gradient);
459
460 let approximation_level = self.approximation_controller.get_approximation_level();
462 let use_approximation = self.config.use_approximations && approximation_level > A::zero();
463 let update = if use_approximation {
464 let simplified = self.simplify_gradient(processed_gradient, approximation_level)?;
465 self.fast_path_update(&simplified, learning_rate)?
466 } else {
467 self.exact_update(processed_gradient)?
468 };
469
470 let latency = start_time.elapsed();
471
472 let accuracy = Self::estimate_accuracy(previous_params.as_ref(), &update, gradient);
474 self.approximation_controller
475 .record_performance(latency, approximation_level, accuracy);
476 self.perf_monitor.record_latency(latency);
477
478 if latency.as_micros() as u64 > self.config.max_latency_us {
480 self.handle_latency_violation(latency)?;
481 }
482
483 if self.config.enable_lock_free {
485 self.update_buffer.push(update.clone());
486 }
487
488 let validity = Duration::from_micros(self.config.max_latency_us.max(1));
491 if let Some(ref mut precomp) = self.precomputation_engine {
492 precomp.start_precomputation(gradient, &update, learning_rate, validity);
493 }
494
495 self.parameters = Some(update.clone());
496 self.step_counter.fetch_add(1, Ordering::Relaxed);
497 Ok(update)
498 }
499
500 fn base_learning_rate(&self) -> A {
502 lock_recovered(&self.base_optimizer).get_learning_rate()
503 }
504
505 fn current_parameters(&self, len: usize) -> Result<Array1<A>> {
507 match self.parameters.as_ref() {
508 Some(params) if params.len() == len => Ok(params.clone()),
509 Some(params) => Err(OptimError::DimensionMismatch(format!(
510 "gradient has {} elements but the tracked parameters have {}",
511 len,
512 params.len()
513 ))),
514 None => Ok(Array1::zeros(len)),
515 }
516 }
517
518 fn exact_update(&mut self, gradient: &Array1<A>) -> Result<Array1<A>> {
520 let current_params = self.current_parameters(gradient.len())?;
521 let mut optimizer = lock_recovered(&self.base_optimizer);
522 optimizer.step(¤t_params, gradient)
523 }
524
525 fn fast_path_update(&mut self, gradient: &Array1<A>, learning_rate: A) -> Result<Array1<A>> {
534 if !self.simd_processor.is_active(gradient.len()) {
535 return self.exact_update(gradient);
536 }
537 let mut params = self.current_parameters(gradient.len())?;
538 self.simd_processor
539 .apply_scaled_subtract(&mut params, gradient, learning_rate);
540 Ok(params)
541 }
542
543 fn simplify_gradient(&self, gradient: &Array1<A>, level: A) -> Result<Array1<A>> {
545 let n = gradient.len();
546 if n == 0 {
547 return Ok(gradient.clone());
548 }
549
550 let sparsity_ratio = level.to_f64().unwrap_or(0.0).clamp(0.0, 1.0);
551 let keep_ratio = 1.0 - sparsity_ratio * 0.8; let keep_count = (((n as f64) * keep_ratio).round() as usize).clamp(1, n);
553 if keep_count == n {
554 return Ok(gradient.clone());
555 }
556
557 let mut magnitudes = self
561 .memory_pool
562 .acquire(n)
563 .unwrap_or_else(|| Vec::with_capacity(n));
564 magnitudes.clear();
565 magnitudes.extend(gradient.iter().map(|g| g.abs()));
566 let kth = keep_count - 1;
567 magnitudes.select_nth_unstable_by(kth, |a, b| {
568 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
569 });
570 let threshold = magnitudes[kth];
571 self.memory_pool.release(magnitudes);
572
573 let mut simplified = Array1::zeros(n);
574 let mut kept = 0usize;
575 for (i, &g) in gradient.iter().enumerate() {
576 if kept < keep_count && g.abs() >= threshold {
577 simplified[i] = g;
578 kept += 1;
579 }
580 }
581
582 Ok(simplified)
583 }
584
585 fn estimate_accuracy(
591 previous_params: Option<&Array1<A>>,
592 new_params: &Array1<A>,
593 gradient: &Array1<A>,
594 ) -> A {
595 if new_params.len() != gradient.len() {
596 return A::zero();
597 }
598
599 let zeros = Array1::zeros(new_params.len());
600 let previous = match previous_params {
601 Some(previous) if previous.len() == new_params.len() => previous,
602 _ => &zeros,
603 };
604
605 let mut dot = A::zero();
606 let mut norm_delta = A::zero();
607 let mut norm_grad = A::zero();
608 for ((&p_new, &p_old), &g) in new_params.iter().zip(previous.iter()).zip(gradient.iter()) {
609 let delta = p_new - p_old;
610 dot = dot + delta * (-g);
611 norm_delta = norm_delta + delta * delta;
612 norm_grad = norm_grad + g * g;
613 }
614
615 let norm_delta = norm_delta.sqrt();
616 let norm_grad = norm_grad.sqrt();
617 if norm_delta == A::zero() || norm_grad == A::zero() {
618 A::zero()
619 } else {
620 dot / (norm_delta * norm_grad)
621 }
622 }
623
624 fn handle_latency_violation(&mut self, latency: Duration) -> Result<()> {
626 self.perf_monitor.violations += 1;
629
630 self.approximation_controller.increase_approximation();
632
633 if !self.config.enable_quantization
636 && latency.as_micros() as u64 > self.config.max_latency_us.saturating_mul(2)
637 {
638 self.config.enable_quantization = true;
639 self.quantizer = Some(GradientQuantizer::new(self.config.quantization_bits));
640 }
641
642 Ok(())
643 }
644
645 pub fn try_pop_staged_update(&mut self) -> Option<Array1<A>> {
647 self.update_buffer.pop()
648 }
649
650 pub fn staged_update_count(&self) -> usize {
652 self.update_buffer.len()
653 }
654
655 pub fn get_performance_metrics(&self) -> LowLatencyMetrics {
657 LowLatencyMetrics {
658 avg_latency_us: self.perf_monitor.get_average_latency().as_micros() as u64,
659 p50_latency_us: self.perf_monitor.p50_latency.as_micros() as u64,
660 p95_latency_us: self.perf_monitor.p95_latency.as_micros() as u64,
661 p99_latency_us: self.perf_monitor.p99_latency.as_micros() as u64,
662 latency_violations: self.perf_monitor.violations,
663 total_operations: self.perf_monitor.total_operations,
664 current_approximation_level: self
665 .approximation_controller
666 .approximation_level
667 .to_f64()
668 .unwrap_or(0.0),
669 approximation_accuracy: self
670 .approximation_controller
671 .mean_accuracy()
672 .and_then(|value| value.to_f64()),
673 precomputation_hit_rate: self
674 .precomputation_engine
675 .as_ref()
676 .and_then(|pe| pe.hit_rate()),
677 precomputation_attempts: self
678 .precomputation_engine
679 .as_ref()
680 .map(|pe| pe.attempts())
681 .unwrap_or(0),
682 memory_efficiency: self.memory_pool.get_efficiency(),
683 memory_pool_misses: self.memory_pool.misses(),
684 }
685 }
686
687 pub fn is_meeting_latency_requirements(&self) -> bool {
689 let avg_latency = self.perf_monitor.get_average_latency().as_micros() as u64;
690 avg_latency <= self.config.target_latency_us
691 }
692}
693
694impl<A: Float + Send + Sync + std::iter::Sum> PrecomputationEngine<A> {
696 fn new(_buffersize: usize) -> Self {
697 let capacity = _buffersize.max(1);
698 Self {
699 precomputed_updates: VecDeque::with_capacity(capacity),
700 gradient_predictor: GradientPredictor::new(10), max_buffer_size: capacity,
702 hits: 0,
703 misses: 0,
704 min_confidence: A::zero(),
705 }
706 }
707
708 fn set_min_confidence(&mut self, min_confidence: A) {
710 self.min_confidence = min_confidence;
711 }
712
713 fn try_get_precomputed(
716 &mut self,
717 actual_gradient: &Array1<A>,
718 tolerance: f64,
719 ) -> Option<PrecomputedUpdate<A>> {
720 let now = Instant::now();
722 while let Some(update) = self.precomputed_updates.front() {
723 if update.valid_until <= now {
724 self.precomputed_updates.pop_front();
725 } else {
726 break;
727 }
728 }
729
730 let candidate = self.precomputed_updates.pop_front();
731 self.gradient_predictor.observe(actual_gradient);
732
733 match candidate {
734 Some(candidate)
735 if candidate.confidence >= self.min_confidence
736 && gradient_matches(&candidate.gradient, actual_gradient, tolerance) =>
737 {
738 self.hits += 1;
739 Some(candidate)
740 }
741 _ => {
742 self.misses += 1;
743 None
744 }
745 }
746 }
747
748 fn start_precomputation(
756 &mut self,
757 _observed_gradient: &Array1<A>,
758 current_params: &Array1<A>,
759 learning_rate: A,
760 validity: Duration,
761 ) {
762 let Some((predicted, confidence)) = self.gradient_predictor.predict() else {
763 return;
764 };
765 if predicted.len() != current_params.len() {
766 return;
767 }
768
769 let mut update = current_params.clone();
770 for (p, &g) in update.iter_mut().zip(predicted.iter()) {
771 *p = *p - learning_rate * g;
772 }
773
774 if self.precomputed_updates.len() >= self.max_buffer_size {
775 self.precomputed_updates.pop_front();
776 }
777 self.precomputed_updates.push_back(PrecomputedUpdate {
778 gradient: predicted,
779 update,
780 valid_until: Instant::now() + validity,
781 confidence,
782 });
783 }
784
785 fn attempts(&self) -> usize {
786 self.hits + self.misses
787 }
788
789 fn hit_rate(&self) -> Option<f64> {
791 let attempts = self.attempts();
792 if attempts == 0 {
793 None
794 } else {
795 Some(self.hits as f64 / attempts as f64)
796 }
797 }
798}
799
800fn gradient_matches<A: Float>(predicted: &Array1<A>, actual: &Array1<A>, tolerance: f64) -> bool {
803 if predicted.len() != actual.len() || predicted.is_empty() {
804 return false;
805 }
806 let mut diff_sq = A::zero();
807 let mut actual_sq = A::zero();
808 for (&p, &a) in predicted.iter().zip(actual.iter()) {
809 let d = p - a;
810 diff_sq = diff_sq + d * d;
811 actual_sq = actual_sq + a * a;
812 }
813 let diff = diff_sq.sqrt().to_f64().unwrap_or(f64::INFINITY);
814 let scale = actual_sq.sqrt().to_f64().unwrap_or(0.0);
815 if !diff.is_finite() {
816 return false;
817 }
818 if scale <= f64::EPSILON {
819 diff <= tolerance
820 } else {
821 diff / scale <= tolerance
822 }
823}
824
825impl<A: Float + Send + Sync> LockFreeBuffer<A> {
826 fn new(capacity: usize) -> Self {
827 let capacity = capacity.max(1);
828 Self {
829 buffer: vec![None; capacity],
830 write_index: AtomicUsize::new(0),
831 read_index: AtomicUsize::new(0),
832 capacity,
833 }
834 }
835
836 fn push(&mut self, value: Array1<A>) {
838 let write = self.write_index.load(Ordering::Relaxed);
839 let read = self.read_index.load(Ordering::Relaxed);
840 if write - read >= self.capacity {
841 let slot = read % self.capacity;
843 self.buffer[slot] = None;
844 self.read_index.store(read + 1, Ordering::Relaxed);
845 }
846 let slot = write % self.capacity;
847 self.buffer[slot] = Some(value);
848 self.write_index.store(write + 1, Ordering::Relaxed);
849 }
850
851 fn pop(&mut self) -> Option<Array1<A>> {
852 let read = self.read_index.load(Ordering::Relaxed);
853 if read == self.write_index.load(Ordering::Relaxed) {
854 return None;
855 }
856 let slot = read % self.capacity;
857 let value = self.buffer[slot].take();
858 self.read_index.store(read + 1, Ordering::Relaxed);
859 value
860 }
861
862 fn len(&self) -> usize {
863 self.write_index.load(Ordering::Relaxed) - self.read_index.load(Ordering::Relaxed)
864 }
865}
866
867impl<A: Float> FastMemoryPool<A> {
868 fn new(_total_size: usize, block_size_bytes: usize) -> Result<Self> {
869 let element_size = std::mem::size_of::<A>().max(1);
870 let elements_per_block = (block_size_bytes / element_size).max(1);
871 let total_blocks = _total_size / block_size_bytes.max(1);
872
873 let mut free_blocks = Vec::with_capacity(total_blocks);
874 for _ in 0..total_blocks {
875 free_blocks.push(Vec::with_capacity(elements_per_block));
876 }
877
878 Ok(Self {
879 free_blocks: Mutex::new(free_blocks),
880 elements_per_block,
881 total_blocks,
882 checked_out: AtomicUsize::new(0),
883 peak_checked_out: AtomicUsize::new(0),
884 misses: AtomicUsize::new(0),
885 })
886 }
887
888 fn acquire(&self, len: usize) -> Option<Vec<A>> {
890 if len > self.elements_per_block {
891 self.misses.fetch_add(1, Ordering::Relaxed);
892 return None;
893 }
894 let block = lock_recovered(&self.free_blocks).pop();
895 match block {
896 Some(mut block) => {
897 block.clear();
898 let in_use = self.checked_out.fetch_add(1, Ordering::Relaxed) + 1;
899 self.peak_checked_out.fetch_max(in_use, Ordering::Relaxed);
900 Some(block)
901 }
902 None => {
903 self.misses.fetch_add(1, Ordering::Relaxed);
904 None
905 }
906 }
907 }
908
909 fn release(&self, mut block: Vec<A>) {
911 if block.capacity() < self.elements_per_block {
912 return;
914 }
915 block.clear();
916 let mut free = lock_recovered(&self.free_blocks);
917 if free.len() < self.total_blocks {
918 free.push(block);
919 drop(free);
920 let previous = self.checked_out.load(Ordering::Relaxed);
921 if previous > 0 {
922 self.checked_out.store(previous - 1, Ordering::Relaxed);
923 }
924 }
925 }
926
927 fn get_efficiency(&self) -> f64 {
931 if self.total_blocks == 0 {
932 return 0.0;
933 }
934 self.peak_checked_out.load(Ordering::Relaxed) as f64 / self.total_blocks as f64
935 }
936
937 fn misses(&self) -> usize {
938 self.misses.load(Ordering::Relaxed)
939 }
940}
941
942impl<A: Float + Send + Sync> SIMDProcessor<A> {
943 fn new(enabled: bool, batch_threshold: usize) -> Self {
944 Self {
945 enabled,
946 vector_width: batch_threshold.max(1),
947 _element: std::marker::PhantomData,
948 }
949 }
950
951 fn is_active(&self, len: usize) -> bool {
954 self.enabled && len >= self.vector_width
955 }
956
957 fn apply_scaled_subtract(
960 &self,
961 params: &mut Array1<A>,
962 gradient: &Array1<A>,
963 learning_rate: A,
964 ) {
965 let width = self.vector_width.max(1);
966 match (params.as_slice_mut(), gradient.as_slice()) {
967 (Some(p), Some(g)) => {
968 for (p_chunk, g_chunk) in p.chunks_mut(width).zip(g.chunks(width)) {
969 for (p_value, g_value) in p_chunk.iter_mut().zip(g_chunk.iter()) {
970 *p_value = *p_value - learning_rate * *g_value;
971 }
972 }
973 }
974 _ => {
975 for (p_value, g_value) in params.iter_mut().zip(gradient.iter()) {
976 *p_value = *p_value - learning_rate * *g_value;
977 }
978 }
979 }
980 }
981}
982
983impl<A: Float + Send + Sync> GradientQuantizer<A> {
984 fn new(bits: u8) -> Self {
985 Self {
986 bits: bits.clamp(1, 24),
989 scale: A::one(),
990 zero_point: A::zero(),
991 error_accumulator: None,
992 }
993 }
994
995 fn quantize(&mut self, gradient: &Array1<A>) -> Result<Array1<A>> {
1004 let n = gradient.len();
1005 if n == 0 {
1006 return Ok(gradient.clone());
1007 }
1008
1009 let compensated = match self.error_accumulator.as_ref() {
1012 Some(error) if error.len() == n => gradient + error,
1013 _ => gradient.clone(),
1014 };
1015
1016 if compensated.iter().any(|value| !value.is_finite()) {
1019 return Err(OptimError::InvalidParameter(
1020 "cannot quantize a gradient containing non-finite values".to_string(),
1021 ));
1022 }
1023 let max_abs = compensated.iter().fold(
1024 A::zero(),
1025 |acc, x| if x.abs() > acc { x.abs() } else { acc },
1026 );
1027
1028 self.zero_point = A::zero(); if max_abs == A::zero() {
1030 self.scale = A::one();
1032 self.error_accumulator = Some(Array1::zeros(n));
1033 return Ok(compensated);
1034 }
1035
1036 let level_count = (1u32 << (self.bits.max(1) as u32 - 1))
1037 .saturating_sub(1)
1038 .max(1);
1039 let levels = A::from(level_count).unwrap_or(A::one());
1040 self.scale = max_abs / levels;
1041 let scale = self.scale;
1042 let zero_point = self.zero_point;
1043
1044 let quantized = compensated.mapv(|x| {
1045 let mut q = (x / scale).round();
1046 if q > levels {
1047 q = levels;
1048 } else if q < -levels {
1049 q = -levels;
1050 }
1051 q * scale + zero_point
1052 });
1053
1054 self.error_accumulator = Some(&compensated - &quantized);
1055 Ok(quantized)
1056 }
1057}
1058
1059impl LatencyMonitor {
1060 fn new(maxsamples: usize) -> Self {
1061 Self {
1062 latency_samples: VecDeque::with_capacity(maxsamples),
1063 maxsamples: maxsamples.max(1),
1064 p50_latency: Duration::from_micros(0),
1065 p95_latency: Duration::from_micros(0),
1066 p99_latency: Duration::from_micros(0),
1067 violations: 0,
1068 total_operations: 0,
1069 }
1070 }
1071
1072 fn record_latency(&mut self, latency: Duration) {
1073 self.latency_samples.push_back(latency);
1074 if self.latency_samples.len() > self.maxsamples {
1075 self.latency_samples.pop_front();
1076 }
1077
1078 self.total_operations += 1;
1079 self.update_percentiles();
1080 }
1081
1082 fn update_percentiles(&mut self) {
1083 if self.latency_samples.is_empty() {
1084 return;
1085 }
1086
1087 let mut sorted: Vec<_> = self.latency_samples.iter().cloned().collect();
1088 sorted.sort();
1089
1090 let last = sorted.len() - 1;
1091 let index_for = |q: f64| ((sorted.len() as f64 * q) as usize).min(last);
1092 self.p50_latency = sorted[index_for(0.50)];
1093 self.p95_latency = sorted[index_for(0.95)];
1094 self.p99_latency = sorted[index_for(0.99)];
1095 }
1096
1097 fn get_average_latency(&self) -> Duration {
1098 if self.latency_samples.is_empty() {
1099 Duration::from_micros(0)
1100 } else {
1101 let total: Duration = self.latency_samples.iter().sum();
1102 total / self.latency_samples.len() as u32
1103 }
1104 }
1105}
1106
1107impl<A: Float + Send + Sync> ApproximationController<A> {
1108 fn new(targetlatency: Duration) -> Self {
1109 Self {
1110 approximation_level: A::zero(),
1111 performance_history: VecDeque::with_capacity(100),
1112 adaptation_rate: A::from(0.1).unwrap_or_else(A::one),
1113 targetlatency,
1114 }
1115 }
1116
1117 fn get_approximation_level(&self) -> A {
1118 self.approximation_level
1119 }
1120
1121 fn record_performance(&mut self, latency: Duration, _approximation_level: A, accuracy: A) {
1122 let now = Instant::now();
1123 let point = PerformancePoint {
1124 latency,
1125 accuracy,
1126 timestamp: now,
1127 };
1128
1129 self.performance_history.push_back(point);
1130 while self
1135 .performance_history
1136 .front()
1137 .is_some_and(|p| now.duration_since(p.timestamp) > PERFORMANCE_WINDOW_AGE)
1138 {
1139 self.performance_history.pop_front();
1140 }
1141 if self.performance_history.len() > PERFORMANCE_WINDOW_LEN {
1142 self.performance_history.pop_front();
1143 }
1144
1145 self.adapt_approximation_level();
1146 }
1147
1148 fn mean_latency(&self) -> Option<Duration> {
1150 let count = self.performance_history.len();
1151 if count == 0 {
1152 return None;
1153 }
1154 let total: Duration = self.performance_history.iter().map(|p| p.latency).sum();
1155 Some(total / count as u32)
1156 }
1157
1158 fn adapt_approximation_level(&mut self) {
1165 let Some(latency) = self.mean_latency() else {
1166 return;
1167 };
1168 let target = self.targetlatency.as_micros().max(1) as f64;
1169 let latency_ratio = latency.as_micros() as f64 / target;
1170
1171 if latency_ratio > 1.1 {
1172 self.approximation_level =
1174 (self.approximation_level + self.adaptation_rate).min(A::one());
1175 } else if latency_ratio < 0.8 {
1176 self.approximation_level =
1178 (self.approximation_level - self.adaptation_rate).max(A::zero());
1179 }
1180 }
1181
1182 fn increase_approximation(&mut self) {
1183 let double = A::from(2.0).unwrap_or_else(A::one);
1184 self.approximation_level =
1185 (self.approximation_level + self.adaptation_rate * double).min(A::one());
1186 }
1187
1188 fn mean_accuracy(&self) -> Option<A> {
1190 if self.performance_history.is_empty() {
1191 return None;
1192 }
1193 let count = A::from(self.performance_history.len())?;
1194 let sum = self
1195 .performance_history
1196 .iter()
1197 .fold(A::zero(), |acc, point| acc + point.accuracy);
1198 Some(sum / count)
1199 }
1200}
1201
1202impl<A: Float + Send + Sync + std::iter::Sum> GradientPredictor<A> {
1203 fn new(windowsize: usize) -> Self {
1204 Self {
1205 gradient_history: VecDeque::with_capacity(windowsize.max(2)),
1206 trend_weights: None,
1207 windowsize: windowsize.max(2),
1208 confidence: None,
1209 pending_prediction: None,
1210 }
1211 }
1212
1213 fn observe(&mut self, gradient: &Array1<A>) {
1216 if let Some(prediction) = self.pending_prediction.take() {
1217 if prediction.len() == gradient.len() {
1218 let similarity = cosine_similarity(&prediction, gradient);
1219 let alpha = A::from(0.2).unwrap_or_else(A::one);
1220 self.confidence = Some(match self.confidence {
1221 Some(previous) => previous * (A::one() - alpha) + similarity * alpha,
1222 None => similarity,
1223 });
1224 }
1225 }
1226
1227 self.gradient_history.push_back(gradient.clone());
1228 while self.gradient_history.len() > self.windowsize {
1229 self.gradient_history.pop_front();
1230 }
1231 self.recompute_trend();
1232 }
1233
1234 fn recompute_trend(&mut self) {
1236 let n = self.gradient_history.len();
1237 if n < 2 {
1238 self.trend_weights = None;
1239 return;
1240 }
1241 let dim = match self.gradient_history.back() {
1242 Some(last) => last.len(),
1243 None => return,
1244 };
1245 if self.gradient_history.iter().any(|g| g.len() != dim) {
1246 self.trend_weights = None;
1247 return;
1248 }
1249
1250 let n_f = A::from(n).unwrap_or_else(A::one);
1252 let x_mean = A::from((n - 1) as f64 / 2.0).unwrap_or_else(A::zero);
1253 let mut denominator = A::zero();
1254 for i in 0..n {
1255 let dx = A::from(i).unwrap_or_else(A::zero) - x_mean;
1256 denominator = denominator + dx * dx;
1257 }
1258 if denominator == A::zero() {
1259 self.trend_weights = None;
1260 return;
1261 }
1262
1263 let mut slopes = Array1::zeros(dim);
1264 for coordinate in 0..dim {
1265 let mut y_sum = A::zero();
1266 for gradient in &self.gradient_history {
1267 y_sum = y_sum + gradient[coordinate];
1268 }
1269 let y_mean = y_sum / n_f;
1270 let mut numerator = A::zero();
1271 for (i, gradient) in self.gradient_history.iter().enumerate() {
1272 let dx = A::from(i).unwrap_or_else(A::zero) - x_mean;
1273 numerator = numerator + dx * (gradient[coordinate] - y_mean);
1274 }
1275 slopes[coordinate] = numerator / denominator;
1276 }
1277 self.trend_weights = Some(slopes);
1278 }
1279
1280 fn predict(&mut self) -> Option<(Array1<A>, A)> {
1283 let last = self.gradient_history.back()?.clone();
1284 let slopes = self.trend_weights.as_ref()?;
1285 if slopes.len() != last.len() {
1286 return None;
1287 }
1288 let mut predicted = last;
1289 for (value, &slope) in predicted.iter_mut().zip(slopes.iter()) {
1290 *value = *value + slope;
1291 }
1292 self.pending_prediction = Some(predicted.clone());
1293 let confidence = self.confidence.unwrap_or_else(A::zero);
1296 Some((predicted, confidence))
1297 }
1298}
1299
1300fn cosine_similarity<A: Float>(a: &Array1<A>, b: &Array1<A>) -> A {
1303 if a.len() != b.len() {
1304 return A::zero();
1305 }
1306 let mut dot = A::zero();
1307 let mut norm_a = A::zero();
1308 let mut norm_b = A::zero();
1309 for (&x, &y) in a.iter().zip(b.iter()) {
1310 dot = dot + x * y;
1311 norm_a = norm_a + x * x;
1312 norm_b = norm_b + y * y;
1313 }
1314 let norm_a = norm_a.sqrt();
1315 let norm_b = norm_b.sqrt();
1316 if norm_a == A::zero() || norm_b == A::zero() {
1317 A::zero()
1318 } else {
1319 dot / (norm_a * norm_b)
1320 }
1321}
1322
1323#[derive(Debug, Clone)]
1325pub struct LowLatencyMetrics {
1326 pub avg_latency_us: u64,
1328 pub p50_latency_us: u64,
1330 pub p95_latency_us: u64,
1332 pub p99_latency_us: u64,
1334 pub latency_violations: usize,
1336 pub total_operations: usize,
1338 pub current_approximation_level: f64,
1340 pub approximation_accuracy: Option<f64>,
1343 pub precomputation_hit_rate: Option<f64>,
1346 pub precomputation_attempts: usize,
1348 pub memory_efficiency: f64,
1350 pub memory_pool_misses: usize,
1352}
1353
1354#[cfg(test)]
1355mod tests {
1356 use super::*;
1357 use crate::optimizers::SGD;
1358
1359 #[test]
1360 fn test_low_latency_config() {
1361 let config = LowLatencyConfig::default();
1362 assert_eq!(config.target_latency_us, 100);
1363 assert!(config.enable_precomputation);
1364 assert!(config.enable_lock_free);
1365 }
1366
1367 #[test]
1368 fn test_low_latency_optimizer_creation() {
1369 let sgd = SGD::new(0.01f64);
1370 let config = LowLatencyConfig::default();
1371 let result = LowLatencyOptimizer::new(sgd, config);
1372 assert!(result.is_ok());
1373 }
1374
1375 #[test]
1376 fn test_latency_monitor() {
1377 let mut monitor = LatencyMonitor::new(10);
1378
1379 for i in 1..=5 {
1380 monitor.record_latency(Duration::from_micros(i * 100));
1381 }
1382
1383 assert_eq!(monitor.total_operations, 5);
1384 assert!(monitor.get_average_latency().as_micros() > 0);
1385 }
1386
1387 #[test]
1388 fn test_gradient_quantizer() {
1389 let mut quantizer = GradientQuantizer::new(8);
1390 let gradient = Array1::from_vec(vec![0.1f64, 0.5, -0.3, 0.8]);
1391
1392 let result = quantizer.quantize(&gradient);
1393 assert!(result.is_ok());
1394
1395 let quantized = result.expect("quantization of a finite gradient must succeed");
1396 assert_eq!(quantized.len(), gradient.len());
1397 }
1398
1399 #[test]
1400 fn test_approximation_controller() {
1401 let mut controller = ApproximationController::new(Duration::from_micros(100));
1402
1403 controller.record_performance(Duration::from_micros(200), 0.0f64, 0.9f64);
1405
1406 assert!(controller.get_approximation_level() > 0.0);
1407 }
1408
1409 #[test]
1410 fn test_lock_free_buffer() {
1411 let buffer = LockFreeBuffer::<f64>::new(4);
1412 assert_eq!(buffer.capacity, 4);
1413 assert_eq!(buffer.write_index.load(Ordering::Relaxed), 0);
1414 assert_eq!(buffer.read_index.load(Ordering::Relaxed), 0);
1415 }
1416}