Skip to main content

torsh_graph/
distributed.rs

1//! Distributed graph neural networks for large-scale graph processing
2//!
3//! This module provides distributed training and inference capabilities
4//! for graph neural networks across multiple devices and machines.
5/// Crate-local result alias: the error type defaults to [`TorshError`],
6/// so both `Result<T>` and `Result<T, OtherError>` stay valid.
7type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
8
9use crate::{GraphData, GraphLayer};
10use std::collections::HashMap;
11use std::sync::{Arc, Mutex};
12use torsh_tensor::Tensor;
13
14/// Distributed training configuration
15#[derive(Debug, Clone)]
16pub struct DistributedConfig {
17    /// Number of worker nodes
18    pub num_workers: usize,
19    /// Rank of current process (0 to num_workers-1)
20    pub rank: usize,
21    /// Communication backend
22    pub backend: CommunicationBackend,
23    /// Graph partitioning strategy
24    pub partitioning: GraphPartitioning,
25    /// Aggregation method for distributed training
26    pub aggregation: AggregationMethod,
27    /// Synchronization frequency (in steps)
28    pub sync_frequency: usize,
29}
30
31/// Communication backends for distributed training
32#[derive(Debug, Clone, PartialEq)]
33pub enum CommunicationBackend {
34    /// Message Passing Interface
35    MPI,
36    /// NVIDIA Collective Communications Library
37    NCCL,
38    /// Gloo collective communications
39    Gloo,
40    /// TCP-based communication
41    TCP,
42    /// In-memory communication (single machine)
43    InMemory,
44}
45
46/// Graph partitioning strategies
47pub enum GraphPartitioning {
48    /// Random vertex partitioning
49    Random,
50    /// METIS-based partitioning
51    METIS,
52    /// Hash-based partitioning
53    Hash,
54    /// Community-based partitioning
55    Community,
56    /// Custom partitioning function
57    Custom(Box<dyn Fn(&GraphData, usize) -> Vec<PartitionInfo> + Send + Sync>),
58}
59
60// Manual Debug implementation for GraphPartitioning
61impl std::fmt::Debug for GraphPartitioning {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        match self {
64            GraphPartitioning::Random => write!(f, "GraphPartitioning::Random"),
65            GraphPartitioning::METIS => write!(f, "GraphPartitioning::METIS"),
66            GraphPartitioning::Hash => write!(f, "GraphPartitioning::Hash"),
67            GraphPartitioning::Community => write!(f, "GraphPartitioning::Community"),
68            GraphPartitioning::Custom(_) => write!(f, "GraphPartitioning::Custom(<function>)"),
69        }
70    }
71}
72
73// Manual Clone implementation for GraphPartitioning (Clone not available for Custom)
74impl Clone for GraphPartitioning {
75    fn clone(&self) -> Self {
76        match self {
77            GraphPartitioning::Random => GraphPartitioning::Random,
78            GraphPartitioning::METIS => GraphPartitioning::METIS,
79            GraphPartitioning::Hash => GraphPartitioning::Hash,
80            GraphPartitioning::Community => GraphPartitioning::Community,
81            GraphPartitioning::Custom(_) => {
82                // Cannot clone function pointer - fallback to Random
83                GraphPartitioning::Random
84            }
85        }
86    }
87}
88
89/// Aggregation methods for distributed updates
90#[derive(Debug, Clone)]
91pub enum AggregationMethod {
92    /// Average gradients across workers
93    Average,
94    /// Sum gradients across workers
95    Sum,
96    /// Weighted average based on partition size
97    WeightedAverage,
98    /// Asynchronous parameter server
99    ParameterServer,
100    /// AllReduce pattern
101    AllReduce,
102}
103
104/// Information about a graph partition
105#[derive(Debug, Clone)]
106pub struct PartitionInfo {
107    /// Worker rank responsible for this partition
108    pub worker_rank: usize,
109    /// Nodes in this partition
110    pub nodes: Vec<usize>,
111    /// Edges within this partition
112    pub internal_edges: Vec<(usize, usize)>,
113    /// Cross-partition edges (boundary edges)
114    pub boundary_edges: Vec<(usize, usize, usize)>, // (src, dst, target_worker)
115    /// Partition size metrics
116    pub metrics: PartitionMetrics,
117}
118
119/// Metrics for evaluating partition quality
120#[derive(Debug, Clone)]
121pub struct PartitionMetrics {
122    /// Number of nodes in partition
123    pub num_nodes: usize,
124    /// Number of internal edges
125    pub num_internal_edges: usize,
126    /// Number of boundary edges
127    pub num_boundary_edges: usize,
128    /// Load balance score (0.0 = perfect, higher = worse)
129    pub load_balance_score: f32,
130    /// Communication cost estimate
131    pub communication_cost: f32,
132}
133
134/// Distributed graph neural network coordinator
135#[derive(Debug)]
136pub struct DistributedGNN {
137    /// Configuration
138    pub config: DistributedConfig,
139    /// Local graph partition
140    pub local_partition: GraphData,
141    /// Partition information
142    pub partition_info: PartitionInfo,
143    /// Communication manager
144    pub comm_manager: CommunicationManager,
145    /// Parameter synchronization state
146    pub sync_state: Arc<Mutex<SyncState>>,
147    /// Performance metrics
148    pub metrics: DistributedMetrics,
149}
150
151impl DistributedGNN {
152    /// Create a new distributed GNN
153    pub fn new(
154        config: DistributedConfig,
155        full_graph: &GraphData,
156    ) -> Result<Self, DistributedError> {
157        // Partition the graph
158        let partitions = Self::partition_graph(full_graph, &config)?;
159        let local_partition = partitions[config.rank].clone();
160
161        // Initialize communication
162        let comm_manager = CommunicationManager::new(&config)?;
163
164        // Create partition info
165        let partition_info = Self::create_partition_info(&local_partition, config.rank);
166
167        let sync_state = Arc::new(Mutex::new(SyncState::new()));
168        let metrics = DistributedMetrics::new();
169
170        Ok(Self {
171            config,
172            local_partition,
173            partition_info,
174            comm_manager,
175            sync_state,
176            metrics,
177        })
178    }
179
180    /// Perform distributed forward pass
181    pub fn distributed_forward(
182        &mut self,
183        layer: &dyn GraphLayer,
184    ) -> Result<GraphData, DistributedError> {
185        // Step 1: Gather boundary node features from other workers
186        let boundary_features = self.gather_boundary_features()?;
187
188        // Step 2: Augment local graph with boundary features
189        let augmented_graph = self.augment_local_graph(&boundary_features)?;
190
191        // Step 3: Perform local forward pass
192        let local_output = layer.forward(&augmented_graph)?;
193
194        // Step 4: Extract and communicate updated boundary features
195        self.communicate_boundary_updates(&local_output)?;
196
197        Ok(local_output)
198    }
199
200    /// Synchronize parameters across workers
201    pub fn synchronize_parameters(
202        &mut self,
203        parameters: &[Tensor],
204    ) -> Result<Vec<Tensor>, DistributedError> {
205        match self.config.aggregation {
206            AggregationMethod::AllReduce => self.all_reduce_parameters(parameters),
207            AggregationMethod::Average => self.average_parameters(parameters),
208            AggregationMethod::Sum => self.sum_parameters(parameters),
209            AggregationMethod::WeightedAverage => self.weighted_average_parameters(parameters),
210            AggregationMethod::ParameterServer => self.parameter_server_sync(parameters),
211        }
212    }
213
214    /// Perform all-reduce on parameters
215    fn all_reduce_parameters(
216        &mut self,
217        parameters: &[Tensor],
218    ) -> Result<Vec<Tensor>, DistributedError> {
219        let mut reduced_params = Vec::new();
220
221        for param in parameters {
222            // Serialize parameter
223            let param_data = param.to_vec().map_err(|e| {
224                DistributedError::CommunicationError(format!(
225                    "Failed to serialize parameter: {:?}",
226                    e
227                ))
228            })?;
229
230            // Perform all-reduce operation
231            let reduced_data = self.comm_manager.all_reduce(&param_data)?;
232
233            // Deserialize back to tensor
234            let reduced_param = self.vec_to_tensor(&reduced_data, param.shape().dims())?;
235            reduced_params.push(reduced_param);
236        }
237
238        Ok(reduced_params)
239    }
240
241    /// Average parameters across workers
242    fn average_parameters(
243        &mut self,
244        parameters: &[Tensor],
245    ) -> Result<Vec<Tensor>, DistributedError> {
246        let summed_params = self.sum_parameters(parameters)?;
247        let num_workers = self.config.num_workers as f32;
248
249        summed_params
250            .into_iter()
251            .map(|param| {
252                param
253                    .div_scalar(num_workers)
254                    .map_err(DistributedError::from)
255            })
256            .collect()
257    }
258
259    /// Sum parameters across workers
260    fn sum_parameters(&mut self, parameters: &[Tensor]) -> Result<Vec<Tensor>, DistributedError> {
261        let mut summed_params = Vec::new();
262
263        for param in parameters {
264            let param_data = param.to_vec().map_err(|e| {
265                DistributedError::CommunicationError(format!(
266                    "Failed to serialize parameter: {:?}",
267                    e
268                ))
269            })?;
270
271            let summed_data = self.comm_manager.all_reduce_sum(&param_data)?;
272            let summed_param = self.vec_to_tensor(&summed_data, param.shape().dims())?;
273            summed_params.push(summed_param);
274        }
275
276        Ok(summed_params)
277    }
278
279    /// Weighted average based on partition sizes
280    fn weighted_average_parameters(
281        &mut self,
282        parameters: &[Tensor],
283    ) -> Result<Vec<Tensor>, DistributedError> {
284        let local_weight = self.partition_info.metrics.num_nodes as f32;
285        let total_weight = self.comm_manager.all_reduce_sum(&[local_weight])?[0];
286
287        let weighted_params = parameters
288            .iter()
289            .map(|param| param.mul_scalar(local_weight))
290            .collect::<std::result::Result<Vec<_>, _>>()?;
291
292        let summed_params = self.sum_parameters(&weighted_params)?;
293
294        summed_params
295            .into_iter()
296            .map(|param| {
297                param
298                    .div_scalar(total_weight)
299                    .map_err(DistributedError::from)
300            })
301            .collect()
302    }
303
304    /// Parameter server synchronization
305    fn parameter_server_sync(
306        &mut self,
307        parameters: &[Tensor],
308    ) -> Result<Vec<Tensor>, DistributedError> {
309        if self.config.rank == 0 {
310            // Parameter server logic
311            self.parameter_server_master(parameters)
312        } else {
313            // Worker logic
314            self.parameter_server_worker(parameters)
315        }
316    }
317
318    fn parameter_server_master(
319        &mut self,
320        parameters: &[Tensor],
321    ) -> Result<Vec<Tensor>, DistributedError> {
322        // Collect updates from all workers
323        let mut accumulated_updates = parameters.to_vec();
324
325        for worker_rank in 1..self.config.num_workers {
326            let worker_updates = self.comm_manager.receive_from(worker_rank)?;
327            // Accumulate updates (simplified)
328            for (i, update) in worker_updates.iter().enumerate() {
329                if i < accumulated_updates.len() {
330                    accumulated_updates[i] = accumulated_updates[i].add(update)?;
331                }
332            }
333        }
334
335        // Average and broadcast back
336        let num_workers = self.config.num_workers as f32;
337        let averaged_params: Vec<Tensor> = accumulated_updates
338            .into_iter()
339            .map(|param| param.div_scalar(num_workers))
340            .collect::<std::result::Result<Vec<_>, _>>()?;
341
342        // Broadcast to all workers
343        for worker_rank in 1..self.config.num_workers {
344            self.comm_manager.send_to(worker_rank, &averaged_params)?;
345        }
346
347        Ok(averaged_params)
348    }
349
350    fn parameter_server_worker(
351        &mut self,
352        parameters: &[Tensor],
353    ) -> Result<Vec<Tensor>, DistributedError> {
354        // Send updates to parameter server
355        self.comm_manager.send_to(0, parameters)?;
356
357        // Receive updated parameters
358        self.comm_manager.receive_from(0)
359    }
360
361    /// Gather boundary node features from neighboring partitions
362    fn gather_boundary_features(&mut self) -> Result<HashMap<usize, Tensor>, DistributedError> {
363        let mut boundary_features = HashMap::new();
364
365        // Request features for boundary nodes
366        for &(_, _, target_worker) in &self.partition_info.boundary_edges {
367            if target_worker != self.config.rank {
368                // Request boundary features from target worker
369                let features = self.comm_manager.request_boundary_features(target_worker)?;
370                boundary_features.insert(target_worker, features);
371            }
372        }
373
374        Ok(boundary_features)
375    }
376
377    /// Augment local graph with boundary features
378    fn augment_local_graph(
379        &self,
380        _boundary_features: &HashMap<usize, Tensor>,
381    ) -> Result<GraphData, DistributedError> {
382        // For now, return the local partition
383        // In practice, would merge boundary features
384        Ok(self.local_partition.clone())
385    }
386
387    /// Communicate boundary updates to neighboring workers
388    fn communicate_boundary_updates(
389        &mut self,
390        _local_output: &GraphData,
391    ) -> Result<(), DistributedError> {
392        // Send boundary node updates to neighboring partitions
393        // Simplified implementation
394        Ok(())
395    }
396
397    /// Partition a graph into distributed chunks
398    fn partition_graph(
399        graph: &GraphData,
400        config: &DistributedConfig,
401    ) -> Result<Vec<GraphData>, DistributedError> {
402        match &config.partitioning {
403            GraphPartitioning::Random => Self::random_partition(graph, config.num_workers),
404            GraphPartitioning::Hash => Self::hash_partition(graph, config.num_workers),
405            GraphPartitioning::METIS => Self::metis_partition(graph, config.num_workers),
406            GraphPartitioning::Community => Self::community_partition(graph, config.num_workers),
407            GraphPartitioning::Custom(partition_fn) => {
408                let partition_infos = partition_fn(graph, config.num_workers);
409                Self::create_partitions_from_info(graph, &partition_infos)
410            }
411        }
412    }
413
414    fn random_partition(
415        graph: &GraphData,
416        num_partitions: usize,
417    ) -> Result<Vec<GraphData>, DistributedError> {
418        let mut partitions = Vec::new();
419        let nodes_per_partition = graph.num_nodes / num_partitions;
420
421        for i in 0..num_partitions {
422            let start_node = i * nodes_per_partition;
423            let end_node = if i == num_partitions - 1 {
424                graph.num_nodes
425            } else {
426                (i + 1) * nodes_per_partition
427            };
428
429            // Create partition subgraph (simplified)
430            let partition_nodes = (start_node..end_node).collect::<Vec<_>>();
431            let partition_graph = Self::extract_subgraph(graph, &partition_nodes)?;
432            partitions.push(partition_graph);
433        }
434
435        Ok(partitions)
436    }
437
438    fn hash_partition(
439        graph: &GraphData,
440        num_partitions: usize,
441    ) -> Result<Vec<GraphData>, DistributedError> {
442        let mut partition_nodes: Vec<Vec<usize>> = vec![Vec::new(); num_partitions];
443
444        // Hash-based node assignment
445        for node in 0..graph.num_nodes {
446            let partition_id = node % num_partitions;
447            partition_nodes[partition_id].push(node);
448        }
449
450        let mut partitions = Vec::new();
451        for nodes in partition_nodes {
452            let partition_graph = Self::extract_subgraph(graph, &nodes)?;
453            partitions.push(partition_graph);
454        }
455
456        Ok(partitions)
457    }
458
459    fn metis_partition(
460        _graph: &GraphData,
461        _num_partitions: usize,
462    ) -> Result<Vec<GraphData>, DistributedError> {
463        // Placeholder for METIS integration
464        Err(DistributedError::PartitioningError(
465            "METIS partitioning not implemented".to_string(),
466        ))
467    }
468
469    fn community_partition(
470        _graph: &GraphData,
471        _num_partitions: usize,
472    ) -> Result<Vec<GraphData>, DistributedError> {
473        // Placeholder for community-based partitioning
474        Err(DistributedError::PartitioningError(
475            "Community partitioning not implemented".to_string(),
476        ))
477    }
478
479    fn create_partitions_from_info(
480        graph: &GraphData,
481        partition_infos: &[PartitionInfo],
482    ) -> Result<Vec<GraphData>, DistributedError> {
483        let mut partitions = Vec::new();
484
485        for info in partition_infos {
486            let partition_graph = Self::extract_subgraph(graph, &info.nodes)?;
487            partitions.push(partition_graph);
488        }
489
490        Ok(partitions)
491    }
492
493    fn extract_subgraph(graph: &GraphData, nodes: &[usize]) -> Result<GraphData, DistributedError> {
494        // Simplified subgraph extraction
495        // In practice, would properly extract edges and reindex nodes
496
497        if nodes.is_empty() {
498            return Ok(GraphData::new(
499                torsh_tensor::creation::zeros(&[0, graph.x.shape().dims()[1]])?,
500                torsh_tensor::creation::zeros(&[2, 0])?,
501            ));
502        }
503
504        // Extract node features
505        let feature_dim = graph.x.shape().dims()[1];
506        let mut subgraph_features = Vec::new();
507
508        for &node in nodes {
509            if node < graph.num_nodes {
510                // Extract features for this node (simplified)
511                for _f in 0..feature_dim {
512                    subgraph_features.push(1.0); // Placeholder
513                }
514            }
515        }
516
517        let x = torsh_tensor::creation::from_vec(
518            subgraph_features,
519            &[nodes.len(), feature_dim],
520            graph.x.device(),
521        )
522        .map_err(|e| {
523            DistributedError::TensorError(format!("Failed to create features tensor: {:?}", e))
524        })?;
525
526        // Create minimal edge index (simplified)
527        let edge_index = torsh_tensor::creation::zeros(&[2, 0])?;
528
529        Ok(GraphData::new(x, edge_index))
530    }
531
532    fn create_partition_info(graph: &GraphData, rank: usize) -> PartitionInfo {
533        PartitionInfo {
534            worker_rank: rank,
535            nodes: (0..graph.num_nodes).collect(),
536            internal_edges: Vec::new(),
537            boundary_edges: Vec::new(),
538            metrics: PartitionMetrics {
539                num_nodes: graph.num_nodes,
540                num_internal_edges: 0,
541                num_boundary_edges: 0,
542                load_balance_score: 0.0,
543                communication_cost: 0.0,
544            },
545        }
546    }
547
548    fn vec_to_tensor(&self, data: &[f32], shape: &[usize]) -> Result<Tensor, DistributedError> {
549        torsh_tensor::creation::from_vec(data.to_vec(), shape, torsh_core::device::DeviceType::Cpu)
550            .map_err(|e| DistributedError::TensorError(format!("Failed to create tensor: {:?}", e)))
551    }
552}
553
554/// Communication manager for distributed operations
555#[derive(Debug)]
556pub struct CommunicationManager {
557    backend: CommunicationBackend,
558    rank: usize,
559    num_workers: usize,
560    // Backend-specific state would go here
561}
562
563impl CommunicationManager {
564    pub fn new(config: &DistributedConfig) -> Result<Self, DistributedError> {
565        Ok(Self {
566            backend: config.backend.clone(),
567            rank: config.rank,
568            num_workers: config.num_workers,
569        })
570    }
571
572    /// Get the rank of this worker
573    pub fn rank(&self) -> usize {
574        self.rank
575    }
576
577    /// Get the number of workers
578    pub fn num_workers(&self) -> usize {
579        self.num_workers
580    }
581
582    pub fn all_reduce(&mut self, data: &[f32]) -> Result<Vec<f32>, DistributedError> {
583        match self.backend {
584            CommunicationBackend::InMemory => {
585                // Simplified in-memory implementation
586                Ok(data.to_vec())
587            }
588            _ => Err(DistributedError::CommunicationError(
589                "Backend not implemented".to_string(),
590            )),
591        }
592    }
593
594    pub fn all_reduce_sum(&mut self, data: &[f32]) -> Result<Vec<f32>, DistributedError> {
595        // Simplified implementation
596        Ok(data.to_vec())
597    }
598
599    pub fn send_to(
600        &mut self,
601        _target_rank: usize,
602        _data: &[Tensor],
603    ) -> Result<(), DistributedError> {
604        // Simplified implementation
605        Ok(())
606    }
607
608    pub fn receive_from(&mut self, _source_rank: usize) -> Result<Vec<Tensor>, DistributedError> {
609        // Simplified implementation
610        Ok(Vec::new())
611    }
612
613    pub fn request_boundary_features(
614        &mut self,
615        _target_worker: usize,
616    ) -> Result<Tensor, DistributedError> {
617        // Simplified implementation
618        torsh_tensor::creation::zeros(&[1, 1])
619            .map_err(|e| DistributedError::TensorError(format!("Failed to create tensor: {:?}", e)))
620    }
621}
622
623/// Synchronization state for distributed training
624#[derive(Debug)]
625pub struct SyncState {
626    pub current_step: usize,
627    pub last_sync_step: usize,
628    pub pending_updates: HashMap<usize, Vec<Tensor>>,
629}
630
631impl SyncState {
632    pub fn new() -> Self {
633        Self {
634            current_step: 0,
635            last_sync_step: 0,
636            pending_updates: HashMap::new(),
637        }
638    }
639
640    pub fn should_sync(&self, sync_frequency: usize) -> bool {
641        self.current_step - self.last_sync_step >= sync_frequency
642    }
643
644    pub fn mark_synced(&mut self) {
645        self.last_sync_step = self.current_step;
646        self.pending_updates.clear();
647    }
648}
649
650/// Performance metrics for distributed training
651#[derive(Debug, Clone)]
652pub struct DistributedMetrics {
653    pub communication_time_ms: f64,
654    pub computation_time_ms: f64,
655    pub synchronization_time_ms: f64,
656    pub total_bytes_communicated: usize,
657    pub num_synchronizations: usize,
658    pub efficiency_score: f32,
659}
660
661impl DistributedMetrics {
662    pub fn new() -> Self {
663        Self {
664            communication_time_ms: 0.0,
665            computation_time_ms: 0.0,
666            synchronization_time_ms: 0.0,
667            total_bytes_communicated: 0,
668            num_synchronizations: 0,
669            efficiency_score: 1.0,
670        }
671    }
672
673    pub fn compute_efficiency(&mut self) {
674        let total_time = self.communication_time_ms + self.computation_time_ms;
675        if total_time > 0.0 {
676            self.efficiency_score = (self.computation_time_ms / total_time) as f32;
677        }
678    }
679}
680
681/// Distributed training errors
682#[derive(Debug, Clone)]
683pub enum DistributedError {
684    /// Communication backend error
685    CommunicationError(String),
686    /// Graph partitioning error
687    PartitioningError(String),
688    /// Tensor operation error
689    TensorError(String),
690    /// Configuration error
691    ConfigError(String),
692    /// Synchronization error
693    SynchronizationError(String),
694}
695
696impl From<torsh_core::error::TorshError> for DistributedError {
697    fn from(error: torsh_core::error::TorshError) -> Self {
698        DistributedError::TensorError(error.to_string())
699    }
700}
701
702impl std::fmt::Display for DistributedError {
703    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
704        match self {
705            DistributedError::CommunicationError(msg) => write!(f, "Communication error: {}", msg),
706            DistributedError::PartitioningError(msg) => write!(f, "Partitioning error: {}", msg),
707            DistributedError::TensorError(msg) => write!(f, "Tensor error: {}", msg),
708            DistributedError::ConfigError(msg) => write!(f, "Configuration error: {}", msg),
709            DistributedError::SynchronizationError(msg) => {
710                write!(f, "Synchronization error: {}", msg)
711            }
712        }
713    }
714}
715
716impl std::error::Error for DistributedError {}
717
718/// Distributed graph layer wrapper
719#[derive(Debug)]
720pub struct DistributedGraphLayer {
721    /// Base layer
722    pub base_layer: Box<dyn GraphLayer>,
723    /// Distributed coordinator
724    pub coordinator: DistributedGNN,
725}
726
727impl DistributedGraphLayer {
728    pub fn new(
729        base_layer: Box<dyn GraphLayer>,
730        config: DistributedConfig,
731        full_graph: &GraphData,
732    ) -> Result<Self, DistributedError> {
733        let coordinator = DistributedGNN::new(config, full_graph)?;
734
735        Ok(Self {
736            base_layer,
737            coordinator,
738        })
739    }
740}
741
742impl GraphLayer for DistributedGraphLayer {
743    fn forward(&self, graph: &GraphData) -> Result<GraphData> {
744        // Simplified distributed forward pass
745        // In practice, would use the coordinator's distributed_forward method
746        self.base_layer.forward(graph)
747    }
748
749    fn parameters(&self) -> Vec<Tensor> {
750        self.base_layer.parameters()
751    }
752}
753
754/// Utility functions for distributed graph operations
755pub mod utils {
756    use super::*;
757
758    /// Calculate load balance score for partitions
759    pub fn calculate_load_balance(partition_sizes: &[usize]) -> f32 {
760        if partition_sizes.is_empty() {
761            return 0.0;
762        }
763
764        let mean_size = partition_sizes.iter().sum::<usize>() as f32 / partition_sizes.len() as f32;
765        let variance: f32 = partition_sizes
766            .iter()
767            .map(|&size| (size as f32 - mean_size).powi(2))
768            .sum::<f32>()
769            / partition_sizes.len() as f32;
770
771        variance / mean_size.max(1.0)
772    }
773
774    /// Estimate communication cost for a partitioning
775    pub fn estimate_communication_cost(partition_infos: &[PartitionInfo]) -> f32 {
776        partition_infos
777            .iter()
778            .map(|info| info.metrics.num_boundary_edges as f32)
779            .sum()
780    }
781
782    /// Create optimal distributed configuration for given hardware
783    pub fn create_optimal_config(num_gpus: usize, graph_size: usize) -> DistributedConfig {
784        let num_workers = num_gpus.max(1);
785        let backend = if num_gpus > 1 {
786            CommunicationBackend::NCCL
787        } else {
788            CommunicationBackend::InMemory
789        };
790
791        let partitioning = if graph_size > 1_000_000 {
792            GraphPartitioning::METIS
793        } else if graph_size > 10_000 {
794            GraphPartitioning::Community
795        } else {
796            GraphPartitioning::Hash
797        };
798
799        DistributedConfig {
800            num_workers,
801            rank: 0, // Will be set by each worker
802            backend,
803            partitioning,
804            aggregation: AggregationMethod::AllReduce,
805            sync_frequency: 10,
806        }
807    }
808}
809
810#[cfg(test)]
811mod tests {
812    use super::*;
813
814    use torsh_tensor::creation::randn;
815
816    #[test]
817    fn test_distributed_config_creation() {
818        let config = DistributedConfig {
819            num_workers: 4,
820            rank: 0,
821            backend: CommunicationBackend::InMemory,
822            partitioning: GraphPartitioning::Random,
823            aggregation: AggregationMethod::Average,
824            sync_frequency: 10,
825        };
826
827        assert_eq!(config.num_workers, 4);
828        assert_eq!(config.rank, 0);
829    }
830
831    #[test]
832    fn test_load_balance_calculation() {
833        let partition_sizes = vec![100, 100, 100, 100];
834        let balance_score = utils::calculate_load_balance(&partition_sizes);
835        assert_eq!(balance_score, 0.0); // Perfect balance
836
837        let unbalanced_sizes = vec![200, 50, 50, 50];
838        let unbalanced_score = utils::calculate_load_balance(&unbalanced_sizes);
839        assert!(unbalanced_score > 0.0); // Poor balance
840    }
841
842    #[test]
843    fn test_communication_cost_estimation() {
844        let partition_info = PartitionInfo {
845            worker_rank: 0,
846            nodes: vec![0, 1, 2],
847            internal_edges: vec![(0, 1)],
848            boundary_edges: vec![(2, 3, 1)],
849            metrics: PartitionMetrics {
850                num_nodes: 3,
851                num_internal_edges: 1,
852                num_boundary_edges: 1,
853                load_balance_score: 0.0,
854                communication_cost: 1.0,
855            },
856        };
857
858        let cost = utils::estimate_communication_cost(&[partition_info]);
859        assert_eq!(cost, 1.0);
860    }
861
862    #[test]
863    fn test_optimal_config_creation() {
864        let config = utils::create_optimal_config(4, 1_000_000);
865        assert_eq!(config.num_workers, 4);
866        assert_eq!(config.backend, CommunicationBackend::NCCL);
867
868        let small_config = utils::create_optimal_config(1, 1000);
869        assert_eq!(small_config.num_workers, 1);
870        assert_eq!(small_config.backend, CommunicationBackend::InMemory);
871    }
872
873    #[test]
874    fn test_sync_state() {
875        let mut sync_state = SyncState::new();
876        assert_eq!(sync_state.current_step, 0);
877        assert!(!sync_state.should_sync(10));
878
879        sync_state.current_step = 10;
880        assert!(sync_state.should_sync(10));
881
882        sync_state.mark_synced();
883        assert_eq!(sync_state.last_sync_step, 10);
884    }
885
886    #[test]
887    fn test_distributed_metrics() {
888        let mut metrics = DistributedMetrics::new();
889        metrics.computation_time_ms = 800.0;
890        metrics.communication_time_ms = 200.0;
891
892        metrics.compute_efficiency();
893        assert_eq!(metrics.efficiency_score, 0.8);
894    }
895
896    #[test]
897    fn test_partition_info_creation() {
898        let x = randn(&[5, 3]).unwrap();
899        let edge_index = torsh_tensor::creation::zeros(&[2, 0]).unwrap();
900        let graph = GraphData::new(x, edge_index);
901
902        let partition_info = DistributedGNN::create_partition_info(&graph, 0);
903        assert_eq!(partition_info.worker_rank, 0);
904        assert_eq!(partition_info.nodes.len(), 5);
905        assert_eq!(partition_info.metrics.num_nodes, 5);
906    }
907}