//! Hybrid Parallelism Coordinator //! //! This module coordinates multiple parallelism strategies together: //! - Data Parallelism (DP): Replicate model, partition data //! - Tensor Parallelism (TP): Partition tensors within layers //! - Pipeline Parallelism (PP): Partition layers across stages //! - Sequence Parallelism (SP): Partition along sequence dimension //! //! Hybrid parallelism enables training models that are too large for //! single GPU memory while maximizing throughput. use crate::error::{DistributedError, Result}; use parking_lot::{Mutex, RwLock}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::time::{Duration, Instant}; // ============================================================================= // Configuration // ============================================================================= /// Parallelism dimension type #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ParallelDimension { /// Data parallelism Data, /// Tensor parallelism Tensor, /// Pipeline parallelism Pipeline, /// Sequence parallelism Sequence, /// Expert parallelism (for MoE) Expert, } /// Configuration for hybrid parallelism #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HybridParallelConfig { /// Total number of GPUs pub total_gpus: usize, /// Data parallel degree pub dp_degree: usize, /// Tensor parallel degree pub tp_degree: usize, /// Pipeline parallel degree pub pp_degree: usize, /// Sequence parallel degree (usually equals tp_degree) pub sp_degree: usize, /// Expert parallel degree (for MoE models) pub ep_degree: usize, /// Enable sequence parallelism pub enable_sequence_parallel: bool, /// Enable activation checkpointing pub enable_checkpointing: bool, /// Number of micro-batches for pipeline pub num_micro_batches: usize, /// Overlap communication with computation pub overlap_comm: bool, } impl Default for HybridParallelConfig { fn default() -> Self { Self { total_gpus: 8, dp_degree: 2, tp_degree: 2, pp_degree: 2, sp_degree: 1, ep_degree: 1, enable_sequence_parallel: false, enable_checkpointing: true, num_micro_batches: 4, overlap_comm: true, } } } impl HybridParallelConfig { /// Validate configuration pub fn validate(&self) -> Result<()> { let product = self.dp_degree * self.tp_degree * self.pp_degree; if product != self.total_gpus { return Err(DistributedError::configuration(format!( "DP({}) x TP({}) x PP({}) = {} must equal total_gpus ({})", self.dp_degree, self.tp_degree, self.pp_degree, product, self.total_gpus ))); } if self.enable_sequence_parallel && self.sp_degree != self.tp_degree { return Err(DistributedError::configuration( "Sequence parallel degree must equal tensor parallel degree", )); } Ok(()) } /// Create for data parallelism only pub fn data_parallel(num_gpus: usize) -> Self { Self { total_gpus: num_gpus, dp_degree: num_gpus, tp_degree: 1, pp_degree: 1, sp_degree: 1, ..Default::default() } } /// Create for tensor + data parallelism pub fn tensor_data_parallel(tp_degree: usize, dp_degree: usize) -> Self { Self { total_gpus: tp_degree * dp_degree, dp_degree, tp_degree, pp_degree: 1, sp_degree: 1, ..Default::default() } } /// Create for 3D parallelism pub fn parallelism_3d(dp_degree: usize, tp_degree: usize, pp_degree: usize) -> Self { Self { total_gpus: dp_degree * tp_degree * pp_degree, dp_degree, tp_degree, pp_degree, sp_degree: 1, ..Default::default() } } } // ============================================================================= // Process Group Mesh // ============================================================================= /// Represents a GPU in the parallel mesh #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MeshCoordinate { /// Global rank (0 to total_gpus - 1) pub global_rank: usize, /// Data parallel rank pub dp_rank: usize, /// Tensor parallel rank pub tp_rank: usize, /// Pipeline parallel rank (stage) pub pp_rank: usize, /// Device ID pub device_id: i32, } impl MeshCoordinate { /// Check if this is the first TP rank pub fn is_tp_first(&self) -> bool { self.tp_rank == 0 } /// Check if this is the last TP rank pub fn is_tp_last(&self, tp_degree: usize) -> bool { self.tp_rank == tp_degree - 1 } /// Check if this is the first PP stage pub fn is_pp_first(&self) -> bool { self.pp_rank == 0 } /// Check if this is the last PP stage pub fn is_pp_last(&self, pp_degree: usize) -> bool { self.pp_rank == pp_degree - 1 } } /// Process group for a subset of GPUs #[derive(Debug, Clone)] pub struct ProcessGroupInfo { /// Group name pub name: String, /// Parallelism dimension pub dimension: ParallelDimension, /// Ranks in this group pub ranks: Vec, /// This process's rank within the group pub local_rank: usize, /// Group size pub size: usize, } impl ProcessGroupInfo { /// Create a new process group info pub fn new( name: String, dimension: ParallelDimension, ranks: Vec, local_rank: usize, ) -> Self { let size = ranks.len(); Self { name, dimension, ranks, local_rank, size, } } /// Check if rank is in this group pub fn contains(&self, rank: usize) -> bool { self.ranks.contains(&rank) } } /// Manages the GPU mesh for hybrid parallelism pub struct ProcessGroupMesh { /// Configuration config: HybridParallelConfig, /// This process's global rank global_rank: usize, /// Mesh coordinate for this process coordinate: MeshCoordinate, /// Data parallel group dp_group: ProcessGroupInfo, /// Tensor parallel group tp_group: ProcessGroupInfo, /// Pipeline parallel group pp_group: ProcessGroupInfo, /// All process groups by name groups: HashMap, } impl ProcessGroupMesh { /// Create a new process group mesh pub fn new(config: HybridParallelConfig, global_rank: usize) -> Result { config.validate()?; if global_rank >= config.total_gpus { return Err(DistributedError::configuration(format!( "Global rank {} exceeds total GPUs {}", global_rank, config.total_gpus ))); } // Calculate mesh coordinates // Layout: [DP, TP, PP] with PP varying fastest let pp_rank = global_rank % config.pp_degree; let tp_rank = (global_rank / config.pp_degree) % config.tp_degree; let dp_rank = global_rank / (config.pp_degree * config.tp_degree); let coordinate = MeshCoordinate { global_rank, dp_rank, tp_rank, pp_rank, device_id: global_rank as i32, }; // Build process groups let dp_group = Self::build_dp_group(&config, &coordinate); let tp_group = Self::build_tp_group(&config, &coordinate); let pp_group = Self::build_pp_group(&config, &coordinate); let mut groups = HashMap::new(); groups.insert(dp_group.name.clone(), dp_group.clone()); groups.insert(tp_group.name.clone(), tp_group.clone()); groups.insert(pp_group.name.clone(), pp_group.clone()); Ok(Self { config, global_rank, coordinate, dp_group, tp_group, pp_group, groups, }) } /// Build data parallel group (same TP and PP ranks) fn build_dp_group(config: &HybridParallelConfig, coord: &MeshCoordinate) -> ProcessGroupInfo { let mut ranks = Vec::new(); for dp in 0..config.dp_degree { let rank = dp * config.pp_degree * config.tp_degree + coord.tp_rank * config.pp_degree + coord.pp_rank; ranks.push(rank); } ProcessGroupInfo::new( format!("dp_tp{}_pp{}", coord.tp_rank, coord.pp_rank), ParallelDimension::Data, ranks, coord.dp_rank, ) } /// Build tensor parallel group (same DP and PP ranks) fn build_tp_group(config: &HybridParallelConfig, coord: &MeshCoordinate) -> ProcessGroupInfo { let mut ranks = Vec::new(); for tp in 0..config.tp_degree { let rank = coord.dp_rank * config.pp_degree * config.tp_degree + tp * config.pp_degree + coord.pp_rank; ranks.push(rank); } ProcessGroupInfo::new( format!("tp_dp{}_pp{}", coord.dp_rank, coord.pp_rank), ParallelDimension::Tensor, ranks, coord.tp_rank, ) } /// Build pipeline parallel group (same DP and TP ranks) fn build_pp_group(config: &HybridParallelConfig, coord: &MeshCoordinate) -> ProcessGroupInfo { let mut ranks = Vec::new(); for pp in 0..config.pp_degree { let rank = coord.dp_rank * config.pp_degree * config.tp_degree + coord.tp_rank * config.pp_degree + pp; ranks.push(rank); } ProcessGroupInfo::new( format!("pp_dp{}_tp{}", coord.dp_rank, coord.tp_rank), ParallelDimension::Pipeline, ranks, coord.pp_rank, ) } /// Get mesh coordinate pub fn coordinate(&self) -> &MeshCoordinate { &self.coordinate } /// Get data parallel group pub fn dp_group(&self) -> &ProcessGroupInfo { &self.dp_group } /// Get tensor parallel group pub fn tp_group(&self) -> &ProcessGroupInfo { &self.tp_group } /// Get pipeline parallel group pub fn pp_group(&self) -> &ProcessGroupInfo { &self.pp_group } /// Get group by name pub fn get_group(&self, name: &str) -> Option<&ProcessGroupInfo> { self.groups.get(name) } /// Get all group names pub fn group_names(&self) -> Vec { self.groups.keys().cloned().collect() } /// Get global rank pub fn global_rank(&self) -> usize { self.global_rank } /// Get config pub fn config(&self) -> &HybridParallelConfig { &self.config } /// Check if this rank should participate in data parallel AllReduce pub fn should_dp_allreduce(&self) -> bool { self.config.dp_degree > 1 } /// Check if this rank should participate in tensor parallel AllReduce pub fn should_tp_allreduce(&self) -> bool { self.config.tp_degree > 1 } /// Get the next rank in pipeline (for send) pub fn next_pp_rank(&self) -> Option { if self.coordinate.pp_rank < self.config.pp_degree - 1 { Some(self.pp_group.ranks[self.coordinate.pp_rank + 1]) } else { None } } /// Get the previous rank in pipeline (for recv) pub fn prev_pp_rank(&self) -> Option { if self.coordinate.pp_rank > 0 { Some(self.pp_group.ranks[self.coordinate.pp_rank - 1]) } else { None } } } // ============================================================================= // Hybrid Parallelism Coordinator // ============================================================================= /// Coordinates hybrid parallelism for distributed training pub struct HybridParallelCoordinator { /// Configuration config: HybridParallelConfig, /// Process group mesh mesh: ProcessGroupMesh, /// Current training step step: AtomicU64, /// Is training active active: AtomicBool, /// Pending communications pending_comms: Mutex>, /// Statistics stats: RwLock, } /// A pending communication #[derive(Debug, Clone)] pub struct PendingComm { /// Communication ID pub id: u64, /// Operation type pub op: CommOp, /// Parallelism dimension pub dimension: ParallelDimension, /// Source rank pub src: usize, /// Destination ranks pub dst: Vec, /// Data size in bytes pub size_bytes: usize, /// Creation time pub created_at: Instant, } /// Communication operation type #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CommOp { /// AllReduce for gradient synchronization AllReduce, /// AllGather for tensor parallel AllGather, /// ReduceScatter for FSDP ReduceScatter, /// Send to next pipeline stage Send, /// Recv from previous pipeline stage Recv, /// Broadcast Broadcast, } impl HybridParallelCoordinator { /// Create a new hybrid parallelism coordinator pub fn new(config: HybridParallelConfig, global_rank: usize) -> Result { let mesh = ProcessGroupMesh::new(config.clone(), global_rank)?; Ok(Self { config, mesh, step: AtomicU64::new(0), active: AtomicBool::new(false), pending_comms: Mutex::new(Vec::new()), stats: RwLock::new(CoordinatorStats::default()), }) } /// Start training pub fn start(&self) { self.active.store(true, Ordering::SeqCst); } /// Stop training pub fn stop(&self) { self.active.store(false, Ordering::SeqCst); } /// Check if active pub fn is_active(&self) -> bool { self.active.load(Ordering::SeqCst) } /// Advance to next step pub fn next_step(&self) -> u64 { self.step.fetch_add(1, Ordering::SeqCst) + 1 } /// Get current step pub fn current_step(&self) -> u64 { self.step.load(Ordering::SeqCst) } /// Schedule gradient AllReduce across data parallel group pub fn schedule_dp_allreduce(&self, _name: &str, size_bytes: usize) -> Option { if !self.mesh.should_dp_allreduce() { return None; } let group = self.mesh.dp_group(); let id = self.create_pending_comm( CommOp::AllReduce, ParallelDimension::Data, group.ranks.clone(), size_bytes, ); let mut stats = self.stats.write(); stats.dp_allreduce_count += 1; stats.dp_allreduce_bytes += size_bytes; Some(id) } /// Schedule tensor parallel AllGather pub fn schedule_tp_allgather(&self, _name: &str, size_bytes: usize) -> Option { if !self.mesh.should_tp_allreduce() { return None; } let group = self.mesh.tp_group(); let id = self.create_pending_comm( CommOp::AllGather, ParallelDimension::Tensor, group.ranks.clone(), size_bytes, ); let mut stats = self.stats.write(); stats.tp_allgather_count += 1; stats.tp_allgather_bytes += size_bytes; Some(id) } /// Schedule tensor parallel ReduceScatter pub fn schedule_tp_reduce_scatter(&self, _name: &str, size_bytes: usize) -> Option { if !self.mesh.should_tp_allreduce() { return None; } let group = self.mesh.tp_group(); let id = self.create_pending_comm( CommOp::ReduceScatter, ParallelDimension::Tensor, group.ranks.clone(), size_bytes, ); Some(id) } /// Schedule pipeline send pub fn schedule_pp_send(&self, size_bytes: usize) -> Option { let next_rank = self.mesh.next_pp_rank()?; let id = self.create_pending_comm( CommOp::Send, ParallelDimension::Pipeline, vec![next_rank], size_bytes, ); let mut stats = self.stats.write(); stats.pp_send_count += 1; stats.pp_send_bytes += size_bytes; Some(id) } /// Schedule pipeline recv pub fn schedule_pp_recv(&self, size_bytes: usize) -> Option { let prev_rank = self.mesh.prev_pp_rank()?; let id = self.create_pending_comm( CommOp::Recv, ParallelDimension::Pipeline, vec![prev_rank], size_bytes, ); let mut stats = self.stats.write(); stats.pp_recv_count += 1; stats.pp_recv_bytes += size_bytes; Some(id) } /// Create a pending communication fn create_pending_comm( &self, op: CommOp, dimension: ParallelDimension, dst: Vec, size_bytes: usize, ) -> u64 { static NEXT_ID: AtomicU64 = AtomicU64::new(1); let id = NEXT_ID.fetch_add(1, Ordering::SeqCst); let comm = PendingComm { id, op, dimension, src: self.mesh.global_rank(), dst, size_bytes, created_at: Instant::now(), }; self.pending_comms.lock().push(comm); id } /// Get pending communication count pub fn pending_count(&self) -> usize { self.pending_comms.lock().len() } /// Clear pending communications pub fn clear_pending(&self) { self.pending_comms.lock().clear(); } /// Get mesh pub fn mesh(&self) -> &ProcessGroupMesh { &self.mesh } /// Get configuration pub fn config(&self) -> &HybridParallelConfig { &self.config } /// Get statistics pub fn stats(&self) -> CoordinatorStats { self.stats.read().clone() } /// Get parallelism summary pub fn parallelism_summary(&self) -> String { format!( "DP={} x TP={} x PP={} (Total: {} GPUs)", self.config.dp_degree, self.config.tp_degree, self.config.pp_degree, self.config.total_gpus ) } } // ============================================================================= // Coordinator Statistics // ============================================================================= /// Statistics for hybrid parallelism coordinator #[derive(Debug, Default, Clone)] pub struct CoordinatorStats { /// Data parallel AllReduce count pub dp_allreduce_count: usize, /// Data parallel AllReduce bytes pub dp_allreduce_bytes: usize, /// Tensor parallel AllGather count pub tp_allgather_count: usize, /// Tensor parallel AllGather bytes pub tp_allgather_bytes: usize, /// Pipeline send count pub pp_send_count: usize, /// Pipeline send bytes pub pp_send_bytes: usize, /// Pipeline recv count pub pp_recv_count: usize, /// Pipeline recv bytes pub pp_recv_bytes: usize, /// Total communication time pub total_comm_time: Duration, } impl CoordinatorStats { /// Get total communication bytes pub fn total_bytes(&self) -> usize { self.dp_allreduce_bytes + self.tp_allgather_bytes + self.pp_send_bytes + self.pp_recv_bytes } /// Get total operation count pub fn total_ops(&self) -> usize { self.dp_allreduce_count + self.tp_allgather_count + self.pp_send_count + self.pp_recv_count } } // ============================================================================= // Thread-Safe Wrappers // ============================================================================= /// Thread-safe shared hybrid parallel coordinator pub type SharedHybridParallelCoordinator = Arc; /// Create a shared hybrid parallel coordinator pub fn shared_hybrid_coordinator( config: HybridParallelConfig, global_rank: usize, ) -> Result { Ok(Arc::new(HybridParallelCoordinator::new( config, global_rank, )?)) } // ============================================================================= // Tests // ============================================================================= #[cfg(test)] mod tests { use super::*; #[test] fn test_hybrid_config_default() { let config = HybridParallelConfig::default(); assert_eq!(config.total_gpus, 8); assert_eq!(config.dp_degree, 2); assert_eq!(config.tp_degree, 2); assert_eq!(config.pp_degree, 2); } #[test] fn test_config_validate_success() { let config = HybridParallelConfig { total_gpus: 8, dp_degree: 2, tp_degree: 2, pp_degree: 2, ..Default::default() }; assert!(config.validate().is_ok()); } #[test] fn test_config_validate_failure() { let config = HybridParallelConfig { total_gpus: 8, dp_degree: 2, tp_degree: 2, pp_degree: 3, // 2 x 2 x 3 = 12 != 8 ..Default::default() }; assert!(config.validate().is_err()); } #[test] fn test_data_parallel_config() { let config = HybridParallelConfig::data_parallel(8); assert_eq!(config.dp_degree, 8); assert_eq!(config.tp_degree, 1); assert_eq!(config.pp_degree, 1); assert!(config.validate().is_ok()); } #[test] fn test_tensor_data_parallel_config() { let config = HybridParallelConfig::tensor_data_parallel(4, 2); assert_eq!(config.total_gpus, 8); assert_eq!(config.dp_degree, 2); assert_eq!(config.tp_degree, 4); assert!(config.validate().is_ok()); } #[test] fn test_3d_parallelism_config() { let config = HybridParallelConfig::parallelism_3d(2, 2, 2); assert_eq!(config.total_gpus, 8); assert!(config.validate().is_ok()); } #[test] fn test_mesh_coordinate() { let config = HybridParallelConfig::default(); let mesh = ProcessGroupMesh::new(config, 0).unwrap(); let coord = mesh.coordinate(); assert_eq!(coord.global_rank, 0); assert_eq!(coord.dp_rank, 0); assert_eq!(coord.tp_rank, 0); assert_eq!(coord.pp_rank, 0); } #[test] fn test_mesh_coordinate_rank_5() { // Layout: [DP, TP, PP] with PP varying fastest // Rank 5 = 5 in base [2,2,2] // pp = 5 % 2 = 1 // tp = (5 / 2) % 2 = 0 // dp = 5 / 4 = 1 let config = HybridParallelConfig::default(); let mesh = ProcessGroupMesh::new(config, 5).unwrap(); let coord = mesh.coordinate(); assert_eq!(coord.global_rank, 5); assert_eq!(coord.pp_rank, 1); assert_eq!(coord.tp_rank, 0); assert_eq!(coord.dp_rank, 1); } #[test] fn test_dp_group() { let config = HybridParallelConfig::default(); let mesh = ProcessGroupMesh::new(config, 0).unwrap(); let dp_group = mesh.dp_group(); assert_eq!(dp_group.dimension, ParallelDimension::Data); assert_eq!(dp_group.size, 2); // dp_degree = 2 assert_eq!(dp_group.local_rank, 0); } #[test] fn test_tp_group() { let config = HybridParallelConfig::default(); let mesh = ProcessGroupMesh::new(config, 0).unwrap(); let tp_group = mesh.tp_group(); assert_eq!(tp_group.dimension, ParallelDimension::Tensor); assert_eq!(tp_group.size, 2); // tp_degree = 2 } #[test] fn test_pp_group() { let config = HybridParallelConfig::default(); let mesh = ProcessGroupMesh::new(config, 0).unwrap(); let pp_group = mesh.pp_group(); assert_eq!(pp_group.dimension, ParallelDimension::Pipeline); assert_eq!(pp_group.size, 2); // pp_degree = 2 } #[test] fn test_pp_next_prev() { let config = HybridParallelConfig::default(); // First stage let mesh_0 = ProcessGroupMesh::new(config.clone(), 0).unwrap(); assert!(mesh_0.next_pp_rank().is_some()); assert!(mesh_0.prev_pp_rank().is_none()); // Last stage let mesh_1 = ProcessGroupMesh::new(config, 1).unwrap(); assert!(mesh_1.next_pp_rank().is_none()); assert!(mesh_1.prev_pp_rank().is_some()); } #[test] fn test_should_allreduce() { let config = HybridParallelConfig::default(); let mesh = ProcessGroupMesh::new(config, 0).unwrap(); assert!(mesh.should_dp_allreduce()); // dp_degree = 2 assert!(mesh.should_tp_allreduce()); // tp_degree = 2 let dp_only = HybridParallelConfig::data_parallel(4); let mesh_dp = ProcessGroupMesh::new(dp_only, 0).unwrap(); assert!(mesh_dp.should_dp_allreduce()); assert!(!mesh_dp.should_tp_allreduce()); // tp_degree = 1 } #[test] fn test_coordinator_creation() { let config = HybridParallelConfig::default(); let coord = HybridParallelCoordinator::new(config, 0).unwrap(); assert!(!coord.is_active()); assert_eq!(coord.current_step(), 0); assert_eq!(coord.pending_count(), 0); } #[test] fn test_coordinator_start_stop() { let config = HybridParallelConfig::default(); let coord = HybridParallelCoordinator::new(config, 0).unwrap(); coord.start(); assert!(coord.is_active()); coord.stop(); assert!(!coord.is_active()); } #[test] fn test_coordinator_steps() { let config = HybridParallelConfig::default(); let coord = HybridParallelCoordinator::new(config, 0).unwrap(); assert_eq!(coord.current_step(), 0); assert_eq!(coord.next_step(), 1); assert_eq!(coord.next_step(), 2); assert_eq!(coord.current_step(), 2); } #[test] fn test_schedule_dp_allreduce() { let config = HybridParallelConfig::default(); let coord = HybridParallelCoordinator::new(config, 0).unwrap(); let id = coord.schedule_dp_allreduce("gradients", 1024); assert!(id.is_some()); assert_eq!(coord.pending_count(), 1); let stats = coord.stats(); assert_eq!(stats.dp_allreduce_count, 1); assert_eq!(stats.dp_allreduce_bytes, 1024); } #[test] fn test_schedule_tp_allgather() { let config = HybridParallelConfig::default(); let coord = HybridParallelCoordinator::new(config, 0).unwrap(); let id = coord.schedule_tp_allgather("weights", 2048); assert!(id.is_some()); assert_eq!(coord.pending_count(), 1); } #[test] fn test_schedule_pp_send() { let config = HybridParallelConfig::default(); // Rank 0 is first stage, can send let coord = HybridParallelCoordinator::new(config, 0).unwrap(); let id = coord.schedule_pp_send(512); assert!(id.is_some()); } #[test] fn test_schedule_pp_recv() { let config = HybridParallelConfig::default(); // Rank 1 is second stage, can recv let coord = HybridParallelCoordinator::new(config, 1).unwrap(); let id = coord.schedule_pp_recv(512); assert!(id.is_some()); } #[test] fn test_parallelism_summary() { let config = HybridParallelConfig::default(); let coord = HybridParallelCoordinator::new(config, 0).unwrap(); let summary = coord.parallelism_summary(); assert!(summary.contains("DP=2")); assert!(summary.contains("TP=2")); assert!(summary.contains("PP=2")); } #[test] fn test_clear_pending() { let config = HybridParallelConfig::default(); let coord = HybridParallelCoordinator::new(config, 0).unwrap(); coord.schedule_dp_allreduce("g1", 100); coord.schedule_dp_allreduce("g2", 200); assert_eq!(coord.pending_count(), 2); coord.clear_pending(); assert_eq!(coord.pending_count(), 0); } #[test] fn test_coordinator_stats() { let stats = CoordinatorStats { dp_allreduce_bytes: 1000, tp_allgather_bytes: 500, pp_send_bytes: 200, pp_recv_bytes: 200, dp_allreduce_count: 2, tp_allgather_count: 1, pp_send_count: 1, pp_recv_count: 1, ..Default::default() }; assert_eq!(stats.total_bytes(), 1900); assert_eq!(stats.total_ops(), 5); } #[test] fn test_shared_coordinator() { let config = HybridParallelConfig::default(); let coord = shared_hybrid_coordinator(config, 0).unwrap(); assert_eq!(coord.mesh().global_rank(), 0); } #[test] fn test_mesh_coordinate_helpers() { let coord = MeshCoordinate { global_rank: 0, dp_rank: 0, tp_rank: 0, pp_rank: 0, device_id: 0, }; assert!(coord.is_tp_first()); assert!(coord.is_pp_first()); assert!(!coord.is_tp_last(2)); assert!(!coord.is_pp_last(2)); } #[test] fn test_process_group_info_contains() { let group = ProcessGroupInfo::new( "test".to_string(), ParallelDimension::Data, vec![0, 2, 4, 6], 0, ); assert!(group.contains(0)); assert!(group.contains(4)); assert!(!group.contains(1)); assert!(!group.contains(3)); } #[test] fn test_groups_cover_all_ranks() { let config = HybridParallelConfig::default(); // For each rank, check that all parallel groups are valid for rank in 0..8 { let mesh = ProcessGroupMesh::new(config.clone(), rank).unwrap(); assert!(mesh.dp_group().contains(rank)); assert!(mesh.tp_group().contains(rank)); assert!(mesh.pp_group().contains(rank)); } } #[test] fn test_no_dp_allreduce_for_single_dp() { let config = HybridParallelConfig::data_parallel(1); let coord = HybridParallelCoordinator::new(config, 0).unwrap(); // With dp_degree=1, no AllReduce should be scheduled let id = coord.schedule_dp_allreduce("grad", 100); assert!(id.is_none()); } }