Skip to main content

trustformers_optim/
hierarchical_aggregation.rs

1// reason: research-stage module — reserved API/scaffolding fields and methods
2// retained intentionally for in-progress features; not yet on active call paths.
3#![allow(dead_code)]
4
5use anyhow::Result;
6use std::collections::HashMap;
7use trustformers_core::parallel::CommunicationBackend;
8use trustformers_core::tensor::Tensor;
9
10/// Hierarchical aggregation strategies for distributed training
11///
12/// This module provides advanced hierarchical aggregation algorithms that optimize
13/// communication patterns for different network topologies and cluster configurations.
14/// It supports tree-based, ring-based, and butterfly aggregation patterns.
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum AggregationStrategy {
18    /// Binary tree aggregation (optimal for small clusters)
19    BinaryTree,
20    /// Ring-based aggregation (bandwidth-optimal)
21    Ring,
22    /// Butterfly aggregation (latency-optimal)
23    Butterfly,
24    /// Adaptive strategy that selects best algorithm based on cluster topology
25    Adaptive,
26}
27
28#[derive(Debug, Clone)]
29pub struct HierarchicalConfig {
30    /// Number of nodes in the cluster
31    pub num_nodes: usize,
32    /// Number of devices per node
33    pub devices_per_node: usize,
34    /// Node rank (0-based)
35    pub node_rank: usize,
36    /// Local rank within node
37    pub local_rank: usize,
38    /// Global rank across all nodes
39    pub global_rank: usize,
40    /// Aggregation strategy
41    pub strategy: AggregationStrategy,
42    /// Communication backend
43    pub comm_backend: CommunicationBackend,
44    /// Enable compression during aggregation
45    pub enable_compression: bool,
46    /// Compression threshold (only compress if savings > threshold)
47    pub compression_threshold: f32,
48    /// Enable fault tolerance
49    pub enable_fault_tolerance: bool,
50    /// Timeout for communication operations (ms)
51    pub comm_timeout_ms: u64,
52}
53
54impl Default for HierarchicalConfig {
55    fn default() -> Self {
56        Self {
57            num_nodes: 1,
58            devices_per_node: 1,
59            node_rank: 0,
60            local_rank: 0,
61            global_rank: 0,
62            strategy: AggregationStrategy::Adaptive,
63            comm_backend: CommunicationBackend::Mpi,
64            enable_compression: true,
65            compression_threshold: 0.1,
66            enable_fault_tolerance: true,
67            comm_timeout_ms: 30000,
68        }
69    }
70}
71
72impl HierarchicalConfig {
73    pub fn new(
74        num_nodes: usize,
75        devices_per_node: usize,
76        node_rank: usize,
77        local_rank: usize,
78    ) -> Self {
79        let global_rank = node_rank * devices_per_node + local_rank;
80        Self {
81            num_nodes,
82            devices_per_node,
83            node_rank,
84            local_rank,
85            global_rank,
86            ..Default::default()
87        }
88    }
89
90    pub fn world_size(&self) -> usize {
91        self.num_nodes * self.devices_per_node
92    }
93
94    pub fn is_master(&self) -> bool {
95        self.global_rank == 0
96    }
97
98    pub fn is_node_master(&self) -> bool {
99        self.local_rank == 0
100    }
101}
102
103/// Hierarchical aggregation coordinator
104pub struct HierarchicalAggregator {
105    config: HierarchicalConfig,
106    node_topology: NodeTopology,
107    communication_groups: CommunicationGroups,
108    aggregation_stats: AggregationStats,
109    fault_detector: Option<FaultDetector>,
110}
111
112/// Network topology representation
113#[derive(Debug, Clone)]
114pub struct NodeTopology {
115    /// Adjacency matrix for inter-node connectivity
116    pub node_adjacency: Vec<Vec<bool>>,
117    /// Bandwidth matrix between nodes (MB/s)
118    pub node_bandwidth: Vec<Vec<f32>>,
119    /// Latency matrix between nodes (ms)
120    pub node_latency: Vec<Vec<f32>>,
121    /// Intra-node connectivity (assumed full connectivity)
122    pub intra_node_bandwidth: f32,
123    /// Intra-node latency
124    pub intra_node_latency: f32,
125}
126
127/// Communication groups for hierarchical operations
128#[derive(Debug, Clone)]
129pub struct CommunicationGroups {
130    /// Ranks within the same node
131    pub node_local_group: Vec<usize>,
132    /// Node master ranks for cross-node communication
133    pub cross_node_group: Vec<usize>,
134    /// Binary tree structure for tree-based aggregation
135    pub tree_structure: TreeStructure,
136    /// Ring structure for ring-based aggregation
137    pub ring_structure: RingStructure,
138    /// Butterfly structure for butterfly aggregation
139    pub butterfly_structure: ButterflyStructure,
140}
141
142#[derive(Debug, Clone)]
143pub struct TreeStructure {
144    /// Parent rank in the tree (-1 if root)
145    pub parent: Option<usize>,
146    /// Children ranks in the tree
147    pub children: Vec<usize>,
148    /// Tree depth
149    pub depth: usize,
150    /// Tree height
151    pub height: usize,
152}
153
154#[derive(Debug, Clone)]
155pub struct RingStructure {
156    /// Next rank in the ring
157    pub next_rank: usize,
158    /// Previous rank in the ring
159    pub prev_rank: usize,
160    /// Ring size
161    pub ring_size: usize,
162}
163
164#[derive(Debug, Clone)]
165pub struct ButterflyStructure {
166    /// Butterfly connections for each stage
167    pub connections: Vec<Vec<usize>>,
168    /// Number of stages
169    pub num_stages: usize,
170}
171
172/// Aggregation operation statistics
173#[derive(Debug, Clone)]
174pub struct AggregationStats {
175    /// Total number of aggregation operations
176    pub total_operations: usize,
177    /// Average aggregation time (ms)
178    pub avg_aggregation_time: f32,
179    /// Total bytes transferred
180    pub total_bytes_transferred: usize,
181    /// Compression ratio achieved
182    pub compression_ratio: f32,
183    /// Number of failed operations
184    pub failed_operations: usize,
185    /// Strategy selection history
186    pub strategy_history: HashMap<AggregationStrategy, usize>,
187}
188
189/// Fault detection and recovery
190#[derive(Debug)]
191pub struct FaultDetector {
192    /// Failed nodes
193    pub failed_nodes: Vec<usize>,
194    /// Timeout threshold for detecting failures
195    pub timeout_threshold: u64,
196    /// Recovery strategy
197    pub recovery_strategy: RecoveryStrategy,
198}
199
200#[derive(Debug, Clone)]
201pub enum RecoveryStrategy {
202    /// Skip failed nodes and continue
203    Skip,
204    /// Retry with backup nodes
205    Retry,
206    /// Abort aggregation
207    Abort,
208}
209
210impl Default for AggregationStats {
211    fn default() -> Self {
212        Self {
213            total_operations: 0,
214            avg_aggregation_time: 0.0,
215            total_bytes_transferred: 0,
216            compression_ratio: 1.0,
217            failed_operations: 0,
218            strategy_history: HashMap::new(),
219        }
220    }
221}
222
223impl HierarchicalAggregator {
224    pub fn new(config: HierarchicalConfig) -> Result<Self> {
225        let node_topology = Self::detect_network_topology(&config)?;
226        let communication_groups = Self::build_communication_groups(&config, &node_topology)?;
227        let aggregation_stats = AggregationStats::default();
228
229        let fault_detector = if config.enable_fault_tolerance {
230            Some(FaultDetector {
231                failed_nodes: Vec::new(),
232                timeout_threshold: config.comm_timeout_ms,
233                recovery_strategy: RecoveryStrategy::Skip,
234            })
235        } else {
236            None
237        };
238
239        Ok(Self {
240            config,
241            node_topology,
242            communication_groups,
243            aggregation_stats,
244            fault_detector,
245        })
246    }
247
248    /// Detect network topology and measure bandwidth/latency
249    fn detect_network_topology(config: &HierarchicalConfig) -> Result<NodeTopology> {
250        let num_nodes = config.num_nodes;
251
252        // Initialize topology matrices
253        let mut node_adjacency = vec![vec![false; num_nodes]; num_nodes];
254        let mut node_bandwidth = vec![vec![0.0; num_nodes]; num_nodes];
255        let mut node_latency = vec![vec![0.0; num_nodes]; num_nodes];
256
257        // For this implementation, assume full connectivity with estimated values
258        // In practice, these would be measured through benchmarking
259        for i in 0..num_nodes {
260            for j in 0..num_nodes {
261                if i != j {
262                    node_adjacency[i][j] = true;
263                    // Estimate bandwidth based on network topology
264                    node_bandwidth[i][j] = if (i as i32 - j as i32).abs() == 1 {
265                        10000.0 // Adjacent nodes: 10 GB/s
266                    } else {
267                        1000.0 // Non-adjacent nodes: 1 GB/s
268                    };
269                    // Estimate latency
270                    node_latency[i][j] = if (i as i32 - j as i32).abs() == 1 {
271                        0.1 // Adjacent nodes: 0.1ms
272                    } else {
273                        1.0 // Non-adjacent nodes: 1ms
274                    };
275                } else {
276                    node_adjacency[i][j] = false;
277                    node_bandwidth[i][j] = f32::INFINITY;
278                    node_latency[i][j] = 0.0;
279                }
280            }
281        }
282
283        Ok(NodeTopology {
284            node_adjacency,
285            node_bandwidth,
286            node_latency,
287            intra_node_bandwidth: 80000.0, // 80 GB/s intra-node
288            intra_node_latency: 0.01,      // 0.01ms intra-node
289        })
290    }
291
292    /// Build communication groups for different aggregation strategies
293    fn build_communication_groups(
294        config: &HierarchicalConfig,
295        topology: &NodeTopology,
296    ) -> Result<CommunicationGroups> {
297        // Node-local group
298        let node_local_group: Vec<usize> = (0..config.devices_per_node)
299            .map(|i| config.node_rank * config.devices_per_node + i)
300            .collect();
301
302        // Cross-node group (node masters)
303        let cross_node_group: Vec<usize> =
304            (0..config.num_nodes).map(|i| i * config.devices_per_node).collect();
305
306        // Build tree structure
307        let tree_structure = Self::build_tree_structure(config, topology)?;
308
309        // Build ring structure
310        let ring_structure = Self::build_ring_structure(config)?;
311
312        // Build butterfly structure
313        let butterfly_structure = Self::build_butterfly_structure(config)?;
314
315        Ok(CommunicationGroups {
316            node_local_group,
317            cross_node_group,
318            tree_structure,
319            ring_structure,
320            butterfly_structure,
321        })
322    }
323
324    /// Build binary tree structure for tree-based aggregation
325    fn build_tree_structure(
326        config: &HierarchicalConfig,
327        _topology: &NodeTopology,
328    ) -> Result<TreeStructure> {
329        let world_size = config.world_size();
330        let rank = config.global_rank;
331
332        // Build binary tree
333        let parent = if rank == 0 { None } else { Some((rank - 1) / 2) };
334
335        let mut children = Vec::new();
336        let left_child = 2 * rank + 1;
337        let right_child = 2 * rank + 2;
338
339        if left_child < world_size {
340            children.push(left_child);
341        }
342        if right_child < world_size {
343            children.push(right_child);
344        }
345
346        // Calculate depth and height
347        let depth = (rank as f32).log2().floor() as usize;
348        let height = (world_size as f32).log2().ceil() as usize;
349
350        Ok(TreeStructure {
351            parent,
352            children,
353            depth,
354            height,
355        })
356    }
357
358    /// Build ring structure for ring-based aggregation
359    fn build_ring_structure(config: &HierarchicalConfig) -> Result<RingStructure> {
360        let world_size = config.world_size();
361        let rank = config.global_rank;
362
363        let next_rank = (rank + 1) % world_size;
364        let prev_rank = (rank + world_size - 1) % world_size;
365
366        Ok(RingStructure {
367            next_rank,
368            prev_rank,
369            ring_size: world_size,
370        })
371    }
372
373    /// Build butterfly structure for butterfly aggregation
374    fn build_butterfly_structure(config: &HierarchicalConfig) -> Result<ButterflyStructure> {
375        let world_size = config.world_size();
376        let rank = config.global_rank;
377        let num_stages = (world_size as f32).log2().ceil() as usize;
378
379        let mut connections = Vec::new();
380
381        for stage in 0..num_stages {
382            let mut stage_connections = Vec::new();
383            let distance = 1 << stage;
384
385            // XOR-based butterfly connections
386            let partner = rank ^ distance;
387            if partner < world_size {
388                stage_connections.push(partner);
389            }
390
391            connections.push(stage_connections);
392        }
393
394        Ok(ButterflyStructure {
395            connections,
396            num_stages,
397        })
398    }
399
400    /// Perform hierarchical all-reduce operation
401    pub fn hierarchical_all_reduce(
402        &mut self,
403        gradients: &mut HashMap<String, Tensor>,
404    ) -> Result<()> {
405        let start_time = std::time::Instant::now();
406
407        // Select optimal strategy based on configuration and topology
408        let strategy = self.select_optimal_strategy(gradients)?;
409
410        // Perform aggregation based on selected strategy
411        match strategy {
412            AggregationStrategy::BinaryTree => {
413                self.tree_based_all_reduce(gradients)?;
414            },
415            AggregationStrategy::Ring => {
416                self.ring_based_all_reduce(gradients)?;
417            },
418            AggregationStrategy::Butterfly => {
419                self.butterfly_based_all_reduce(gradients)?;
420            },
421            AggregationStrategy::Adaptive => {
422                // Adaptive strategy selects the best algorithm dynamically
423                let optimal_strategy = self.adaptive_strategy_selection(gradients)?;
424                match optimal_strategy {
425                    AggregationStrategy::BinaryTree => self.tree_based_all_reduce(gradients)?,
426                    AggregationStrategy::Ring => self.ring_based_all_reduce(gradients)?,
427                    AggregationStrategy::Butterfly => self.butterfly_based_all_reduce(gradients)?,
428                    AggregationStrategy::Adaptive => {
429                        return Err(anyhow::anyhow!(
430                            "Invalid adaptive strategy selection: recursive Adaptive strategy returned"
431                        ));
432                    },
433                }
434            },
435        }
436
437        // Update statistics
438        let elapsed = start_time.elapsed().as_millis() as f32;
439        self.update_aggregation_stats(strategy, elapsed, gradients)?;
440
441        Ok(())
442    }
443
444    /// Select optimal aggregation strategy
445    fn select_optimal_strategy(
446        &self,
447        gradients: &HashMap<String, Tensor>,
448    ) -> Result<AggregationStrategy> {
449        match self.config.strategy {
450            AggregationStrategy::Adaptive => self.adaptive_strategy_selection(gradients),
451            strategy => Ok(strategy),
452        }
453    }
454
455    /// Adaptive strategy selection based on cluster topology and data characteristics
456    fn adaptive_strategy_selection(
457        &self,
458        gradients: &HashMap<String, Tensor>,
459    ) -> Result<AggregationStrategy> {
460        let world_size = self.config.world_size();
461        let num_nodes = self.config.num_nodes;
462
463        // Calculate total data size
464        let total_data_size: usize = gradients.values().map(|tensor| tensor.memory_usage()).sum();
465
466        // Strategy selection heuristics
467        if world_size <= 8 {
468            // Small clusters: tree is optimal
469            Ok(AggregationStrategy::BinaryTree)
470        } else if total_data_size > 100 * 1024 * 1024 {
471            // Large data: ring is bandwidth-optimal
472            Ok(AggregationStrategy::Ring)
473        } else if num_nodes > 16 {
474            // Large clusters with small data: butterfly is latency-optimal
475            Ok(AggregationStrategy::Butterfly)
476        } else {
477            // Default to tree for medium-sized clusters
478            Ok(AggregationStrategy::BinaryTree)
479        }
480    }
481
482    /// Tree-based all-reduce (divide-and-conquer)
483    fn tree_based_all_reduce(&mut self, gradients: &mut HashMap<String, Tensor>) -> Result<()> {
484        let tree = self.communication_groups.tree_structure.clone();
485
486        // Phase 1: Reduce up the tree
487        self.tree_reduce_up(gradients, &tree)?;
488
489        // Phase 2: Broadcast down the tree
490        self.tree_broadcast_down(gradients, &tree)?;
491
492        Ok(())
493    }
494
495    /// Ring-based all-reduce (bandwidth-optimal)
496    fn ring_based_all_reduce(&mut self, gradients: &mut HashMap<String, Tensor>) -> Result<()> {
497        let ring = self.communication_groups.ring_structure.clone();
498
499        // Phase 1: Reduce-scatter
500        self.ring_reduce_scatter(gradients, &ring)?;
501
502        // Phase 2: All-gather
503        self.ring_all_gather(gradients, &ring)?;
504
505        Ok(())
506    }
507
508    /// Butterfly-based all-reduce (latency-optimal)
509    fn butterfly_based_all_reduce(
510        &mut self,
511        gradients: &mut HashMap<String, Tensor>,
512    ) -> Result<()> {
513        let butterfly = self.communication_groups.butterfly_structure.clone();
514
515        // Butterfly all-reduce in multiple stages
516        for stage in 0..butterfly.num_stages {
517            self.butterfly_stage_operation(gradients, &butterfly, stage)?;
518        }
519
520        Ok(())
521    }
522
523    /// Tree reduce-up phase
524    fn tree_reduce_up(
525        &mut self,
526        gradients: &mut HashMap<String, Tensor>,
527        tree: &TreeStructure,
528    ) -> Result<()> {
529        // Collect gradients from children
530        for &child_rank in &tree.children {
531            for (name, gradient) in gradients.iter_mut() {
532                // Simulate receiving gradient from child
533                let child_gradient = self.simulate_receive_gradient(child_rank, name)?;
534                *gradient = gradient.add(&child_gradient)?;
535            }
536        }
537
538        // Send reduced gradients to parent
539        if let Some(parent_rank) = tree.parent {
540            for (name, gradient) in gradients.iter() {
541                self.simulate_send_gradient(parent_rank, name, gradient)?;
542            }
543        }
544
545        Ok(())
546    }
547
548    /// Tree broadcast-down phase
549    fn tree_broadcast_down(
550        &mut self,
551        gradients: &mut HashMap<String, Tensor>,
552        tree: &TreeStructure,
553    ) -> Result<()> {
554        // Receive final gradients from parent
555        if let Some(parent_rank) = tree.parent {
556            for (name, gradient) in gradients.iter_mut() {
557                *gradient = self.simulate_receive_gradient(parent_rank, name)?;
558            }
559        }
560
561        // Broadcast to children
562        for &child_rank in &tree.children {
563            for (name, gradient) in gradients.iter() {
564                self.simulate_send_gradient(child_rank, name, gradient)?;
565            }
566        }
567
568        Ok(())
569    }
570
571    /// Ring reduce-scatter phase
572    fn ring_reduce_scatter(
573        &mut self,
574        gradients: &mut HashMap<String, Tensor>,
575        ring: &RingStructure,
576    ) -> Result<()> {
577        let num_chunks = ring.ring_size;
578        let rank = self.config.global_rank;
579
580        for step in 0..num_chunks - 1 {
581            let _send_chunk = (rank + ring.ring_size - step) % ring.ring_size;
582            let _recv_chunk = (rank + ring.ring_size - step - 1) % ring.ring_size;
583
584            // Send to next rank and receive from previous rank
585            for (name, gradient) in gradients.iter_mut() {
586                let chunk_gradient = self.simulate_receive_gradient(ring.prev_rank, name)?;
587                *gradient = gradient.add(&chunk_gradient)?;
588                self.simulate_send_gradient(ring.next_rank, name, gradient)?;
589            }
590        }
591
592        Ok(())
593    }
594
595    /// Ring all-gather phase
596    fn ring_all_gather(
597        &mut self,
598        gradients: &mut HashMap<String, Tensor>,
599        ring: &RingStructure,
600    ) -> Result<()> {
601        let num_chunks = ring.ring_size;
602
603        for _step in 0..num_chunks - 1 {
604            // Send to next rank and receive from previous rank
605            for (name, gradient) in gradients.iter_mut() {
606                let chunk_gradient = self.simulate_receive_gradient(ring.prev_rank, name)?;
607                *gradient = gradient.add(&chunk_gradient)?;
608                self.simulate_send_gradient(ring.next_rank, name, gradient)?;
609            }
610        }
611
612        Ok(())
613    }
614
615    /// Butterfly stage operation
616    fn butterfly_stage_operation(
617        &mut self,
618        gradients: &mut HashMap<String, Tensor>,
619        butterfly: &ButterflyStructure,
620        stage: usize,
621    ) -> Result<()> {
622        if stage < butterfly.connections.len() {
623            for &partner_rank in &butterfly.connections[stage] {
624                for (name, gradient) in gradients.iter_mut() {
625                    // Exchange gradients with partner
626                    let partner_gradient = self.simulate_receive_gradient(partner_rank, name)?;
627                    *gradient = gradient.add(&partner_gradient)?;
628                    self.simulate_send_gradient(partner_rank, name, gradient)?;
629                }
630            }
631        }
632
633        Ok(())
634    }
635
636    /// Simulate receiving gradient from another rank
637    fn simulate_receive_gradient(&self, _from_rank: usize, _name: &str) -> Result<Tensor> {
638        // In a real implementation, this would use MPI or other communication backend
639        // For this implementation, we'll create a dummy tensor
640        Ok(Tensor::zeros(&[1])?)
641    }
642
643    /// Simulate sending gradient to another rank
644    fn simulate_send_gradient(
645        &self,
646        _to_rank: usize,
647        _name: &str,
648        _gradient: &Tensor,
649    ) -> Result<()> {
650        // In a real implementation, this would use MPI or other communication backend
651        Ok(())
652    }
653
654    /// Update aggregation statistics
655    fn update_aggregation_stats(
656        &mut self,
657        strategy: AggregationStrategy,
658        elapsed_ms: f32,
659        gradients: &HashMap<String, Tensor>,
660    ) -> Result<()> {
661        let stats = &mut self.aggregation_stats;
662
663        stats.total_operations += 1;
664        stats.avg_aggregation_time =
665            (stats.avg_aggregation_time * (stats.total_operations - 1) as f32 + elapsed_ms)
666                / stats.total_operations as f32;
667
668        let bytes_transferred: usize = gradients.values().map(|tensor| tensor.memory_usage()).sum();
669        stats.total_bytes_transferred += bytes_transferred;
670
671        *stats.strategy_history.entry(strategy).or_insert(0) += 1;
672
673        Ok(())
674    }
675
676    /// Get current aggregation statistics
677    pub fn get_stats(&self) -> &AggregationStats {
678        &self.aggregation_stats
679    }
680
681    /// Reset aggregation statistics
682    pub fn reset_stats(&mut self) {
683        self.aggregation_stats = AggregationStats::default();
684    }
685
686    /// Get recommended strategy for current configuration
687    pub fn get_recommended_strategy(&self) -> AggregationStrategy {
688        let world_size = self.config.world_size();
689        let num_nodes = self.config.num_nodes;
690
691        if world_size <= 8 {
692            AggregationStrategy::BinaryTree
693        } else if num_nodes > 16 {
694            AggregationStrategy::Butterfly
695        } else {
696            AggregationStrategy::Ring
697        }
698    }
699}
700
701#[cfg(test)]
702mod tests {
703    use super::*;
704
705    #[test]
706    fn test_hierarchical_config() {
707        let config = HierarchicalConfig::new(4, 8, 2, 3);
708        assert_eq!(config.num_nodes, 4);
709        assert_eq!(config.devices_per_node, 8);
710        assert_eq!(config.node_rank, 2);
711        assert_eq!(config.local_rank, 3);
712        assert_eq!(config.global_rank, 19);
713        assert_eq!(config.world_size(), 32);
714        assert!(!config.is_master());
715        assert!(!config.is_node_master());
716    }
717
718    #[test]
719    fn test_tree_structure_building() {
720        let config = HierarchicalConfig::new(2, 4, 0, 0);
721        let topology = HierarchicalAggregator::detect_network_topology(&config)
722            .expect("Operation failed in test");
723        let tree = HierarchicalAggregator::build_tree_structure(&config, &topology)
724            .expect("Operation failed in test");
725
726        assert_eq!(tree.parent, None); // Root node
727        assert_eq!(tree.children, vec![1, 2]);
728        assert_eq!(tree.depth, 0);
729    }
730
731    #[test]
732    fn test_ring_structure_building() {
733        let config = HierarchicalConfig::new(2, 4, 0, 1);
734        let ring = HierarchicalAggregator::build_ring_structure(&config)
735            .expect("Operation failed in test");
736
737        assert_eq!(ring.next_rank, 2);
738        assert_eq!(ring.prev_rank, 0);
739        assert_eq!(ring.ring_size, 8);
740    }
741
742    #[test]
743    fn test_adaptive_strategy_selection() {
744        let config = HierarchicalConfig::new(4, 4, 0, 0);
745        let aggregator = HierarchicalAggregator::new(config).expect("Construction failed");
746
747        let mut gradients = HashMap::new();
748        // Create a large tensor that exceeds 100MB threshold: 8000x8000x4bytes = 256MB
749        gradients.insert(
750            "param1".to_string(),
751            Tensor::zeros(&[8000, 8000]).expect("Failed to create tensor"),
752        );
753
754        let strategy = aggregator
755            .adaptive_strategy_selection(&gradients)
756            .expect("Operation failed in test");
757        // Should select ring for large data
758        assert!(matches!(strategy, AggregationStrategy::Ring));
759    }
760
761    #[test]
762    fn test_aggregation_stats_update() {
763        let config = HierarchicalConfig::new(2, 2, 0, 0);
764        let mut aggregator = HierarchicalAggregator::new(config).expect("Construction failed");
765
766        let mut gradients = HashMap::new();
767        gradients.insert(
768            "param1".to_string(),
769            Tensor::zeros(&[10, 10]).expect("Failed to create tensor"),
770        );
771
772        aggregator
773            .update_aggregation_stats(AggregationStrategy::BinaryTree, 100.0, &gradients)
774            .expect("Operation failed in test");
775
776        let stats = aggregator.get_stats();
777        assert_eq!(stats.total_operations, 1);
778        assert_eq!(stats.avg_aggregation_time, 100.0);
779        assert_eq!(
780            stats.strategy_history.get(&AggregationStrategy::BinaryTree),
781            Some(&1)
782        );
783    }
784
785    #[test]
786    fn test_recommended_strategy() {
787        let small_config = HierarchicalConfig::new(2, 2, 0, 0);
788        let small_aggregator =
789            HierarchicalAggregator::new(small_config).expect("Construction failed");
790        assert!(matches!(
791            small_aggregator.get_recommended_strategy(),
792            AggregationStrategy::BinaryTree
793        ));
794
795        let large_config = HierarchicalConfig::new(20, 1, 0, 0);
796        let large_aggregator =
797            HierarchicalAggregator::new(large_config).expect("Construction failed");
798        assert!(matches!(
799            large_aggregator.get_recommended_strategy(),
800            AggregationStrategy::Butterfly
801        ));
802    }
803
804    #[test]
805    fn test_butterfly_structure() {
806        let config = HierarchicalConfig::new(1, 8, 0, 0);
807        let butterfly = HierarchicalAggregator::build_butterfly_structure(&config)
808            .expect("Operation failed in test");
809
810        assert_eq!(butterfly.num_stages, 3); // log2(8) = 3
811        assert_eq!(butterfly.connections.len(), 3);
812    }
813
814    #[test]
815    fn test_network_topology_detection() {
816        let config = HierarchicalConfig::new(3, 2, 0, 0);
817        let topology = HierarchicalAggregator::detect_network_topology(&config)
818            .expect("Operation failed in test");
819
820        assert_eq!(topology.node_adjacency.len(), 3);
821        assert_eq!(topology.node_bandwidth.len(), 3);
822        assert_eq!(topology.node_latency.len(), 3);
823        assert!(topology.intra_node_bandwidth > 0.0);
824        assert!(topology.intra_node_latency > 0.0);
825    }
826}