1use optirs_core::Optimizer;
7use scirs2_core::error::ErrorContext;
8use scirs2_core::ndarray::{Array, ArrayBase, Data, Dimension, Ix1};
9use scirs2_core::numeric::Float;
10use std::collections::HashMap;
11
12use crate::error::{OptimError, Result};
13
14#[derive(Debug, Clone)]
16pub struct TPUConfig {
17 pub tpu_version: TPUVersion,
19
20 pub num_cores: usize,
22
23 pub enable_xla: bool,
25
26 pub xla_optimization_level: XLAOptimizationLevel,
28
29 pub mixed_precision: bool,
31
32 pub batch_size_per_core: usize,
34
35 pub enable_pod_coordination: bool,
37
38 pub pod_topology: PodTopology,
40
41 pub memory_optimization: TPUMemoryOptimization,
43
44 pub gradient_compression: bool,
46
47 pub prefetch_depth: usize,
49
50 pub experimental_features: bool,
52}
53
54impl Default for TPUConfig {
55 fn default() -> Self {
56 Self {
57 tpu_version: TPUVersion::V4,
58 num_cores: 8,
59 enable_xla: true,
60 xla_optimization_level: XLAOptimizationLevel::Aggressive,
61 mixed_precision: true,
62 batch_size_per_core: 32,
63 enable_pod_coordination: false,
64 pod_topology: PodTopology::Single,
65 memory_optimization: TPUMemoryOptimization::Balanced,
66 gradient_compression: true,
67 prefetch_depth: 2,
68 experimental_features: false,
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum TPUVersion {
76 V2,
77 V3,
78 V4,
79 V5e,
80 V5p,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum XLAOptimizationLevel {
86 None,
87 Basic,
88 Standard,
89 Aggressive,
90 Experimental,
91}
92
93#[derive(Debug, Clone, Copy, Default)]
95pub enum PodTopology {
96 #[default]
97 Single, Pod2x2, Pod4x4, Pod8x8, Pod16x16, Pod32x32, }
104
105#[derive(Debug, Clone, Copy)]
107pub enum TPUMemoryOptimization {
108 Memory,
110 Speed,
112 Balanced,
114 Custom,
116}
117
118pub struct TPUOptimizer<O, A>
120where
121 A: Float + scirs2_core::ndarray::ScalarOperand + std::fmt::Debug,
122 O: Optimizer<A, scirs2_core::ndarray::Ix1>,
123{
124 base_optimizer: O,
126
127 config: TPUConfig,
129
130 xla_graph: Option<XLAComputationGraph>,
132
133 memory_allocator: TPUMemoryAllocator<A>,
135
136 pod_coordinator: Option<TPUPodCoordinator>,
138
139 profiler: TPUProfiler,
141
142 step_count: usize,
144
145 computation_cache: HashMap<String, CompiledComputation>,
147}
148
149#[derive(Debug)]
169struct XLAComputationGraph {
170 nodes: Vec<XLANode>,
172
173 builder: XLAComputationBuilder,
175
176 inputs: HashMap<String, XLAOperand>,
178
179 outputs: Vec<XLAOperand>,
181
182 optimization_passes: Vec<XLAOptimizationPass>,
184}
185
186#[derive(Debug, Clone)]
188struct XLANode {
189 operation: XLAOperation,
191
192 inputs: Vec<XLAOperand>,
194
195 outputshape: XLAShape,
197
198 metadata: XLANodeMetadata,
200}
201
202#[derive(Debug, Clone)]
211enum XLAOperation {
212 Add,
213 Multiply,
214}
215
216#[derive(Debug, Clone, Copy)]
218struct XLAOperand {
219 id: usize,
220 shape: XLAShape,
221}
222
223#[derive(Debug, Clone, Copy)]
225pub struct XLAShape {
226 dimensions: [usize; 4], rank: usize,
228 element_type: XLAElementType,
229}
230
231#[derive(Debug, Clone, Copy)]
237enum XLAElementType {
238 F32,
239 BF16,
240}
241
242#[derive(Debug)]
244struct XLAComputationBuilder {
245 optimization_level: XLAOptimizationLevel,
247
248 target_config: TPUConfig,
250}
251
252#[derive(Debug, Clone)]
254enum XLAOptimizationPass {
255 ConstantFolding,
256 DeadCodeElimination,
257 OperatorFusion,
258 LayoutOptimization,
259 MemoryOptimization,
260 TensorCoreUtilization,
261}
262
263#[derive(Debug, Clone)]
265struct XLANodeMetadata {
266 flops: u64,
268
269 memory_bytes: usize,
271}
272
273#[derive(Debug)]
283struct TPUMemoryAllocator<A: Float> {
284 total_memory: usize,
286
287 allocated_memory: usize,
289
290 fragmentation_stats: FragmentationStats,
292
293 _phantom: std::marker::PhantomData<A>,
295}
296
297#[derive(Debug, Clone)]
299struct FragmentationStats {
300 external_fragmentation: f64,
302}
303
304#[derive(Debug)]
313struct TPUPodCoordinator {
314 num_cores: usize,
316}
317
318#[derive(Debug)]
320struct TPUProfiler {
321 timeline: Vec<ProfileEvent>,
323
324 compilation_metrics: CompilationMetrics,
326
327 utilization_metrics: UtilizationMetrics,
329}
330
331#[derive(Debug, Clone)]
334pub struct ProfileEvent {
335 pub timestamp: std::time::Instant,
337
338 pub event_type: ProfileEventType,
340
341 pub core_id: usize,
343
344 pub duration_us: u64,
346
347 pub metadata: HashMap<String, String>,
349}
350
351#[derive(Debug, Clone, PartialEq, Eq)]
358pub enum ProfileEventType {
359 Computation,
360 Communication,
361 Compilation,
362}
363
364#[derive(Debug, Clone)]
366pub struct CompilationMetrics {
367 pub compilation_time_ms: u64,
369
370 pub optimizations_applied: usize,
372
373 pub code_size: usize,
375}
376
377#[derive(Debug, Clone)]
379pub struct UtilizationMetrics {
380 pub compute_utilization: f64,
382
383 pub memory_bandwidth_utilization: f64,
385
386 pub communication_utilization: f64,
388
389 pub matrix_unit_utilization: f64,
391
392 pub vector_unit_utilization: f64,
394}
395
396#[derive(Debug)]
402struct CompiledComputation {
403 id: String,
405
406 code: Vec<u8>,
408
409 perf_characteristics: PerformanceCharacteristics,
411
412 memory_requirements: MemoryRequirements,
414}
415
416#[derive(Debug, Clone)]
418struct PerformanceCharacteristics {
419 estimated_execution_time_us: u64,
421
422 flops: u64,
424
425 memory_bandwidth_gbs: f64,
427
428 utilization_estimate: f64,
430}
431
432#[derive(Debug, Clone)]
434struct MemoryRequirements {
435 total_memory: usize,
437
438 working_memory: usize,
440
441 parameter_memory: usize,
443
444 temp_memory: usize,
446}
447
448impl<O, A> TPUOptimizer<O, A>
449where
450 A: Float
451 + Default
452 + Clone
453 + Send
454 + Sync
455 + scirs2_core::ndarray::ScalarOperand
456 + std::fmt::Debug,
457 O: Optimizer<A, scirs2_core::ndarray::Ix1> + Send + Sync,
458{
459 pub fn new(base_optimizer: O, config: TPUConfig) -> Result<Self> {
461 let memory_allocator = TPUMemoryAllocator::new(&config)?;
462 let pod_coordinator = if config.enable_pod_coordination {
463 Some(TPUPodCoordinator::new(&config)?)
464 } else {
465 None
466 };
467
468 let profiler = TPUProfiler::new();
469
470 Ok(Self {
471 base_optimizer,
472 config,
473 xla_graph: None,
474 memory_allocator,
475 pod_coordinator,
476 profiler,
477 step_count: 0,
478 computation_cache: HashMap::new(),
479 })
480 }
481
482 pub fn initialize_xla_graph(&mut self) -> Result<()> {
484 if !self.config.enable_xla {
485 return Ok(());
486 }
487
488 self.xla_graph = Some(self.default_xla_graph());
489
490 Ok(())
491 }
492
493 fn default_xla_graph(&self) -> XLAComputationGraph {
500 let builder =
501 XLAComputationBuilder::new(self.config.xla_optimization_level, self.config.clone());
502
503 XLAComputationGraph {
504 nodes: Vec::new(),
505 builder,
506 inputs: HashMap::new(),
507 outputs: Vec::new(),
508 optimization_passes: vec![
509 XLAOptimizationPass::ConstantFolding,
510 XLAOptimizationPass::DeadCodeElimination,
511 XLAOptimizationPass::OperatorFusion,
512 XLAOptimizationPass::LayoutOptimization,
513 XLAOptimizationPass::MemoryOptimization,
514 XLAOptimizationPass::TensorCoreUtilization,
515 ],
516 }
517 }
518
519 pub fn compile_step(&mut self, inputshapes: &[XLAShape]) -> Result<String> {
521 let compilation_id = format!("optimizer_step_{}", self.step_count);
522
523 if self.computation_cache.contains_key(&compilation_id) {
524 return Ok(compilation_id);
525 }
526
527 let start_time = std::time::Instant::now();
528
529 let computation = self.build_optimizer_computation(inputshapes)?;
531
532 let (optimized_computation, effective_passes) =
535 self.apply_optimization_passes(computation)?;
536
537 let compiled = self.compile_to_tpu(optimized_computation)?;
539 let generated_code_size = compiled.code.len();
540
541 let compilation_time = start_time.elapsed();
542
543 self.profiler.compilation_metrics.compilation_time_ms = compilation_time.as_millis() as u64;
545 self.profiler.compilation_metrics.optimizations_applied = effective_passes;
546 self.profiler.compilation_metrics.code_size = generated_code_size;
547
548 let peak_bandwidth_gbs = self.get_interconnect_bandwidth();
552 self.profiler.utilization_metrics.compute_utilization = compiled
553 .perf_characteristics
554 .utilization_estimate
555 .clamp(0.0, 1.0);
556 self.profiler
557 .utilization_metrics
558 .memory_bandwidth_utilization = if peak_bandwidth_gbs > 0.0 {
559 (compiled.perf_characteristics.memory_bandwidth_gbs / peak_bandwidth_gbs)
560 .clamp(0.0, 1.0)
561 } else {
562 0.0
563 };
564 self.profiler.utilization_metrics.communication_utilization =
567 if self.pod_coordinator.is_some() {
568 self.profiler.utilization_metrics.compute_utilization
569 } else {
570 0.0
571 };
572 self.profiler.utilization_metrics.vector_unit_utilization =
576 self.profiler.utilization_metrics.compute_utilization;
577 self.profiler.utilization_metrics.matrix_unit_utilization = 0.0;
578
579 let mut metadata = HashMap::new();
582 metadata.insert("program".to_string(), compiled.id.clone());
583 metadata.insert(
584 "total_memory".to_string(),
585 compiled.memory_requirements.total_memory.to_string(),
586 );
587 metadata.insert(
588 "working_memory".to_string(),
589 compiled.memory_requirements.working_memory.to_string(),
590 );
591 metadata.insert(
592 "parameter_memory".to_string(),
593 compiled.memory_requirements.parameter_memory.to_string(),
594 );
595 metadata.insert(
596 "temp_memory".to_string(),
597 compiled.memory_requirements.temp_memory.to_string(),
598 );
599 metadata.insert(
600 "flops".to_string(),
601 compiled.perf_characteristics.flops.to_string(),
602 );
603 metadata.insert(
604 "estimated_execution_time_us".to_string(),
605 compiled
606 .perf_characteristics
607 .estimated_execution_time_us
608 .to_string(),
609 );
610 self.profiler.timeline.push(ProfileEvent {
611 timestamp: start_time,
612 event_type: ProfileEventType::Compilation,
613 core_id: 0,
614 duration_us: compilation_time.as_micros() as u64,
615 metadata,
616 });
617
618 self.computation_cache
620 .insert(compilation_id.clone(), compiled);
621
622 Ok(compilation_id)
623 }
624
625 pub fn tpu_step<S, DIM>(
627 &mut self,
628 params: &ArrayBase<S, DIM>,
629 gradients: &ArrayBase<S, DIM>,
630 ) -> Result<Array<A, DIM>>
631 where
632 S: Data<Elem = A>,
633 DIM: Dimension + Clone,
634 {
635 let start_time = std::time::Instant::now();
636
637 let paramshape = self.array_to_xlashape(params)?;
639 let gradshape = self.array_to_xlashape(gradients)?;
640
641 let computation_id = self.compile_step(&[paramshape, gradshape])?;
643
644 let result = if self.pod_coordinator.is_some() {
648 self.execute_distributed(&computation_id, params, gradients)?
649 } else {
650 self.execute_single_tpu(&computation_id, params, gradients)?
651 };
652
653 let execution_time = start_time.elapsed();
655 self.profiler.timeline.push(ProfileEvent {
656 timestamp: start_time,
657 event_type: ProfileEventType::Computation,
658 core_id: 0,
659 duration_us: execution_time.as_micros() as u64,
660 metadata: HashMap::new(),
661 });
662
663 self.step_count += 1;
664
665 Ok(result)
666 }
667
668 fn build_optimizer_computation(&self, inputshapes: &[XLAShape]) -> Result<XLAComputationGraph> {
678 let mut graph = match self.xla_graph.as_ref() {
681 Some(existing) => existing.clone(),
682 None => self.default_xla_graph(),
683 };
684
685 let mut operands = Vec::with_capacity(inputshapes.len());
687 for (i, &shape) in inputshapes.iter().enumerate() {
688 let operand = XLAOperand { id: i, shape };
689 graph.inputs.insert(format!("input_{}", i), operand);
690 operands.push(operand);
691 }
692
693 if let [parameter, gradient] = operands.as_slice() {
696 let elements = shape_element_count(&gradient.shape);
697 let bytes = shape_byte_count(&gradient.shape);
698 let next_id = graph.inputs.len();
699
700 let scaled = XLAOperand {
702 id: next_id,
703 shape: gradient.shape,
704 };
705 graph.nodes.push(XLANode {
706 operation: XLAOperation::Multiply,
707 inputs: vec![*gradient],
708 outputshape: gradient.shape,
709 metadata: XLANodeMetadata {
710 flops: elements,
711 memory_bytes: bytes,
712 },
713 });
714
715 let updated = XLAOperand {
717 id: next_id + 1,
718 shape: parameter.shape,
719 };
720 graph.nodes.push(XLANode {
721 operation: XLAOperation::Add,
722 inputs: vec![*parameter, scaled],
723 outputshape: parameter.shape,
724 metadata: XLANodeMetadata {
725 flops: shape_element_count(¶meter.shape),
726 memory_bytes: shape_byte_count(¶meter.shape),
727 },
728 });
729
730 graph.outputs = vec![updated];
731 }
732
733 Ok(graph)
734 }
735
736 fn apply_optimization_passes(
742 &self,
743 mut computation: XLAComputationGraph,
744 ) -> Result<(XLAComputationGraph, usize)> {
745 let mut effective = 0usize;
746 for pass in computation.optimization_passes.clone() {
747 let (next, changed) = self.apply_single_pass(computation, &pass)?;
748 computation = next;
749 if changed {
750 effective += 1;
751 }
752 }
753 Ok((computation, effective))
754 }
755
756 fn apply_single_pass(
765 &self,
766 mut computation: XLAComputationGraph,
767 pass: &XLAOptimizationPass,
768 ) -> Result<(XLAComputationGraph, bool)> {
769 let changed = match pass {
770 XLAOptimizationPass::DeadCodeElimination => {
771 let before = computation.nodes.len();
772 computation
773 .nodes
774 .retain(|node| node.metadata.flops != 0 || node.metadata.memory_bytes != 0);
775 computation.nodes.len() != before
776 }
777 XLAOptimizationPass::ConstantFolding
778 | XLAOptimizationPass::OperatorFusion
779 | XLAOptimizationPass::LayoutOptimization
780 | XLAOptimizationPass::MemoryOptimization
781 | XLAOptimizationPass::TensorCoreUtilization => false,
782 };
783 Ok((computation, changed))
784 }
785
786 fn compile_to_tpu(&self, computation: XLAComputationGraph) -> Result<CompiledComputation> {
787 let code = encode_program(&computation);
792 let compilation_id = format!("tpu_comp_{:016x}", fnv1a_64(&code));
793
794 let input_elements: u64 = computation
798 .inputs
799 .values()
800 .map(|op| shape_element_count(&op.shape))
801 .sum();
802
803 const FLOPS_PER_ELEMENT: u64 = 2;
807 let node_flops: u64 = computation
808 .nodes
809 .iter()
810 .map(|node| node.metadata.flops)
811 .sum();
812 let flops = input_elements
813 .saturating_mul(FLOPS_PER_ELEMENT)
814 .saturating_add(node_flops);
815
816 let peak_flops_per_us = self.peak_compute_flops_per_us();
820 let estimated_execution_time_us =
821 flops.checked_div(peak_flops_per_us).unwrap_or(flops).max(1);
822
823 let saturation = (self
828 .config
829 .batch_size_per_core
830 .saturating_mul(self.config.num_cores))
831 .max(1) as f64;
832 let elems = input_elements as f64;
833 let utilization_estimate = elems / (elems + saturation);
834
835 let input_bytes: usize = computation
837 .inputs
838 .values()
839 .map(|op| shape_byte_count(&op.shape))
840 .sum();
841 let output_bytes: usize = computation
842 .outputs
843 .iter()
844 .map(|op| shape_byte_count(&op.shape))
845 .sum();
846 let largest_input_bytes = computation
847 .inputs
848 .values()
849 .map(|op| shape_byte_count(&op.shape))
850 .max()
851 .unwrap_or(0);
852
853 let working_memory = input_bytes.saturating_add(output_bytes);
854 let parameter_memory = largest_input_bytes;
855 let temp_memory = working_memory;
856 let total_memory = working_memory
857 .saturating_add(parameter_memory)
858 .saturating_add(temp_memory);
859
860 let bytes_moved = working_memory.saturating_add(parameter_memory) as f64;
862 let seconds = estimated_execution_time_us as f64 / 1.0e6;
863 let memory_bandwidth_gbs = if seconds > 0.0 {
864 (bytes_moved / 1.0e9) / seconds
865 } else {
866 0.0
867 };
868
869 let perf_characteristics = PerformanceCharacteristics {
870 estimated_execution_time_us,
871 flops,
872 memory_bandwidth_gbs,
873 utilization_estimate,
874 };
875
876 let memory_requirements = MemoryRequirements {
877 total_memory,
878 working_memory,
879 parameter_memory,
880 temp_memory,
881 };
882
883 Ok(CompiledComputation {
884 id: compilation_id,
885 code,
886 perf_characteristics,
887 memory_requirements,
888 })
889 }
890
891 fn peak_compute_flops_per_us(&self) -> u64 {
895 match self.config.tpu_version {
896 TPUVersion::V2 => 45_000_000, TPUVersion::V3 => 123_000_000, TPUVersion::V4 => 275_000_000, TPUVersion::V5e => 197_000_000, TPUVersion::V5p => 459_000_000, }
902 }
903
904 fn cpu_optimizer_update<S, DIM>(
912 &mut self,
913 params: &ArrayBase<S, DIM>,
914 gradients: &ArrayBase<S, DIM>,
915 ) -> Result<Array<A, DIM>>
916 where
917 S: Data<Elem = A>,
918 DIM: Dimension + Clone,
919 {
920 if params.shape() != gradients.shape() {
921 return Err(OptimError::ShapeError(ErrorContext::new(format!(
922 "parameter shape {:?} does not match gradient shape {:?}",
923 params.shape(),
924 gradients.shape()
925 ))));
926 }
927
928 let params_flat: Array<A, Ix1> = params.iter().cloned().collect();
929 let grads_flat: Array<A, Ix1> = gradients.iter().cloned().collect();
930
931 let updated_flat = self
932 .base_optimizer
933 .step(¶ms_flat, &grads_flat)
934 .map_err(|e| {
935 OptimError::ComputationError(ErrorContext::new(format!(
936 "inner optimizer step failed: {e}"
937 )))
938 })?;
939
940 let updated_vec: Vec<A> = updated_flat.into_iter().collect();
941 Array::from_shape_vec(params.raw_dim(), updated_vec).map_err(|e| {
942 OptimError::ShapeError(ErrorContext::new(format!(
943 "failed to reshape updated parameters to original shape: {e}"
944 )))
945 })
946 }
947
948 fn execute_single_tpu<S, DIM>(
949 &mut self,
950 _computation_id: &str,
951 params: &ArrayBase<S, DIM>,
952 gradients: &ArrayBase<S, DIM>,
953 ) -> Result<Array<A, DIM>>
954 where
955 S: Data<Elem = A>,
956 DIM: Dimension + Clone,
957 {
958 self.cpu_optimizer_update(params, gradients)
960 }
961
962 fn execute_distributed<S, DIM>(
963 &mut self,
964 _computation_id: &str,
965 params: &ArrayBase<S, DIM>,
966 gradients: &ArrayBase<S, DIM>,
967 ) -> Result<Array<A, DIM>>
968 where
969 S: Data<Elem = A>,
970 DIM: Dimension + Clone,
971 {
972 let num_cores = self
983 .pod_coordinator
984 .as_ref()
985 .map(|coordinator| coordinator.num_cores)
986 .unwrap_or(1);
987
988 let comm_start = std::time::Instant::now();
989 let mut metadata = HashMap::new();
990 metadata.insert("collective".to_string(), "all_reduce_mean".to_string());
991 metadata.insert("replicas".to_string(), num_cores.to_string());
992 self.profiler.timeline.push(ProfileEvent {
993 timestamp: comm_start,
994 event_type: ProfileEventType::Communication,
995 core_id: 0,
996 duration_us: comm_start.elapsed().as_micros() as u64,
997 metadata,
998 });
999
1000 self.cpu_optimizer_update(params, gradients)
1001 }
1002
1003 fn array_to_xlashape<S, DIM>(&self, array: &ArrayBase<S, DIM>) -> Result<XLAShape>
1004 where
1005 S: Data<Elem = A>,
1006 DIM: Dimension,
1007 {
1008 let dims = array.shape();
1009 let mut dimensions = [1usize; 4];
1010
1011 for (i, &dim) in dims.iter().enumerate().take(4) {
1012 dimensions[i] = dim;
1013 }
1014
1015 Ok(XLAShape {
1016 dimensions,
1017 rank: dims.len().min(4),
1018 element_type: if self.config.mixed_precision {
1022 XLAElementType::BF16
1023 } else {
1024 XLAElementType::F32
1025 },
1026 })
1027 }
1028
1029 pub fn get_performance_metrics(&self) -> TPUPerformanceMetrics {
1031 TPUPerformanceMetrics {
1032 utilization: self.profiler.utilization_metrics.clone(),
1033 compilation: self.profiler.compilation_metrics.clone(),
1034 memory_usage: self.memory_allocator.get_usage_stats(),
1035 step_count: self.step_count,
1036 cache_hit_rate: self.get_cache_hit_rate(),
1037 }
1038 }
1039
1040 pub fn profile_timeline(&self) -> &[ProfileEvent] {
1045 &self.profiler.timeline
1046 }
1047
1048 fn get_cache_hit_rate(&self) -> f64 {
1049 if self.step_count == 0 {
1050 0.0
1051 } else {
1052 self.computation_cache.len() as f64 / self.step_count as f64
1053 }
1054 }
1055
1056 pub fn optimize_memory_layout(&mut self) -> Result<()> {
1058 self.memory_allocator.optimize_layout()?;
1059 Ok(())
1060 }
1061
1062 pub fn get_topology_info(&self) -> TPUTopologyInfo {
1064 TPUTopologyInfo {
1065 version: self.config.tpu_version,
1066 num_cores: self.config.num_cores,
1067 topology: self.config.pod_topology,
1068 memory_per_core: self.get_memory_per_core(),
1069 interconnect_bandwidth: self.get_interconnect_bandwidth(),
1070 }
1071 }
1072
1073 fn get_memory_per_core(&self) -> usize {
1074 match self.config.tpu_version {
1075 TPUVersion::V2 => 8 * 1024 * 1024 * 1024, TPUVersion::V3 => 16 * 1024 * 1024 * 1024, TPUVersion::V4 => 32 * 1024 * 1024 * 1024, TPUVersion::V5e => 16 * 1024 * 1024 * 1024, TPUVersion::V5p => 95 * 1024 * 1024 * 1024, }
1081 }
1082
1083 fn get_interconnect_bandwidth(&self) -> f64 {
1084 match self.config.tpu_version {
1085 TPUVersion::V2 => 500.0, TPUVersion::V3 => 900.0, TPUVersion::V4 => 1200.0, TPUVersion::V5e => 1600.0, TPUVersion::V5p => 4800.0, }
1091 }
1092}
1093
1094impl<O, A> Optimizer<A, Ix1> for TPUOptimizer<O, A>
1119where
1120 A: Float
1121 + Default
1122 + Clone
1123 + Send
1124 + Sync
1125 + scirs2_core::ndarray::ScalarOperand
1126 + std::fmt::Debug,
1127 O: Optimizer<A, Ix1> + Send + Sync,
1128{
1129 fn step(
1130 &mut self,
1131 params: &Array<A, Ix1>,
1132 gradients: &Array<A, Ix1>,
1133 ) -> optirs_core::Result<Array<A, Ix1>> {
1134 self.tpu_step(params, gradients)
1139 .map_err(|e| optirs_core::OptimError::OptimizationError(e.to_string()))
1140 }
1141
1142 fn get_learning_rate(&self) -> A {
1143 self.base_optimizer.get_learning_rate()
1144 }
1145
1146 fn set_learning_rate(&mut self, learning_rate: A) {
1147 self.base_optimizer.set_learning_rate(learning_rate);
1148 }
1149
1150 fn step_list(
1151 &mut self,
1152 params_list: &[&Array<A, Ix1>],
1153 gradients_list: &[&Array<A, Ix1>],
1154 ) -> optirs_core::Result<Vec<Array<A, Ix1>>> {
1155 self.base_optimizer.step_list(params_list, gradients_list)
1164 }
1165}
1166
1167#[derive(Debug, Clone)]
1169pub struct TPUPerformanceMetrics {
1170 pub utilization: UtilizationMetrics,
1171 pub compilation: CompilationMetrics,
1172 pub memory_usage: MemoryUsageStats,
1173 pub step_count: usize,
1174 pub cache_hit_rate: f64,
1175}
1176
1177#[derive(Debug, Clone)]
1179pub struct MemoryUsageStats {
1180 pub total_allocated: usize,
1181 pub peak_usage: usize,
1182 pub fragmentation: f64,
1183 pub pool_efficiency: f64,
1184}
1185
1186#[derive(Debug, Clone)]
1188pub struct TPUTopologyInfo {
1189 pub version: TPUVersion,
1190 pub num_cores: usize,
1191 pub topology: PodTopology,
1192 pub memory_per_core: usize,
1193 pub interconnect_bandwidth: f64,
1194}
1195
1196impl<A: Float + Send + Sync> TPUMemoryAllocator<A> {
1199 fn new(config: &TPUConfig) -> Result<Self> {
1200 let total_memory = match config.tpu_version {
1201 TPUVersion::V2 => 8 * 1024 * 1024 * 1024 * config.num_cores,
1202 TPUVersion::V3 => 16 * 1024 * 1024 * 1024 * config.num_cores,
1203 TPUVersion::V4 => 32 * 1024 * 1024 * 1024 * config.num_cores,
1204 TPUVersion::V5e => 16 * 1024 * 1024 * 1024 * config.num_cores,
1205 TPUVersion::V5p => 95 * 1024 * 1024 * 1024 * config.num_cores,
1206 };
1207
1208 Ok(Self {
1209 total_memory,
1210 allocated_memory: 0,
1211 fragmentation_stats: FragmentationStats {
1212 external_fragmentation: 0.0,
1213 },
1214 _phantom: std::marker::PhantomData,
1215 })
1216 }
1217
1218 fn optimize_layout(&mut self) -> Result<()> {
1219 Ok(())
1221 }
1222
1223 fn get_usage_stats(&self) -> MemoryUsageStats {
1224 MemoryUsageStats {
1225 total_allocated: self.allocated_memory,
1226 peak_usage: self.allocated_memory, fragmentation: self.fragmentation_stats.external_fragmentation,
1228 pool_efficiency: if self.total_memory > 0 {
1229 self.allocated_memory as f64 / self.total_memory as f64
1230 } else {
1231 0.0
1232 },
1233 }
1234 }
1235}
1236
1237impl TPUPodCoordinator {
1238 fn new(config: &TPUConfig) -> Result<Self> {
1239 let num_cores = match config.pod_topology {
1240 PodTopology::Single => 1,
1241 PodTopology::Pod2x2 => 4,
1242 PodTopology::Pod4x4 => 16,
1243 PodTopology::Pod8x8 => 64,
1244 PodTopology::Pod16x16 => 256,
1245 PodTopology::Pod32x32 => 1024,
1246 };
1247
1248 Ok(Self { num_cores })
1249 }
1250}
1251
1252impl TPUProfiler {
1253 fn new() -> Self {
1254 Self {
1255 timeline: Vec::new(),
1256 compilation_metrics: CompilationMetrics {
1257 compilation_time_ms: 0,
1258 optimizations_applied: 0,
1259 code_size: 0,
1260 },
1261 utilization_metrics: UtilizationMetrics {
1262 compute_utilization: 0.0,
1263 memory_bandwidth_utilization: 0.0,
1264 communication_utilization: 0.0,
1265 matrix_unit_utilization: 0.0,
1266 vector_unit_utilization: 0.0,
1267 },
1268 }
1269 }
1270}
1271
1272impl XLAComputationBuilder {
1273 fn new(optimization_level: XLAOptimizationLevel, target_config: TPUConfig) -> Self {
1274 Self {
1275 optimization_level,
1276 target_config,
1277 }
1278 }
1279}
1280
1281impl Clone for XLAComputationGraph {
1282 fn clone(&self) -> Self {
1283 Self {
1284 nodes: self.nodes.clone(),
1285 builder: XLAComputationBuilder::new(
1286 self.builder.optimization_level,
1287 self.builder.target_config.clone(),
1288 ),
1289 inputs: self.inputs.clone(),
1290 outputs: self.outputs.clone(),
1291 optimization_passes: self.optimization_passes.clone(),
1292 }
1293 }
1294}
1295
1296fn fnv1a_64(bytes: &[u8]) -> u64 {
1303 const OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
1304 const PRIME: u64 = 0x0000_0100_0000_01b3;
1305 let mut hash = OFFSET_BASIS;
1306 for &b in bytes {
1307 hash ^= b as u64;
1308 hash = hash.wrapping_mul(PRIME);
1309 }
1310 hash
1311}
1312
1313fn shape_element_count(shape: &XLAShape) -> u64 {
1315 let rank = shape.rank.min(shape.dimensions.len());
1316 shape.dimensions[..rank].iter().map(|&d| d as u64).product()
1317}
1318
1319fn element_type_bytes(element_type: XLAElementType) -> usize {
1321 match element_type {
1322 XLAElementType::BF16 => 2,
1323 XLAElementType::F32 => 4,
1324 }
1325}
1326
1327fn shape_byte_count(shape: &XLAShape) -> usize {
1329 (shape_element_count(shape) as usize).saturating_mul(element_type_bytes(shape.element_type))
1330}
1331
1332fn element_type_code(element_type: XLAElementType) -> u8 {
1334 match element_type {
1335 XLAElementType::F32 => 1,
1336 XLAElementType::BF16 => 2,
1337 }
1338}
1339
1340fn operation_code(operation: &XLAOperation) -> u8 {
1342 match operation {
1345 XLAOperation::Add => 0,
1346 XLAOperation::Multiply => 1,
1347 }
1348}
1349
1350fn pass_code(pass: &XLAOptimizationPass) -> u8 {
1352 match pass {
1353 XLAOptimizationPass::ConstantFolding => 0,
1354 XLAOptimizationPass::DeadCodeElimination => 1,
1355 XLAOptimizationPass::OperatorFusion => 2,
1356 XLAOptimizationPass::LayoutOptimization => 3,
1357 XLAOptimizationPass::MemoryOptimization => 4,
1358 XLAOptimizationPass::TensorCoreUtilization => 5,
1359 }
1360}
1361
1362fn encode_shape(bytes: &mut Vec<u8>, shape: &XLAShape) {
1364 let rank = shape.rank.min(shape.dimensions.len());
1365 bytes.push(rank as u8);
1366 bytes.push(element_type_code(shape.element_type));
1367 for &dim in &shape.dimensions[..rank] {
1368 bytes.extend_from_slice(&(dim as u64).to_le_bytes());
1369 }
1370}
1371
1372fn encode_operand(bytes: &mut Vec<u8>, operand: &XLAOperand) {
1374 bytes.extend_from_slice(&(operand.id as u64).to_le_bytes());
1375 encode_shape(bytes, &operand.shape);
1376}
1377
1378fn encode_program(graph: &XLAComputationGraph) -> Vec<u8> {
1386 let mut bytes = Vec::new();
1387 bytes.extend_from_slice(b"OTPU");
1388 bytes.push(1); bytes.push(graph.builder.optimization_level as u8);
1390
1391 let mut inputs: Vec<(&String, &XLAOperand)> = graph.inputs.iter().collect();
1393 inputs.sort_by(|a, b| a.0.cmp(b.0));
1394 bytes.extend_from_slice(&(inputs.len() as u32).to_le_bytes());
1395 for (name, operand) in inputs {
1396 bytes.extend_from_slice(&(name.len() as u32).to_le_bytes());
1397 bytes.extend_from_slice(name.as_bytes());
1398 encode_operand(&mut bytes, operand);
1399 }
1400
1401 bytes.extend_from_slice(&(graph.nodes.len() as u32).to_le_bytes());
1403 for node in &graph.nodes {
1404 bytes.push(operation_code(&node.operation));
1405 bytes.extend_from_slice(&(node.inputs.len() as u32).to_le_bytes());
1406 for operand in &node.inputs {
1407 encode_operand(&mut bytes, operand);
1408 }
1409 encode_shape(&mut bytes, &node.outputshape);
1410 bytes.extend_from_slice(&node.metadata.flops.to_le_bytes());
1411 bytes.extend_from_slice(&(node.metadata.memory_bytes as u64).to_le_bytes());
1412 }
1413
1414 bytes.extend_from_slice(&(graph.outputs.len() as u32).to_le_bytes());
1416 for operand in &graph.outputs {
1417 encode_operand(&mut bytes, operand);
1418 }
1419
1420 bytes.extend_from_slice(&(graph.optimization_passes.len() as u32).to_le_bytes());
1422 for pass in &graph.optimization_passes {
1423 bytes.push(pass_code(pass));
1424 }
1425
1426 bytes
1427}
1428
1429#[cfg(test)]
1430mod tests {
1431 use super::*;
1432
1433 #[test]
1434 fn test_tpu_config_default() {
1435 let config = TPUConfig::default();
1436 assert_eq!(config.num_cores, 8);
1437 assert!(config.enable_xla);
1438 assert!(matches!(config.tpu_version, TPUVersion::V4));
1439 }
1440
1441 #[test]
1451 fn test_xlashape_creation() {
1452 let shape = XLAShape {
1453 dimensions: [10, 20, 1, 1],
1454 rank: 2,
1455 element_type: XLAElementType::F32,
1456 };
1457
1458 assert_eq!(shape.rank, 2);
1459 assert_eq!(shape.dimensions[0], 10);
1460 assert_eq!(shape.dimensions[1], 20);
1461 }
1462
1463 #[test]
1464 fn test_memory_allocator_creation() {
1465 let config = TPUConfig {
1466 tpu_version: TPUVersion::V4,
1467 num_cores: 8,
1468 ..Default::default()
1469 };
1470
1471 let allocator = TPUMemoryAllocator::<f32>::new(&config);
1472 assert!(allocator.is_ok());
1473
1474 let allocator = allocator.expect("unwrap failed");
1475 assert_eq!(allocator.total_memory, 32 * 1024 * 1024 * 1024 * 8); }
1477
1478 use optirs_core::optimizers::SGD;
1479 use scirs2_core::ndarray::Array1;
1480
1481 fn new_sgd_tpu(config: TPUConfig) -> TPUOptimizer<SGD<f32>, f32> {
1482 TPUOptimizer::new(SGD::new(0.1f32), config).expect("failed to build TPU optimizer")
1483 }
1484
1485 #[test]
1486 fn test_tpu_step_updates_params_in_descent_direction() {
1487 let mut optimizer = new_sgd_tpu(TPUConfig::default());
1490 optimizer
1491 .initialize_xla_graph()
1492 .expect("xla graph init failed");
1493
1494 let params = Array1::from(vec![1.0f32, 2.0, 3.0]);
1495 let gradients = Array1::from(vec![1.0f32, 1.0, 1.0]);
1496
1497 let updated = optimizer
1498 .tpu_step(¶ms, &gradients)
1499 .expect("tpu_step must succeed and return updated params");
1500
1501 let expected = [0.9f32, 1.9, 2.9];
1503 assert_eq!(updated.len(), 3);
1504 for (i, (&u, &e)) in updated.iter().zip(expected.iter()).enumerate() {
1505 assert!(
1506 (u - e).abs() < 1e-6,
1507 "index {i}: updated {u} != expected {e}"
1508 );
1509 assert!(
1511 u < params[i],
1512 "index {i}: {u} not below original {}",
1513 params[i]
1514 );
1515 }
1516
1517 assert_eq!(optimizer.step_count, 1);
1519 }
1520
1521 #[test]
1522 fn test_tpu_step_works_without_explicit_graph_init() {
1523 let mut optimizer = new_sgd_tpu(TPUConfig::default());
1526 let params = Array1::from(vec![0.5f32, -0.5]);
1527 let gradients = Array1::from(vec![1.0f32, -1.0]);
1528
1529 let updated = optimizer
1530 .tpu_step(¶ms, &gradients)
1531 .expect("tpu_step must succeed without prior graph init");
1532
1533 assert!((updated[0] - 0.4).abs() < 1e-6);
1534 assert!((updated[1] - (-0.4)).abs() < 1e-6);
1535 }
1536
1537 fn run_one_generic_step<O: Optimizer<f32, scirs2_core::ndarray::Ix1>>(
1542 optimizer: &mut O,
1543 params: &Array1<f32>,
1544 gradients: &Array1<f32>,
1545 ) -> Array1<f32> {
1546 optimizer
1547 .step(params, gradients)
1548 .expect("generic Optimizer::step must succeed")
1549 }
1550
1551 #[test]
1552 fn tpu_optimizer_is_usable_through_the_optimizer_trait() {
1553 let mut optimizer = new_sgd_tpu(TPUConfig::default());
1554 let params = Array1::from(vec![1.0f32, 2.0, 3.0]);
1555 let gradients = Array1::from(vec![1.0f32, 1.0, 1.0]);
1556
1557 let updated = run_one_generic_step(&mut optimizer, ¶ms, &gradients);
1558
1559 let expected = [0.9f32, 1.9, 2.9];
1562 for (i, (&u, &e)) in updated.iter().zip(expected.iter()).enumerate() {
1563 assert!((u - e).abs() < 1e-6, "index {i}: {u} != {e}");
1564 }
1565 assert_eq!(optimizer.step_count, 1);
1566 }
1567
1568 #[test]
1569 fn tpu_optimizer_learning_rate_forwards_to_base_optimizer() {
1570 let mut optimizer = new_sgd_tpu(TPUConfig::default());
1571 assert!((Optimizer::get_learning_rate(&optimizer) - 0.1).abs() < 1e-6);
1572
1573 Optimizer::set_learning_rate(&mut optimizer, 0.5);
1574 assert!((Optimizer::get_learning_rate(&optimizer) - 0.5).abs() < 1e-6);
1575
1576 let params = Array1::from(vec![1.0f32]);
1579 let gradients = Array1::from(vec![1.0f32]);
1580 let updated =
1581 Optimizer::step(&mut optimizer, ¶ms, &gradients).expect("step must succeed");
1582 assert!((updated[0] - 0.5).abs() < 1e-6, "got {}", updated[0]);
1583 }
1584
1585 #[test]
1600 fn tpu_optimizer_step_list_gives_each_tensor_its_own_optimizer_state() {
1601 use optirs_core::optimizers::Adam;
1602
1603 let mut optimizer = TPUOptimizer::new(Adam::new(0.1f32), TPUConfig::default())
1604 .expect("failed to build TPU optimizer");
1605
1606 let params_a = Array1::from(vec![1.0f32, 2.0]);
1607 let grads_a = Array1::from(vec![0.1f32, 0.1]);
1608 let params_b = Array1::from(vec![10.0f32, 20.0]);
1609 let grads_b = Array1::from(vec![0.5f32, 0.5]);
1610
1611 let results = Optimizer::step_list(
1612 &mut optimizer,
1613 &[¶ms_a, ¶ms_b],
1614 &[&grads_a, &grads_b],
1615 )
1616 .expect("step_list must succeed");
1617 assert_eq!(results.len(), 2);
1618
1619 let mut reference = Adam::new(0.1f32);
1622 let expected_a = reference
1623 .step_indexed(0, ¶ms_a, &grads_a)
1624 .expect("reference step_indexed(0) must succeed");
1625 let expected_b = reference
1626 .step_indexed(1, ¶ms_b, &grads_b)
1627 .expect("reference step_indexed(1) must succeed");
1628
1629 for i in 0..2 {
1630 assert!(
1631 (results[0][i] - expected_a[i]).abs() < 1e-6,
1632 "tensor 0 index {i}: {} != {}",
1633 results[0][i],
1634 expected_a[i]
1635 );
1636 assert!(
1637 (results[1][i] - expected_b[i]).abs() < 1e-6,
1638 "tensor 1 index {i}: {} != {}",
1639 results[1][i],
1640 expected_b[i]
1641 );
1642 }
1643
1644 let mut shared_slot = Adam::new(0.1f32);
1650 let _ = shared_slot
1651 .step_indexed(0, ¶ms_a, &grads_a)
1652 .expect("shared-slot step_indexed(0) [a] must succeed");
1653 let shared_slot_b = shared_slot
1654 .step_indexed(0, ¶ms_b, &grads_b)
1655 .expect("shared-slot step_indexed(0) [b] must succeed");
1656 let materially_different = (0..2).any(|i| (shared_slot_b[i] - expected_b[i]).abs() > 1e-4);
1657 assert!(
1658 materially_different,
1659 "expected sharing one state slot to diverge from independent per-tensor state, \
1660 got shared={shared_slot_b:?} independent={expected_b:?}"
1661 );
1662 }
1663
1664 #[test]
1665 fn test_tpu_step_distributed_matches_single_device() {
1666 let config = TPUConfig {
1669 enable_pod_coordination: true,
1670 pod_topology: PodTopology::Pod2x2,
1671 ..Default::default()
1672 };
1673 let mut optimizer = new_sgd_tpu(config);
1674 assert!(optimizer.pod_coordinator.is_some());
1675
1676 let params = Array1::from(vec![1.0f32, 2.0, 3.0]);
1677 let gradients = Array1::from(vec![2.0f32, 2.0, 2.0]);
1678
1679 let updated = optimizer
1680 .tpu_step(¶ms, &gradients)
1681 .expect("distributed tpu_step must succeed");
1682
1683 let expected = [0.8f32, 1.8, 2.8];
1685 for (&u, &e) in updated.iter().zip(expected.iter()) {
1686 assert!((u - e).abs() < 1e-6, "updated {u} != expected {e}");
1687 }
1688
1689 assert!(optimizer
1691 .profiler
1692 .timeline
1693 .iter()
1694 .any(|event| matches!(event.event_type, ProfileEventType::Communication)));
1695 }
1696
1697 #[test]
1698 fn test_tpu_step_shape_mismatch_errors() {
1699 let mut optimizer = new_sgd_tpu(TPUConfig::default());
1700 let params = Array1::from(vec![1.0f32, 2.0, 3.0]);
1701 let gradients = Array1::from(vec![1.0f32, 1.0]);
1702 assert!(optimizer.tpu_step(¶ms, &gradients).is_err());
1703 }
1704
1705 #[test]
1706 fn test_compile_to_tpu_produces_real_code_and_metrics() {
1707 let optimizer = new_sgd_tpu(TPUConfig::default());
1708 let shape = XLAShape {
1709 dimensions: [4, 1, 1, 1],
1710 rank: 1,
1711 element_type: XLAElementType::F32,
1712 };
1713 let graph = optimizer
1714 .build_optimizer_computation(&[shape, shape])
1715 .expect("graph build failed");
1716 let compiled = optimizer
1717 .compile_to_tpu(graph)
1718 .expect("compile_to_tpu failed");
1719
1720 assert!(
1723 compiled.code.len() > 4,
1724 "code too short: {}",
1725 compiled.code.len()
1726 );
1727 assert_eq!(&compiled.code[0..4], b"OTPU");
1728 assert!(
1729 compiled.code.iter().any(|&b| b != 0),
1730 "code must not be all zero"
1731 );
1732
1733 assert_eq!(compiled.perf_characteristics.flops, 24);
1740
1741 let util = compiled.perf_characteristics.utilization_estimate;
1743 assert!(util > 0.0 && util < 1.0, "utilization out of range: {util}");
1744
1745 assert!(compiled.perf_characteristics.estimated_execution_time_us >= 1);
1747
1748 assert!(compiled.memory_requirements.working_memory > 0);
1750 assert!(
1751 compiled.memory_requirements.total_memory
1752 >= compiled.memory_requirements.working_memory
1753 );
1754 }
1755
1756 #[test]
1757 fn test_compile_to_tpu_is_deterministic() {
1758 let optimizer = new_sgd_tpu(TPUConfig::default());
1759 let shape = XLAShape {
1760 dimensions: [8, 1, 1, 1],
1761 rank: 1,
1762 element_type: XLAElementType::F32,
1763 };
1764 let graph_a = optimizer
1765 .build_optimizer_computation(&[shape, shape])
1766 .expect("graph build failed");
1767 let graph_b = optimizer
1768 .build_optimizer_computation(&[shape, shape])
1769 .expect("graph build failed");
1770 let a = optimizer.compile_to_tpu(graph_a).expect("compile failed");
1771 let b = optimizer.compile_to_tpu(graph_b).expect("compile failed");
1772 assert_eq!(a.code, b.code);
1773 assert_eq!(a.id, b.id);
1774 }
1775
1776 #[test]
1777 fn test_optimizations_applied_counts_only_effective_passes() {
1778 let mut optimizer = new_sgd_tpu(TPUConfig::default());
1782 optimizer
1783 .initialize_xla_graph()
1784 .expect("xla graph init failed");
1785 let shape = XLAShape {
1786 dimensions: [4, 1, 1, 1],
1787 rank: 1,
1788 element_type: XLAElementType::F32,
1789 };
1790 optimizer
1791 .compile_step(&[shape, shape])
1792 .expect("compile_step failed");
1793
1794 assert_eq!(
1795 optimizer.profiler.compilation_metrics.optimizations_applied,
1796 0
1797 );
1798 assert!(optimizer.profiler.compilation_metrics.code_size > 0);
1799 }
1800}