Skip to main content

torsh_distributed/
tensor_parallel.rs

1//! Tensor Parallelism implementation for distributed training
2//!
3//! Tensor parallelism splits individual tensors across multiple devices,
4//! enabling training of models that are too large to fit on a single device.
5//! This is particularly useful for transformer models where we can split
6//! attention and feed-forward layers.
7//!
8//! Enhanced with SciRS2 memory-efficient operations for optimal performance
9//! and reduced memory footprint in distributed training scenarios.
10
11// Framework infrastructure - components designed for future use
12#![allow(dead_code)]
13use crate::collectives::{all_gather, reduce_scatter};
14use crate::{ProcessGroup, TorshDistributedError, TorshResult};
15use std::collections::HashMap;
16use std::sync::Arc;
17use torsh_core::{error::Result, DeviceType, Shape};
18use torsh_nn::{Module, Parameter};
19use torsh_tensor::Tensor;
20use tracing::{debug, info};
21
22// Enhanced SciRS2 integration for memory-efficient tensor operations
23// TODO: These features are not yet available in scirs2_core
24// Uncomment when scirs2_core provides these modules
25// #[cfg(feature = "scirs2-memory")]
26// use scirs2_core::memory::{BufferPool, ChunkProcessor, GlobalBufferPool};
27// #[cfg(feature = "scirs2-memory")]
28// use scirs2_core::memory_efficient::{AdaptiveChunking, DiskBackedArray, ZeroCopyOps};
29// #[cfg(feature = "scirs2-memory")]
30// use scirs2_core::memory_efficient::{ChunkedArray, LazyArray, MemoryMappedArray};
31// #[cfg(feature = "scirs2-memory")]
32// use scirs2_core::parallel_ops::{par_chunks, par_join, par_scope};
33// #[cfg(feature = "scirs2-memory")]
34// use scirs2_core::simd_ops::{simd_dot_product, simd_matrix_multiply};
35
36/// Enhanced tensor parallelism configuration with SciRS2 memory optimizations
37#[derive(Debug, Clone)]
38pub struct TensorParallelConfig {
39    /// Tensor parallel group size
40    pub tp_size: usize,
41    /// Whether to use sequence parallelism
42    pub sequence_parallel: bool,
43    /// Communication backend for tensor parallel operations
44    pub communication_backend: String,
45    /// Whether to use async communication
46    pub async_communication: bool,
47    /// Memory optimization level (0-3)
48    pub memory_optimization_level: u8,
49    /// Enable SciRS2 memory-efficient operations
50    #[cfg(feature = "scirs2-memory")]
51    pub enable_scirs2_memory: bool,
52    /// Use memory-mapped arrays for large tensors
53    #[cfg(feature = "scirs2-memory")]
54    pub use_memory_mapping: bool,
55    /// Enable lazy tensor loading
56    #[cfg(feature = "scirs2-memory")]
57    pub enable_lazy_loading: bool,
58    /// Enable chunked tensor processing
59    #[cfg(feature = "scirs2-memory")]
60    pub enable_chunked_processing: bool,
61    /// Enable SIMD optimizations
62    #[cfg(feature = "scirs2-memory")]
63    pub enable_simd_ops: bool,
64    /// Buffer pool size for memory management
65    #[cfg(feature = "scirs2-memory")]
66    pub buffer_pool_size_mb: usize,
67}
68
69impl Default for TensorParallelConfig {
70    fn default() -> Self {
71        Self {
72            tp_size: 1,
73            sequence_parallel: false,
74            communication_backend: "nccl".to_string(),
75            async_communication: true,
76            memory_optimization_level: 1,
77            #[cfg(feature = "scirs2-memory")]
78            enable_scirs2_memory: true,
79            #[cfg(feature = "scirs2-memory")]
80            use_memory_mapping: true,
81            #[cfg(feature = "scirs2-memory")]
82            enable_lazy_loading: false,
83            #[cfg(feature = "scirs2-memory")]
84            enable_chunked_processing: true,
85            #[cfg(feature = "scirs2-memory")]
86            enable_simd_ops: true,
87            #[cfg(feature = "scirs2-memory")]
88            buffer_pool_size_mb: 512,
89        }
90    }
91}
92
93/// Tensor parallelism strategy
94#[derive(Debug, Clone, PartialEq)]
95pub enum TensorParallelStrategy {
96    /// Split tensor along rows (for weight matrices)
97    RowParallel,
98    /// Split tensor along columns (for weight matrices)
99    ColumnParallel,
100    /// Split along vocabulary dimension (for embeddings)
101    VocabParallel,
102    /// Split along sequence dimension
103    SequenceParallel,
104    /// Split along attention heads
105    AttentionHeadParallel,
106}
107
108/// Tensor parallel layer types
109#[derive(Debug, Clone)]
110pub enum TensorParallelLayer {
111    /// Row-parallel linear layer
112    RowParallelLinear {
113        input_size: usize,
114        output_size: usize,
115        bias: bool,
116        input_is_parallel: bool,
117    },
118    /// Column-parallel linear layer
119    ColumnParallelLinear {
120        input_size: usize,
121        output_size: usize,
122        bias: bool,
123        gather_output: bool,
124    },
125    /// Parallel embedding layer
126    ParallelEmbedding {
127        num_embeddings: usize,
128        embedding_dim: usize,
129        padding_idx: Option<usize>,
130    },
131    /// Parallel attention layer
132    ParallelAttention {
133        hidden_size: usize,
134        num_attention_heads: usize,
135        dropout_prob: f32,
136    },
137}
138
139/// Tensor parallel wrapper for modules
140pub struct TensorParallel {
141    /// The underlying module
142    module: Box<dyn Module>,
143    /// Process group for tensor parallel communication
144    tp_group: Arc<ProcessGroup>,
145    /// Configuration
146    config: TensorParallelConfig,
147    /// Tensor parallel rank within the group
148    tp_rank: usize,
149    /// Layer type and strategy
150    layer_info: TensorParallelLayer,
151    /// Parameter sharding information
152    shard_info: HashMap<String, ShardInfo>,
153    /// Communication buffers
154    comm_buffers: HashMap<String, Tensor>,
155}
156
157/// Information about how a parameter is sharded
158#[derive(Debug, Clone)]
159pub struct ShardInfo {
160    /// Which dimension is sharded
161    pub shard_dim: usize,
162    /// Start index of this shard
163    pub start_idx: usize,
164    /// Size of this shard
165    pub shard_size: usize,
166    /// Original tensor shape
167    pub original_shape: Shape,
168    /// Strategy used for sharding
169    pub strategy: TensorParallelStrategy,
170}
171
172impl TensorParallel {
173    /// Create a new tensor parallel wrapper
174    pub fn new(
175        module: Box<dyn Module>,
176        tp_group: Arc<ProcessGroup>,
177        config: TensorParallelConfig,
178        layer_info: TensorParallelLayer,
179    ) -> TorshResult<Self> {
180        let tp_rank = tp_group.rank() as usize;
181        let tp_size = tp_group.world_size() as usize;
182
183        if tp_size != config.tp_size {
184            return Err(TorshDistributedError::invalid_argument(
185                "tp_size",
186                format!(
187                    "TP group size ({}) doesn't match config TP size ({})",
188                    tp_size, config.tp_size
189                ),
190                format!("tp_size = {}", config.tp_size),
191            ));
192        }
193
194        let mut tensor_parallel = Self {
195            module,
196            tp_group,
197            config,
198            tp_rank,
199            layer_info,
200            shard_info: HashMap::new(),
201            comm_buffers: HashMap::new(),
202        };
203
204        // Initialize parameter sharding
205        tensor_parallel.init_parameter_sharding()?;
206
207        info!(
208            "Initialized tensor parallel layer with TP size {} at rank {}",
209            tp_size, tp_rank
210        );
211
212        Ok(tensor_parallel)
213    }
214
215    /// Initialize parameter sharding based on layer type
216    fn init_parameter_sharding(&mut self) -> TorshResult<()> {
217        let parameters = self.module.parameters();
218
219        match &self.layer_info {
220            TensorParallelLayer::RowParallelLinear { output_size, .. } => {
221                self.shard_row_parallel_parameters(&parameters, *output_size)?;
222            }
223            TensorParallelLayer::ColumnParallelLinear { input_size, .. } => {
224                self.shard_column_parallel_parameters(&parameters, *input_size)?;
225            }
226            TensorParallelLayer::ParallelEmbedding { num_embeddings, .. } => {
227                self.shard_embedding_parameters(&parameters, *num_embeddings)?;
228            }
229            TensorParallelLayer::ParallelAttention {
230                num_attention_heads,
231                ..
232            } => {
233                self.shard_attention_parameters(&parameters, *num_attention_heads)?;
234            }
235        }
236
237        Ok(())
238    }
239
240    /// Shard parameters for row-parallel linear layer
241    fn shard_row_parallel_parameters(
242        &mut self,
243        parameters: &HashMap<String, Parameter>,
244        output_size: usize,
245    ) -> TorshResult<()> {
246        for name in parameters.keys() {
247            if name.contains("weight") {
248                let shard_size = output_size / self.config.tp_size;
249                let start_idx = self.tp_rank * shard_size;
250
251                let shard_info = ShardInfo {
252                    shard_dim: 0, // Row dimension
253                    start_idx,
254                    shard_size,
255                    original_shape: Shape::new(vec![output_size, parameters.len()]), // Simplified
256                    strategy: TensorParallelStrategy::RowParallel,
257                };
258
259                self.shard_info.insert(name.clone(), shard_info);
260                debug!("Sharded parameter '{}' with row-parallel strategy", name);
261            }
262        }
263
264        Ok(())
265    }
266
267    /// Shard parameters for column-parallel linear layer
268    fn shard_column_parallel_parameters(
269        &mut self,
270        parameters: &HashMap<String, Parameter>,
271        input_size: usize,
272    ) -> TorshResult<()> {
273        for name in parameters.keys() {
274            if name.contains("weight") {
275                let shard_size = input_size / self.config.tp_size;
276                let start_idx = self.tp_rank * shard_size;
277
278                let shard_info = ShardInfo {
279                    shard_dim: 1, // Column dimension
280                    start_idx,
281                    shard_size,
282                    original_shape: Shape::new(vec![parameters.len(), input_size]), // Simplified
283                    strategy: TensorParallelStrategy::ColumnParallel,
284                };
285
286                self.shard_info.insert(name.clone(), shard_info);
287                debug!("Sharded parameter '{}' with column-parallel strategy", name);
288            }
289        }
290
291        Ok(())
292    }
293
294    /// Shard parameters for parallel embedding layer
295    fn shard_embedding_parameters(
296        &mut self,
297        parameters: &HashMap<String, Parameter>,
298        num_embeddings: usize,
299    ) -> TorshResult<()> {
300        for name in parameters.keys() {
301            if name.contains("weight") {
302                let shard_size = num_embeddings / self.config.tp_size;
303                let start_idx = self.tp_rank * shard_size;
304
305                let shard_info = ShardInfo {
306                    shard_dim: 0, // Vocabulary dimension
307                    start_idx,
308                    shard_size,
309                    original_shape: Shape::new(vec![num_embeddings, 512]), // Simplified embedding dim
310                    strategy: TensorParallelStrategy::VocabParallel,
311                };
312
313                self.shard_info.insert(name.clone(), shard_info);
314                debug!("Sharded parameter '{}' with vocab-parallel strategy", name);
315            }
316        }
317
318        Ok(())
319    }
320
321    /// Shard parameters for parallel attention layer
322    fn shard_attention_parameters(
323        &mut self,
324        parameters: &HashMap<String, Parameter>,
325        num_attention_heads: usize,
326    ) -> TorshResult<()> {
327        let heads_per_partition = num_attention_heads / self.config.tp_size;
328        let start_head = self.tp_rank * heads_per_partition;
329
330        for name in parameters.keys() {
331            if name.contains("query")
332                || name.contains("key")
333                || name.contains("value")
334                || name.contains("output")
335            {
336                let shard_info = ShardInfo {
337                    shard_dim: 0, // Head dimension
338                    start_idx: start_head,
339                    shard_size: heads_per_partition,
340                    original_shape: Shape::new(vec![num_attention_heads, 64]), // Simplified head dim
341                    strategy: TensorParallelStrategy::AttentionHeadParallel,
342                };
343
344                self.shard_info.insert(name.clone(), shard_info);
345                debug!(
346                    "Sharded parameter '{}' with attention-head-parallel strategy",
347                    name
348                );
349            }
350        }
351
352        Ok(())
353    }
354
355    /// Perform all-gather communication for row-parallel layers
356    async fn all_gather_for_row_parallel(&mut self, input: &Tensor) -> TorshResult<Tensor> {
357        debug!("Performing all-gather for row-parallel layer");
358
359        let mut gathered_tensors = Vec::new();
360        all_gather(&mut gathered_tensors, input, &self.tp_group).await?;
361
362        // Concatenate gathered tensors along the row dimension
363        if gathered_tensors.len() == 1 {
364            Ok(gathered_tensors
365                .into_iter()
366                .next()
367                .expect("gathered_tensors should not be empty"))
368        } else {
369            // For simplicity, just return the first tensor
370            // In a real implementation, we would concatenate properly
371            Ok(gathered_tensors
372                .into_iter()
373                .next()
374                .expect("gathered_tensors should not be empty"))
375        }
376    }
377
378    /// Perform reduce-scatter communication for column-parallel layers
379    async fn reduce_scatter_for_column_parallel(&mut self, input: &Tensor) -> TorshResult<Tensor> {
380        debug!("Performing reduce-scatter for column-parallel layer");
381
382        let mut output_tensor = input.clone();
383        reduce_scatter(
384            &mut output_tensor,
385            input,
386            crate::backend::ReduceOp::Sum,
387            &self.tp_group,
388        )
389        .await?;
390
391        // Return the local shard
392        Ok(output_tensor)
393    }
394
395    /// Perform sequence-parallel communication
396    async fn sequence_parallel_communication(&mut self, input: &Tensor) -> TorshResult<Tensor> {
397        debug!("Performing sequence-parallel communication");
398
399        if self.config.sequence_parallel {
400            // Gather along sequence dimension
401            self.all_gather_for_row_parallel(input).await
402        } else {
403            Ok(input.clone())
404        }
405    }
406
407    /// Get tensor parallel rank
408    pub fn tp_rank(&self) -> usize {
409        self.tp_rank
410    }
411
412    /// Get tensor parallel world size
413    pub fn tp_world_size(&self) -> usize {
414        self.config.tp_size
415    }
416
417    /// Get sharding information for a parameter
418    pub fn get_shard_info(&self, param_name: &str) -> Option<&ShardInfo> {
419        self.shard_info.get(param_name)
420    }
421
422    /// Check if layer uses sequence parallelism
423    pub fn uses_sequence_parallel(&self) -> bool {
424        self.config.sequence_parallel
425    }
426
427    /// Get memory usage statistics
428    pub fn memory_stats(&self) -> TensorParallelStats {
429        let total_params = self.module.parameters().len();
430        let sharded_params = self.shard_info.len();
431        let memory_reduction = if total_params > 0 {
432            1.0 - (sharded_params as f64 / total_params as f64)
433        } else {
434            0.0
435        };
436
437        TensorParallelStats {
438            tp_rank: self.tp_rank,
439            tp_world_size: self.config.tp_size,
440            total_parameters: total_params,
441            sharded_parameters: sharded_params,
442            memory_reduction_ratio: memory_reduction,
443            communication_overhead_ms: 0.0, // Would be measured in real implementation
444        }
445    }
446
447    // Enhanced SciRS2 memory-efficient operations
448
449    /// Create memory-efficient tensor shard using SciRS2 operations
450    #[cfg(feature = "scirs2-memory")]
451    pub fn create_memory_efficient_shard(
452        &self,
453        tensor: &Tensor,
454        shard_dim: usize,
455        use_memory_mapping: bool,
456    ) -> TorshResult<Tensor> {
457        debug!(
458            "Creating memory-efficient shard for tensor with shape {:?}",
459            tensor.shape()
460        );
461
462        if !self.config.enable_scirs2_memory {
463            return self.create_chunked_shard(tensor, shard_dim);
464        }
465
466        // Use SciRS2 memory-efficient operations
467        // TODO: Implement proper memory-mapped tensor support
468        // For now, use chunked processing for all cases
469        let _use_mapping = use_memory_mapping && tensor.numel() > 1_000_000;
470        if self.config.enable_chunked_processing {
471            // Use chunked processing for tensors
472            self.create_chunked_shard(tensor, shard_dim)
473        } else {
474            // Fallback to standard sharding
475            self.create_chunked_shard(tensor, shard_dim)
476        }
477    }
478
479    /// Create chunked tensor shard using SciRS2 operations
480    #[cfg(feature = "scirs2-memory")]
481    fn create_chunked_shard(&self, tensor: &Tensor, shard_dim: usize) -> TorshResult<Tensor> {
482        // TODO: Implement proper chunked array support with AdaptiveChunking
483        // For now, use narrow operation to create the shard along one dimension
484        let shard_size = tensor.shape().dims()[shard_dim] / self.config.tp_size;
485        let start_idx = self.tp_rank * shard_size;
486
487        // Use narrow to extract a slice along the specified dimension
488        // narrow(dim, start, length) creates a new tensor that's a narrowed version
489        let shard_tensor = tensor.narrow(shard_dim as i32, start_idx as i64, shard_size)?;
490
491        info!(
492            "Created chunked shard with shape {:?}",
493            shard_tensor.shape()
494        );
495        Ok(shard_tensor)
496    }
497
498    /// Perform SIMD-optimized tensor operations for parallel processing
499    #[cfg(feature = "scirs2-memory")]
500    pub fn simd_optimized_forward(&self, input: &Tensor, weights: &Tensor) -> TorshResult<Tensor> {
501        if !self.config.enable_simd_ops {
502            return self.standard_forward(input, weights);
503        }
504
505        debug!("Performing SIMD-optimized forward pass");
506
507        match (input.dtype(), weights.dtype()) {
508            (torsh_core::DType::F32, torsh_core::DType::F32) => {
509                let input_shape_owned = input.shape();
510                let weights_shape_owned = weights.shape();
511                let input_dims = input_shape_owned.dims();
512                let weights_dims = weights_shape_owned.dims();
513                if input_dims.len() == 2
514                    && weights_dims.len() == 2
515                    && input_dims[1] == weights_dims[0]
516                {
517                    let m = input_dims[0];
518                    let k = input_dims[1];
519                    let n = weights_dims[1];
520                    let a_data = input.to_vec().map_err(|e| {
521                        TorshDistributedError::InternalError(format!(
522                            "failed to materialize input data: {e}"
523                        ))
524                    })?;
525                    let b_data = weights.to_vec().map_err(|e| {
526                        TorshDistributedError::InternalError(format!(
527                            "failed to materialize weights data: {e}"
528                        ))
529                    })?;
530                    let mut c_data = vec![0.0f32; m * n];
531                    scirs2_core::simd_ops::simd_matrix_multiply_f32(
532                        m,
533                        k,
534                        n,
535                        1.0,
536                        &a_data,
537                        &b_data,
538                        0.0,
539                        &mut c_data,
540                    );
541                    Tensor::from_vec(c_data, &[m, n]).map_err(|e| {
542                        TorshDistributedError::InternalError(format!(
543                            "failed to build matmul output tensor: {e}"
544                        ))
545                    })
546                } else {
547                    self.standard_forward(input, weights)
548                }
549            }
550            _ => self.standard_forward(input, weights),
551        }
552    }
553
554    /// Parallel all-gather operation using SciRS2 parallel processing
555    #[cfg(feature = "scirs2-memory")]
556    pub async fn parallel_all_gather(&self, tensor: &Tensor) -> TorshResult<Tensor> {
557        // TODO: Implement proper parallel all-gather with SciRS2 optimizations
558        // For now, use a simplified approach
559        debug!("Performing parallel all-gather (simplified implementation)");
560
561        // Create output buffer for gathered tensors
562        let mut output: Vec<Tensor> = Vec::with_capacity(self.config.tp_size);
563
564        // Call all_gather with proper signature
565        all_gather(&mut output, tensor, &self.tp_group).await?;
566
567        // Concatenate the gathered tensors
568        // For simplicity, just return the first tensor (this rank's data)
569        // In a real implementation, we'd concatenate all gathered tensors
570        let result = if !output.is_empty() {
571            output
572                .into_iter()
573                .next()
574                .expect("output should not be empty")
575        } else {
576            tensor.clone()
577        };
578
579        info!(
580            "Parallel all-gather completed with shape {:?}",
581            result.shape()
582        );
583        Ok(result)
584    }
585
586    /// Initialize memory-efficient buffer pools
587    #[cfg(feature = "scirs2-memory")]
588    pub fn init_scirs2_memory_pools(&mut self) -> TorshResult<()> {
589        if !self.config.enable_scirs2_memory {
590            return Ok(());
591        }
592
593        info!(
594            "Initializing SciRS2 memory pools with {}MB buffer",
595            self.config.buffer_pool_size_mb
596        );
597
598        // TODO: Initialize global buffer pool when available in scirs2_core
599        // let buffer_pool =
600        //     GlobalBufferPool::initialize(self.config.buffer_pool_size_mb * 1024 * 1024)?;
601        //
602        // // Pre-allocate buffers for common tensor sizes
603        // let common_sizes = vec![
604        //     1024 * 1024,      // 1M elements
605        //     4 * 1024 * 1024,  // 4M elements
606        //     16 * 1024 * 1024, // 16M elements
607        // ];
608        //
609        // for size in common_sizes {
610        //     buffer_pool.pre_allocate_buffer(size * 4)?; // 4 bytes per f32
611        // }
612
613        info!("SciRS2 memory pools initialized successfully");
614        Ok(())
615    }
616
617    /// Get memory efficiency statistics
618    #[cfg(feature = "scirs2-memory")]
619    pub fn get_memory_efficiency_stats(&self) -> HashMap<String, f64> {
620        let mut stats = HashMap::new();
621
622        if self.config.enable_scirs2_memory {
623            // TODO: Re-enable buffer pool stats when GlobalBufferPool is properly available
624            // if let Ok(buffer_pool) = GlobalBufferPool::instance() {
625            //     stats.insert("buffer_pool_utilization".to_string(), buffer_pool.utilization_ratio());
626            //     stats.insert("buffer_pool_fragmentation".to_string(), buffer_pool.fragmentation_ratio());
627            //     stats.insert("total_allocations".to_string(), buffer_pool.total_allocations() as f64);
628            //     stats.insert("cache_hit_ratio".to_string(), buffer_pool.cache_hit_ratio());
629            // }
630        }
631
632        // Add tensor parallelism specific stats
633        // TODO: Implement get_stats() method or remove this call
634        stats.insert(
635            "memory_reduction_ratio".to_string(),
636            1.0 / self.config.tp_size as f64, // Estimated reduction ratio
637        );
638        stats.insert(
639            "tp_efficiency".to_string(),
640            1.0 / self.config.tp_size as f64,
641        );
642
643        stats
644    }
645
646    // Helper methods
647
648    #[cfg(feature = "scirs2-memory")]
649    fn compute_output_shape(
650        &self,
651        input_shape: &Shape,
652        weights_shape: &Shape,
653    ) -> TorshResult<Shape> {
654        // Simplified shape computation - in real implementation would be more sophisticated
655        let input_dims = input_shape.dims();
656        let weights_dims = weights_shape.dims();
657
658        let output_dims = vec![input_dims[0], weights_dims[1]];
659        Shape::from_dims(output_dims).map_err(|e| {
660            TorshDistributedError::internal_error(format!("Failed to create shape: {}", e))
661        })
662    }
663
664    #[cfg(feature = "scirs2-memory")]
665    fn compute_gathered_shape(&self, shard_shape: &Shape) -> TorshResult<Shape> {
666        let mut dims = shard_shape.dims().to_vec();
667        dims[1] *= self.config.tp_size; // Assuming gathering along dimension 1
668        Shape::from_dims(dims).map_err(|e| {
669            TorshDistributedError::internal_error(format!("Failed to create gathered shape: {}", e))
670        })
671    }
672
673    #[cfg(feature = "scirs2-memory")]
674    fn standard_forward(&self, input: &Tensor, weights: &Tensor) -> TorshResult<Tensor> {
675        // Fallback implementation without SIMD optimizations
676        info!("Using standard forward pass (SIMD disabled)");
677
678        // Basic matrix multiplication implementation
679        let result = input.matmul(weights)?;
680        Ok(result)
681    }
682}
683
684impl Module for TensorParallel {
685    fn forward(&self, input: &Tensor) -> Result<Tensor> {
686        match &self.layer_info {
687            TensorParallelLayer::RowParallelLinear {
688                input_is_parallel, ..
689            } => {
690                // For row-parallel, input might need all-gather first
691                let processed_input = if *input_is_parallel {
692                    input.clone()
693                } else {
694                    // Would need async version in real implementation
695                    input.clone()
696                };
697
698                // Forward through local shard
699                let local_output = self.module.forward(&processed_input)?;
700
701                // All-reduce the output (since each rank computes a partial result)
702                // For simplicity, returning local output for now
703                Ok(local_output)
704            }
705
706            TensorParallelLayer::ColumnParallelLinear { gather_output, .. } => {
707                // Forward through local shard
708                let local_output = self.module.forward(input)?;
709
710                if *gather_output {
711                    // All-gather outputs from all ranks
712                    // For simplicity, returning local output for now
713                    Ok(local_output)
714                } else {
715                    Ok(local_output)
716                }
717            }
718
719            TensorParallelLayer::ParallelEmbedding { .. } => {
720                // For vocab-parallel embeddings, only some ranks have relevant embeddings
721                let output = self.module.forward(input)?;
722
723                // All-reduce to combine embeddings from different vocab shards
724                // For simplicity, returning local output for now
725                Ok(output)
726            }
727
728            TensorParallelLayer::ParallelAttention { .. } => {
729                // For attention, each rank computes a subset of attention heads
730                let output = self.module.forward(input)?;
731
732                // Concatenate attention heads from all ranks
733                // For simplicity, returning local output for now
734                Ok(output)
735            }
736        }
737    }
738
739    fn parameters(&self) -> HashMap<String, Parameter> {
740        // Return only the local sharded parameters
741        let all_params = self.module.parameters();
742        let mut sharded_params = HashMap::new();
743
744        for (name, param) in all_params {
745            if let Some(_shard_info) = self.shard_info.get(&name) {
746                // Extract the local shard of this parameter
747                let tensor = param.tensor();
748                let _tensor_guard = tensor.read();
749
750                // For simplicity, return the whole parameter
751                // In a real implementation, we would slice based on shard_info
752                sharded_params.insert(name, param);
753            } else {
754                // Parameter is not sharded, include as-is
755                sharded_params.insert(name, param);
756            }
757        }
758
759        sharded_params
760    }
761
762    fn named_parameters(&self) -> HashMap<String, Parameter> {
763        self.parameters()
764    }
765
766    fn training(&self) -> bool {
767        self.module.training()
768    }
769
770    fn train(&mut self) {
771        self.module.train()
772    }
773
774    fn eval(&mut self) {
775        self.module.eval()
776    }
777
778    fn to_device(&mut self, device: DeviceType) -> Result<()> {
779        self.module.to_device(device)
780    }
781}
782
783/// Statistics for tensor parallelism
784#[derive(Debug, Clone)]
785pub struct TensorParallelStats {
786    /// Tensor parallel rank
787    pub tp_rank: usize,
788    /// Tensor parallel world size
789    pub tp_world_size: usize,
790    /// Total number of parameters in the original model
791    pub total_parameters: usize,
792    /// Number of parameters that are sharded
793    pub sharded_parameters: usize,
794    /// Memory reduction ratio due to sharding
795    pub memory_reduction_ratio: f64,
796    /// Communication overhead in milliseconds
797    pub communication_overhead_ms: f64,
798}
799
800/// Utility functions for tensor parallelism
801pub mod utils {
802    use super::*;
803
804    /// Create a row-parallel linear layer
805    pub fn create_row_parallel_linear(
806        input_size: usize,
807        output_size: usize,
808        bias: bool,
809        input_is_parallel: bool,
810        tp_group: Arc<ProcessGroup>,
811        config: Option<TensorParallelConfig>,
812    ) -> TorshResult<TensorParallel> {
813        let linear = torsh_nn::layers::Linear::new(input_size, output_size, bias);
814        let module = Box::new(linear) as Box<dyn Module>;
815
816        let layer_info = TensorParallelLayer::RowParallelLinear {
817            input_size,
818            output_size,
819            bias,
820            input_is_parallel,
821        };
822
823        let config = config.unwrap_or_default();
824        TensorParallel::new(module, tp_group, config, layer_info)
825    }
826
827    /// Create a column-parallel linear layer
828    pub fn create_column_parallel_linear(
829        input_size: usize,
830        output_size: usize,
831        bias: bool,
832        gather_output: bool,
833        tp_group: Arc<ProcessGroup>,
834        config: Option<TensorParallelConfig>,
835    ) -> TorshResult<TensorParallel> {
836        let linear = torsh_nn::layers::Linear::new(input_size, output_size, bias);
837        let module = Box::new(linear) as Box<dyn Module>;
838
839        let layer_info = TensorParallelLayer::ColumnParallelLinear {
840            input_size,
841            output_size,
842            bias,
843            gather_output,
844        };
845
846        let config = config.unwrap_or_default();
847        TensorParallel::new(module, tp_group, config, layer_info)
848    }
849
850    /// Split a tensor along a given dimension for tensor parallelism
851    pub fn split_tensor_for_tp(
852        tensor: &Tensor,
853        split_dim: usize,
854        tp_rank: usize,
855        tp_size: usize,
856    ) -> TorshResult<Tensor> {
857        let shape = tensor.shape();
858        let dim_size = shape.dims()[split_dim];
859
860        if dim_size % tp_size != 0 {
861            return Err(TorshDistributedError::invalid_argument(
862                "tensor_dimension",
863                format!(
864                    "Dimension size {} is not divisible by TP size {}",
865                    dim_size, tp_size
866                ),
867                format!("dimension size must be multiple of tp_size ({})", tp_size),
868            ));
869        }
870
871        let shard_size = dim_size / tp_size;
872        let start_idx = tp_rank * shard_size;
873        let end_idx = start_idx + shard_size;
874
875        Ok(tensor.slice(split_dim, start_idx, end_idx)?.to_tensor()?)
876    }
877
878    /// Gather tensors from all TP ranks along a given dimension
879    pub async fn gather_tensor_from_tp(
880        tensor: &Tensor,
881        _gather_dim: usize,
882        tp_group: &ProcessGroup,
883    ) -> TorshResult<Tensor> {
884        let mut gathered_tensors = Vec::new();
885        all_gather(&mut gathered_tensors, tensor, tp_group).await?;
886
887        // For simplicity, return the first tensor
888        // In a real implementation, we would concatenate along gather_dim
889        if gathered_tensors.is_empty() {
890            Err(TorshDistributedError::communication_error(
891                "tensor_parallel",
892                "No tensors gathered",
893            ))
894        } else {
895            Ok(gathered_tensors
896                .into_iter()
897                .next()
898                .expect("gathered_tensors should not be empty"))
899        }
900    }
901}
902
903#[cfg(test)]
904mod tests {
905    use super::*;
906    use crate::{init_process_group, BackendType};
907
908    #[tokio::test]
909    async fn test_tensor_parallel_config() {
910        let config = TensorParallelConfig::default();
911        assert_eq!(config.tp_size, 1);
912        assert!(!config.sequence_parallel);
913        assert_eq!(config.communication_backend, "nccl");
914        assert!(config.async_communication);
915    }
916
917    #[tokio::test]
918    async fn test_shard_info() {
919        let shard_info = ShardInfo {
920            shard_dim: 0,
921            start_idx: 0,
922            shard_size: 128,
923            original_shape: Shape::new(vec![512, 256]),
924            strategy: TensorParallelStrategy::RowParallel,
925        };
926
927        assert_eq!(shard_info.shard_dim, 0);
928        assert_eq!(shard_info.shard_size, 128);
929        assert_eq!(shard_info.strategy, TensorParallelStrategy::RowParallel);
930    }
931
932    #[tokio::test]
933    async fn test_tensor_parallel_stats() {
934        let stats = TensorParallelStats {
935            tp_rank: 0,
936            tp_world_size: 4,
937            total_parameters: 1000,
938            sharded_parameters: 800,
939            memory_reduction_ratio: 0.75,
940            communication_overhead_ms: 5.2,
941        };
942
943        assert_eq!(stats.tp_rank, 0);
944        assert_eq!(stats.tp_world_size, 4);
945        assert_eq!(stats.memory_reduction_ratio, 0.75);
946    }
947
948    #[tokio::test]
949    async fn test_create_row_parallel_linear() -> TorshResult<()> {
950        let process_group =
951            Arc::new(init_process_group(BackendType::Gloo, 0, 2, "127.0.0.1", 12345).await?);
952
953        let config = TensorParallelConfig {
954            tp_size: 2,
955            ..Default::default()
956        };
957
958        let tp_layer =
959            utils::create_row_parallel_linear(128, 256, true, false, process_group, Some(config))?;
960
961        assert_eq!(tp_layer.tp_rank(), 0);
962        assert_eq!(tp_layer.tp_world_size(), 2);
963
964        Ok(())
965    }
966
967    #[tokio::test]
968    async fn test_create_column_parallel_linear() -> TorshResult<()> {
969        let process_group =
970            Arc::new(init_process_group(BackendType::Gloo, 0, 2, "127.0.0.1", 12346).await?);
971
972        let config = TensorParallelConfig {
973            tp_size: 2,
974            ..Default::default()
975        };
976
977        let tp_layer = utils::create_column_parallel_linear(
978            128,
979            256,
980            true,
981            true,
982            process_group,
983            Some(config),
984        )?;
985
986        assert_eq!(tp_layer.tp_rank(), 0);
987        assert_eq!(tp_layer.tp_world_size(), 2);
988
989        Ok(())
990    }
991
992    #[test]
993    fn test_tensor_parallel_strategies() {
994        assert_ne!(
995            TensorParallelStrategy::RowParallel,
996            TensorParallelStrategy::ColumnParallel
997        );
998        assert_ne!(
999            TensorParallelStrategy::VocabParallel,
1000            TensorParallelStrategy::SequenceParallel
1001        );
1002        assert_ne!(
1003            TensorParallelStrategy::AttentionHeadParallel,
1004            TensorParallelStrategy::RowParallel
1005        );
1006    }
1007
1008    #[tokio::test]
1009    async fn test_split_tensor_for_tp() -> TorshResult<()> {
1010        let tensor = torsh_tensor::creation::ones(&[8, 16])?;
1011
1012        let shard = utils::split_tensor_for_tp(&tensor, 1, 0, 2)?;
1013        assert_eq!(shard.shape().dims(), &[8, 8]);
1014
1015        Ok(())
1016    }
1017}