1type 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#[derive(Debug, Clone)]
16pub struct DistributedConfig {
17 pub num_workers: usize,
19 pub rank: usize,
21 pub backend: CommunicationBackend,
23 pub partitioning: GraphPartitioning,
25 pub aggregation: AggregationMethod,
27 pub sync_frequency: usize,
29}
30
31#[derive(Debug, Clone, PartialEq)]
33pub enum CommunicationBackend {
34 MPI,
36 NCCL,
38 Gloo,
40 TCP,
42 InMemory,
44}
45
46pub enum GraphPartitioning {
48 Random,
50 METIS,
52 Hash,
54 Community,
56 Custom(Box<dyn Fn(&GraphData, usize) -> Vec<PartitionInfo> + Send + Sync>),
58}
59
60impl 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
73impl 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 GraphPartitioning::Random
84 }
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
91pub enum AggregationMethod {
92 Average,
94 Sum,
96 WeightedAverage,
98 ParameterServer,
100 AllReduce,
102}
103
104#[derive(Debug, Clone)]
106pub struct PartitionInfo {
107 pub worker_rank: usize,
109 pub nodes: Vec<usize>,
111 pub internal_edges: Vec<(usize, usize)>,
113 pub boundary_edges: Vec<(usize, usize, usize)>, pub metrics: PartitionMetrics,
117}
118
119#[derive(Debug, Clone)]
121pub struct PartitionMetrics {
122 pub num_nodes: usize,
124 pub num_internal_edges: usize,
126 pub num_boundary_edges: usize,
128 pub load_balance_score: f32,
130 pub communication_cost: f32,
132}
133
134#[derive(Debug)]
136pub struct DistributedGNN {
137 pub config: DistributedConfig,
139 pub local_partition: GraphData,
141 pub partition_info: PartitionInfo,
143 pub comm_manager: CommunicationManager,
145 pub sync_state: Arc<Mutex<SyncState>>,
147 pub metrics: DistributedMetrics,
149}
150
151impl DistributedGNN {
152 pub fn new(
154 config: DistributedConfig,
155 full_graph: &GraphData,
156 ) -> Result<Self, DistributedError> {
157 let partitions = Self::partition_graph(full_graph, &config)?;
159 let local_partition = partitions[config.rank].clone();
160
161 let comm_manager = CommunicationManager::new(&config)?;
163
164 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 pub fn distributed_forward(
182 &mut self,
183 layer: &dyn GraphLayer,
184 ) -> Result<GraphData, DistributedError> {
185 let boundary_features = self.gather_boundary_features()?;
187
188 let augmented_graph = self.augment_local_graph(&boundary_features)?;
190
191 let local_output = layer.forward(&augmented_graph)?;
193
194 self.communicate_boundary_updates(&local_output)?;
196
197 Ok(local_output)
198 }
199
200 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 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 let param_data = param.to_vec().map_err(|e| {
224 DistributedError::CommunicationError(format!(
225 "Failed to serialize parameter: {:?}",
226 e
227 ))
228 })?;
229
230 let reduced_data = self.comm_manager.all_reduce(¶m_data)?;
232
233 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 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 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(¶m_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 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 fn parameter_server_sync(
306 &mut self,
307 parameters: &[Tensor],
308 ) -> Result<Vec<Tensor>, DistributedError> {
309 if self.config.rank == 0 {
310 self.parameter_server_master(parameters)
312 } else {
313 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 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 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 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 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 self.comm_manager.send_to(0, parameters)?;
356
357 self.comm_manager.receive_from(0)
359 }
360
361 fn gather_boundary_features(&mut self) -> Result<HashMap<usize, Tensor>, DistributedError> {
363 let mut boundary_features = HashMap::new();
364
365 for &(_, _, target_worker) in &self.partition_info.boundary_edges {
367 if target_worker != self.config.rank {
368 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 fn augment_local_graph(
379 &self,
380 _boundary_features: &HashMap<usize, Tensor>,
381 ) -> Result<GraphData, DistributedError> {
382 Ok(self.local_partition.clone())
385 }
386
387 fn communicate_boundary_updates(
389 &mut self,
390 _local_output: &GraphData,
391 ) -> Result<(), DistributedError> {
392 Ok(())
395 }
396
397 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 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 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 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 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 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 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 for _f in 0..feature_dim {
512 subgraph_features.push(1.0); }
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 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#[derive(Debug)]
556pub struct CommunicationManager {
557 backend: CommunicationBackend,
558 rank: usize,
559 num_workers: usize,
560 }
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 pub fn rank(&self) -> usize {
574 self.rank
575 }
576
577 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 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 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 Ok(())
606 }
607
608 pub fn receive_from(&mut self, _source_rank: usize) -> Result<Vec<Tensor>, DistributedError> {
609 Ok(Vec::new())
611 }
612
613 pub fn request_boundary_features(
614 &mut self,
615 _target_worker: usize,
616 ) -> Result<Tensor, DistributedError> {
617 torsh_tensor::creation::zeros(&[1, 1])
619 .map_err(|e| DistributedError::TensorError(format!("Failed to create tensor: {:?}", e)))
620 }
621}
622
623#[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#[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#[derive(Debug, Clone)]
683pub enum DistributedError {
684 CommunicationError(String),
686 PartitioningError(String),
688 TensorError(String),
690 ConfigError(String),
692 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#[derive(Debug)]
720pub struct DistributedGraphLayer {
721 pub base_layer: Box<dyn GraphLayer>,
723 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 self.base_layer.forward(graph)
747 }
748
749 fn parameters(&self) -> Vec<Tensor> {
750 self.base_layer.parameters()
751 }
752}
753
754pub mod utils {
756 use super::*;
757
758 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 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 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, 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); let unbalanced_sizes = vec![200, 50, 50, 50];
838 let unbalanced_score = utils::calculate_load_balance(&unbalanced_sizes);
839 assert!(unbalanced_score > 0.0); }
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}