#![allow(dead_code)]
#![allow(clippy::await_holding_lock)]
use crate::collectives::{all_gather, all_reduce, broadcast, reduce_scatter};
use crate::{ProcessGroup, TorshDistributedError, TorshResult};
#[cfg(feature = "scirs2-simd")]
use std::collections::HashMap;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tokio::sync::Semaphore;
use torsh_tensor::Tensor;
use tracing::{debug, info};
#[cfg(feature = "scirs2-simd")]
use scirs2_core::ndarray::ArrayView1;
#[cfg(feature = "scirs2-simd")]
use scirs2_core::simd_ops::SimdUnifiedOps;
#[cfg(feature = "scirs2-simd")]
#[derive(Debug, Clone, PartialEq)]
pub enum ParallelExecutionStrategy {
UniformChunking,
AdaptiveLoadBalancing,
WorkStealing,
PriorityBased,
}
#[derive(Debug, Clone)]
pub struct SchedulerConfig {
pub max_concurrent_ops: usize,
pub bandwidth_limit_bps: u64,
pub strategy: SchedulingStrategy,
pub enable_priorities: bool,
pub adaptive_scheduling: bool,
pub timeout_ms: u64,
pub enable_compression: bool,
pub compression_threshold: usize,
#[cfg(feature = "scirs2-simd")]
pub enable_simd_optimization: bool,
#[cfg(feature = "scirs2-simd")]
pub simd_chunk_size: usize,
#[cfg(feature = "scirs2-simd")]
pub enable_auto_vectorization: bool,
#[cfg(feature = "scirs2-simd")]
pub parallel_execution_strategy: ParallelExecutionStrategy,
}
impl Default for SchedulerConfig {
fn default() -> Self {
Self {
max_concurrent_ops: 4,
bandwidth_limit_bps: 1_000_000_000, strategy: SchedulingStrategy::PriorityBased,
enable_priorities: true,
adaptive_scheduling: true,
timeout_ms: 30000,
enable_compression: false,
compression_threshold: 1024 * 1024, #[cfg(feature = "scirs2-simd")]
enable_simd_optimization: true,
#[cfg(feature = "scirs2-simd")]
simd_chunk_size: 1024,
#[cfg(feature = "scirs2-simd")]
enable_auto_vectorization: true,
#[cfg(feature = "scirs2-simd")]
parallel_execution_strategy: ParallelExecutionStrategy::AdaptiveLoadBalancing,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SchedulingStrategy {
FIFO,
PriorityBased,
ShortestJobFirst,
RoundRobin,
Adaptive,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CommunicationOp {
AllReduce,
AllGather,
ReduceScatter,
Broadcast,
PointToPoint,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Priority {
Low = 0,
Normal = 1,
High = 2,
Critical = 3,
}
pub struct CommunicationTask {
pub id: String,
pub op_type: CommunicationOp,
pub priority: Priority,
pub tensor: Tensor,
pub process_group: Arc<ProcessGroup>,
pub estimated_time_ms: u64,
pub created_at: Instant,
pub response_tx: tokio::sync::oneshot::Sender<TorshResult<Tensor>>,
}
impl std::fmt::Debug for CommunicationTask {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CommunicationTask")
.field("id", &self.id)
.field("op_type", &self.op_type)
.field("priority", &self.priority)
.field("estimated_time_ms", &self.estimated_time_ms)
.field("created_at", &self.created_at)
.finish()
}
}
pub struct CommunicationScheduler {
config: SchedulerConfig,
task_queue: Arc<Mutex<VecDeque<CommunicationTask>>>,
concurrency_semaphore: Arc<Semaphore>,
bandwidth_monitor: Arc<Mutex<BandwidthMonitor>>,
stats: Arc<Mutex<SchedulerStats>>,
shutdown_tx: Arc<Mutex<Option<tokio::sync::broadcast::Sender<()>>>>,
worker_handles: Arc<Mutex<Vec<tokio::task::JoinHandle<()>>>>,
}
#[derive(Debug)]
struct BandwidthMonitor {
recent_measurements: VecDeque<(Instant, u64)>,
available_bandwidth: u64,
last_measurement: Instant,
}
impl BandwidthMonitor {
fn new(initial_bandwidth: u64) -> Self {
Self {
recent_measurements: VecDeque::new(),
available_bandwidth: initial_bandwidth,
last_measurement: Instant::now(),
}
}
fn update_bandwidth(&mut self, bytes_transferred: u64, duration: Duration) {
let bandwidth = if duration.as_secs_f64() > 0.0 {
(bytes_transferred as f64 / duration.as_secs_f64()) as u64
} else {
self.available_bandwidth
};
let now = Instant::now();
self.recent_measurements.push_back((now, bandwidth));
while let Some(&(timestamp, _)) = self.recent_measurements.front() {
if now.duration_since(timestamp) > Duration::from_secs(10) {
self.recent_measurements.pop_front();
} else {
break;
}
}
if !self.recent_measurements.is_empty() {
let total_bandwidth: u64 = self.recent_measurements.iter().map(|(_, bw)| *bw).sum();
self.available_bandwidth = total_bandwidth / self.recent_measurements.len() as u64;
}
self.last_measurement = now;
}
fn get_available_bandwidth(&self) -> u64 {
self.available_bandwidth
}
}
#[derive(Debug, Clone, Default)]
pub struct SchedulerStats {
pub total_tasks: u64,
pub completed_tasks: u64,
pub failed_tasks: u64,
pub avg_queue_time_ms: f64,
pub avg_execution_time_ms: f64,
pub current_queue_size: usize,
pub peak_queue_size: usize,
pub total_bytes_transferred: u64,
pub avg_bandwidth_utilization: f64,
}
impl CommunicationScheduler {
pub fn new(config: SchedulerConfig) -> Self {
info!(
"Creating communication scheduler with strategy: {:?}",
config.strategy
);
let bandwidth_monitor = BandwidthMonitor::new(config.bandwidth_limit_bps);
Self {
concurrency_semaphore: Arc::new(Semaphore::new(config.max_concurrent_ops)),
task_queue: Arc::new(Mutex::new(VecDeque::new())),
bandwidth_monitor: Arc::new(Mutex::new(bandwidth_monitor)),
stats: Arc::new(Mutex::new(SchedulerStats::default())),
shutdown_tx: Arc::new(Mutex::new(None)),
worker_handles: Arc::new(Mutex::new(Vec::new())),
config,
}
}
pub async fn start(&self) -> TorshResult<()> {
info!("Starting communication scheduler");
let (shutdown_tx, shutdown_rx) = tokio::sync::broadcast::channel::<()>(1);
*self
.shutdown_tx
.lock()
.expect("lock should not be poisoned") = Some(shutdown_tx);
let num_workers = self.config.max_concurrent_ops;
let mut handles = self
.worker_handles
.lock()
.expect("lock should not be poisoned");
for worker_id in 0..num_workers {
let task_queue = self.task_queue.clone();
let semaphore = self.concurrency_semaphore.clone();
let bandwidth_monitor = self.bandwidth_monitor.clone();
let stats = self.stats.clone();
let config = self.config.clone();
let mut worker_shutdown_rx = shutdown_rx.resubscribe();
let handle = tokio::spawn(async move {
loop {
tokio::select! {
_ = worker_shutdown_rx.recv() => {
debug!("Worker {} shutting down", worker_id);
break;
}
_ = tokio::time::sleep(Duration::from_millis(10)) => {
if let Some(task) = Self::get_next_task(&task_queue, &config) {
Self::execute_task(task, &semaphore, &bandwidth_monitor, &stats).await;
}
}
}
}
});
handles.push(handle);
}
info!(
"Communication scheduler started with {} workers",
num_workers
);
Ok(())
}
pub async fn schedule_task(
&self,
op_type: CommunicationOp,
tensor: Tensor,
process_group: Arc<ProcessGroup>,
priority: Priority,
) -> TorshResult<Tensor> {
let (response_tx, response_rx) = tokio::sync::oneshot::channel();
let estimated_time = self.estimate_execution_time(&tensor, &op_type);
let task_id = uuid::Uuid::new_v4().to_string();
let task = CommunicationTask {
id: task_id.clone(),
op_type: op_type.clone(),
priority,
tensor,
process_group,
estimated_time_ms: estimated_time,
created_at: Instant::now(),
response_tx,
};
{
let mut queue = self.task_queue.lock().expect("lock should not be poisoned");
queue.push_back(task);
let mut stats = self.stats.lock().expect("lock should not be poisoned");
stats.total_tasks += 1;
stats.current_queue_size = queue.len();
if queue.len() > stats.peak_queue_size {
stats.peak_queue_size = queue.len();
}
}
debug!("Scheduled {:?} task with priority {:?}", op_type, priority);
match tokio::time::timeout(Duration::from_millis(self.config.timeout_ms), response_rx).await
{
Ok(Ok(result)) => result,
Ok(Err(_)) => Err(TorshDistributedError::communication_error(
"Task execution",
"Task response channel closed",
)),
Err(_) => Err(TorshDistributedError::communication_error(
"Task execution",
"Task timeout",
)),
}
}
fn get_next_task(
task_queue: &Arc<Mutex<VecDeque<CommunicationTask>>>,
config: &SchedulerConfig,
) -> Option<CommunicationTask> {
let mut queue = task_queue.lock().expect("lock should not be poisoned");
if queue.is_empty() {
return None;
}
let task_index = match config.strategy {
SchedulingStrategy::FIFO => 0,
SchedulingStrategy::PriorityBased => Self::find_highest_priority_task(&queue),
SchedulingStrategy::ShortestJobFirst => Self::find_shortest_job(&queue),
SchedulingStrategy::RoundRobin => {
0
}
SchedulingStrategy::Adaptive => {
Self::find_adaptive_task(&queue)
}
};
if task_index < queue.len() {
Some(
queue
.remove(task_index)
.expect("task should exist at valid index"),
)
} else {
None
}
}
fn find_highest_priority_task(queue: &VecDeque<CommunicationTask>) -> usize {
queue
.iter()
.enumerate()
.max_by_key(|(_, task)| task.priority)
.map(|(i, _)| i)
.unwrap_or(0)
}
fn find_shortest_job(queue: &VecDeque<CommunicationTask>) -> usize {
queue
.iter()
.enumerate()
.min_by_key(|(_, task)| task.estimated_time_ms)
.map(|(i, _)| i)
.unwrap_or(0)
}
fn find_adaptive_task(queue: &VecDeque<CommunicationTask>) -> usize {
queue
.iter()
.enumerate()
.min_by_key(|(_, task)| {
let priority_score = 4 - task.priority as u64; let time_score = task.estimated_time_ms / 100; priority_score * 1000 + time_score
})
.map(|(i, _)| i)
.unwrap_or(0)
}
async fn execute_task(
task: CommunicationTask,
semaphore: &Arc<Semaphore>,
bandwidth_monitor: &Arc<Mutex<BandwidthMonitor>>,
stats: &Arc<Mutex<SchedulerStats>>,
) {
let _permit = semaphore
.acquire()
.await
.expect("semaphore should not be closed");
let start_time = Instant::now();
debug!("Executing task: {} ({:?})", task.id, task.op_type);
let result = match task.op_type {
CommunicationOp::AllReduce => {
let mut tensor = task.tensor.clone();
all_reduce(
&mut tensor,
crate::backend::ReduceOp::Sum,
&task.process_group,
)
.await
.map(|_| tensor)
}
CommunicationOp::AllGather => {
let mut gathered = Vec::new();
all_gather(&mut gathered, &task.tensor, &task.process_group)
.await
.map(|_| {
if let Some(tensor) = gathered.into_iter().next() {
tensor
} else {
task.tensor.clone()
}
})
}
CommunicationOp::ReduceScatter => {
let mut output_tensor = task.tensor.clone();
reduce_scatter(
&mut output_tensor,
&task.tensor,
crate::backend::ReduceOp::Sum,
&task.process_group,
)
.await
.map(|_| output_tensor)
}
CommunicationOp::Broadcast => {
let mut tensor = task.tensor.clone();
broadcast(&mut tensor, 0, &task.process_group)
.await
.map(|_| tensor)
}
CommunicationOp::PointToPoint => {
Ok(task.tensor.clone())
}
};
let execution_time = start_time.elapsed();
let queue_time = start_time.duration_since(task.created_at);
if let Ok(ref tensor) = result {
let bytes_transferred = tensor.numel() * std::mem::size_of::<f32>();
bandwidth_monitor
.lock()
.expect("lock should not be poisoned")
.update_bandwidth(bytes_transferred as u64, execution_time);
}
{
let mut stats_guard = stats.lock().expect("lock should not be poisoned");
stats_guard.completed_tasks += 1;
stats_guard.current_queue_size = stats_guard.current_queue_size.saturating_sub(1);
let total_completed = stats_guard.completed_tasks as f64;
stats_guard.avg_queue_time_ms = (stats_guard.avg_queue_time_ms
* (total_completed - 1.0)
+ queue_time.as_millis() as f64)
/ total_completed;
stats_guard.avg_execution_time_ms = (stats_guard.avg_execution_time_ms
* (total_completed - 1.0)
+ execution_time.as_millis() as f64)
/ total_completed;
if let Ok(ref tensor) = result {
stats_guard.total_bytes_transferred +=
tensor.numel() as u64 * std::mem::size_of::<f32>() as u64;
}
if result.is_err() {
stats_guard.failed_tasks += 1;
}
}
let _ = task.response_tx.send(result);
debug!("Task {} completed in {:?}", task.id, execution_time);
}
fn estimate_execution_time(&self, tensor: &Tensor, op_type: &CommunicationOp) -> u64 {
let tensor_size = tensor.numel() * std::mem::size_of::<f32>();
let bandwidth = self
.bandwidth_monitor
.lock()
.expect("lock should not be poisoned")
.get_available_bandwidth();
let base_time_ms = if bandwidth > 0 {
(tensor_size as u64 * 1000) / bandwidth
} else {
100 };
let overhead_ms = match op_type {
CommunicationOp::AllReduce => 50,
CommunicationOp::AllGather => 30,
CommunicationOp::ReduceScatter => 40,
CommunicationOp::Broadcast => 20,
CommunicationOp::PointToPoint => 10,
};
base_time_ms + overhead_ms
}
pub fn get_stats(&self) -> SchedulerStats {
self.stats
.lock()
.expect("lock should not be poisoned")
.clone()
}
pub async fn stop(&self) -> TorshResult<()> {
info!("Stopping communication scheduler");
if let Some(shutdown_tx) = self
.shutdown_tx
.lock()
.expect("lock should not be poisoned")
.take()
{
let _ = shutdown_tx.send(());
}
#[allow(clippy::await_holding_lock)]
let mut handles = self
.worker_handles
.lock()
.expect("lock should not be poisoned");
while let Some(handle) = handles.pop() {
let _ = handle.await;
}
info!("Communication scheduler stopped");
Ok(())
}
pub fn queue_size(&self) -> usize {
self.task_queue
.lock()
.expect("lock should not be poisoned")
.len()
}
pub fn get_available_bandwidth(&self) -> u64 {
self.bandwidth_monitor
.lock()
.expect("lock should not be poisoned")
.get_available_bandwidth()
}
pub fn update_bandwidth_limit(&self, new_limit: u64) {
self.bandwidth_monitor
.lock()
.expect("lock should not be poisoned")
.available_bandwidth = new_limit;
}
#[cfg(feature = "scirs2-simd")]
pub fn simd_compress_tensor(&self, tensor: &Tensor) -> TorshResult<Vec<u8>> {
if !self.config.enable_simd_optimization {
return self.standard_compress_tensor(tensor);
}
debug!(
"Performing SIMD-optimized tensor compression for {} elements",
tensor.numel()
);
const CLAMP_RANGE: f32 = 1.0e9;
const SCALE: f32 = 1.0;
let data: Vec<f32> = tensor.to_vec().map_err(|e| {
TorshDistributedError::communication_error(
"simd_compress_tensor",
format!("failed to read tensor data: {e}"),
)
})?;
if data.is_empty() {
return Ok(Vec::new());
}
let view = ArrayView1::from(&data[..]);
let clamped = <f32 as SimdUnifiedOps>::simd_clip(&view, -CLAMP_RANGE, CLAMP_RANGE);
let scaled = <f32 as SimdUnifiedOps>::simd_scalar_mul(&clamped.view(), SCALE);
let scaled_slice: &[f32] = scaled
.as_slice()
.expect("simd_scalar_mul output is always contiguous");
let chunk_size = self.config.simd_chunk_size.max(1);
let mut compressed = Vec::with_capacity(data.len() * std::mem::size_of::<f32>());
for chunk in scaled_slice.chunks(chunk_size) {
compressed.extend(self.apply_simd_compression(chunk));
}
debug!(
"SIMD compression produced {} bytes from {} elements",
compressed.len(),
data.len()
);
Ok(compressed)
}
#[cfg(feature = "scirs2-simd")]
pub fn simd_analyze_communication_patterns(&self) -> TorshResult<HashMap<String, f64>> {
if !self.config.enable_simd_optimization {
return Ok(HashMap::new());
}
debug!("Analyzing communication patterns using SIMD operations");
let mut patterns = HashMap::new();
let stats = self.get_stats();
let bandwidth_samples = self.get_bandwidth_history();
if bandwidth_samples.len() >= 4 {
let n = bandwidth_samples.len();
let bw_view = ArrayView1::from(&bandwidth_samples[..]);
let sum_bw = <f32 as SimdUnifiedOps>::simd_sum(&bw_view);
let mean_bandwidth = sum_bw as f64 / n as f64;
let mean_f32 = mean_bandwidth as f32;
let deviations: Vec<f32> = bandwidth_samples.iter().map(|&x| x - mean_f32).collect();
let dev_view = ArrayView1::from(&deviations[..]);
let dev_sq = <f32 as SimdUnifiedOps>::simd_mul(&dev_view, &dev_view);
let variance = <f32 as SimdUnifiedOps>::simd_sum(&dev_sq.view()) as f64 / n as f64;
patterns.insert("mean_bandwidth".to_string(), mean_bandwidth);
patterns.insert("bandwidth_variance".to_string(), variance);
patterns.insert(
"efficiency_ratio".to_string(),
stats.avg_bandwidth_utilization,
);
if let Ok(trend) = self.compute_simd_trend(&bandwidth_samples) {
patterns.insert("bandwidth_trend".to_string(), trend);
}
}
let task_durations = self.get_task_duration_history();
if task_durations.len() >= 4 {
let n = task_durations.len();
let td_view = ArrayView1::from(&task_durations[..]);
let sum_td = <f32 as SimdUnifiedOps>::simd_sum(&td_view);
let mean_duration = sum_td as f64 / n as f64;
let mean_f32 = mean_duration as f32;
let deviations: Vec<f32> = task_durations.iter().map(|&x| x - mean_f32).collect();
let dev_view = ArrayView1::from(&deviations[..]);
let dev_sq = <f32 as SimdUnifiedOps>::simd_mul(&dev_view, &dev_view);
let std_dev =
(<f32 as SimdUnifiedOps>::simd_sum(&dev_sq.view()) as f64 / n as f64).sqrt();
patterns.insert("avg_task_duration".to_string(), mean_duration);
patterns.insert("task_duration_std".to_string(), std_dev);
}
info!(
"Communication pattern analysis completed with {} metrics",
patterns.len()
);
Ok(patterns)
}
#[cfg(feature = "scirs2-simd")]
pub fn simd_optimize_scheduling(&self) -> TorshResult<()> {
if !self.config.enable_simd_optimization {
return Ok(());
}
debug!("Optimizing scheduling using SIMD-accelerated algorithms");
let task_queue = self.task_queue.lock().expect("lock should not be poisoned");
if task_queue.len() < 4 {
return Ok(()); }
let priorities: Vec<f32> = task_queue
.iter()
.map(|task| task.priority as u8 as f32)
.collect();
let estimated_times: Vec<f32> = task_queue
.iter()
.map(|task| task.estimated_time_ms as f32)
.collect();
drop(task_queue);
const TIME_EPS: f32 = 1.0e-9;
const TIME_MAX: f32 = f32::MAX / 2.0;
let times_view = ArrayView1::from(&estimated_times[..]);
let times_safe = <f32 as SimdUnifiedOps>::simd_clip(×_view, TIME_EPS, TIME_MAX);
let priorities_view = ArrayView1::from(&priorities[..]);
let _scheduling_scores =
<f32 as SimdUnifiedOps>::simd_div(&priorities_view, ×_safe.view());
info!("Scheduling optimization completed");
Ok(())
}
#[cfg(feature = "scirs2-simd")]
fn apply_simd_compression(&self, chunk: &[f32]) -> Vec<u8> {
chunk
.iter()
.flat_map(|&x| (x as u32).to_le_bytes())
.collect()
}
#[cfg(feature = "scirs2-simd")]
fn compute_simd_trend(&self, samples: &[f32]) -> TorshResult<f64> {
let n = samples.len();
if n < 2 {
return Ok(0.0);
}
let y_view = ArrayView1::from(samples);
let mean_y = <f32 as SimdUnifiedOps>::simd_sum(&y_view) / (n as f32);
let mean_x = (n as f32 - 1.0) * 0.5;
let dx: Vec<f32> = (0..n).map(|i| (i as f32) - mean_x).collect();
let dy: Vec<f32> = samples.iter().map(|&y| y - mean_y).collect();
let dx_view = ArrayView1::from(&dx[..]);
let dy_view = ArrayView1::from(&dy[..]);
let prod = <f32 as SimdUnifiedOps>::simd_mul(&dx_view, &dy_view);
let numerator = <f32 as SimdUnifiedOps>::simd_sum(&prod.view());
let sq = <f32 as SimdUnifiedOps>::simd_mul(&dx_view, &dx_view);
let denominator = <f32 as SimdUnifiedOps>::simd_sum(&sq.view());
if denominator.abs() < f32::EPSILON {
return Ok(0.0);
}
Ok((numerator / denominator) as f64)
}
#[cfg(feature = "scirs2-simd")]
fn compute_simd_scheduling_scores(
&self,
priorities: &[f32],
times: &[f32],
) -> TorshResult<Vec<f64>> {
if priorities.len() != times.len() {
return Err(TorshDistributedError::communication_error(
"compute_simd_scheduling_scores",
format!(
"length mismatch: priorities={} times={}",
priorities.len(),
times.len()
),
));
}
if priorities.is_empty() {
return Ok(Vec::new());
}
const TIME_EPS: f32 = 1.0e-9;
const TIME_MAX: f32 = f32::MAX / 2.0;
const EFFICIENCY_FACTOR: f32 = 1.0;
let times_view = ArrayView1::from(times);
let times_safe = <f32 as SimdUnifiedOps>::simd_clip(×_view, TIME_EPS, TIME_MAX);
let priorities_view = ArrayView1::from(priorities);
let ratio = <f32 as SimdUnifiedOps>::simd_div(&priorities_view, ×_safe.view());
let scored = <f32 as SimdUnifiedOps>::simd_scalar_mul(&ratio.view(), EFFICIENCY_FACTOR);
Ok(scored.iter().map(|&x| x as f64).collect())
}
#[cfg(feature = "scirs2-simd")]
fn get_bandwidth_history(&self) -> Vec<f32> {
vec![1000.0, 1100.0, 950.0, 1200.0, 1050.0, 1150.0, 980.0, 1300.0]
}
#[cfg(feature = "scirs2-simd")]
fn get_task_duration_history(&self) -> Vec<f32> {
vec![100.0, 150.0, 80.0, 200.0, 120.0, 90.0, 180.0, 110.0]
}
#[cfg(feature = "scirs2-simd")]
fn standard_compress_tensor(&self, tensor: &Tensor) -> TorshResult<Vec<u8>> {
debug!(
"Using standard tensor serialization for {} elements (SIMD disabled)",
tensor.numel()
);
serialize_tensor_le(tensor)
}
}
const TENSOR_WIRE_HEADER_LEN: usize = 10;
const TENSOR_WIRE_MAGIC: [u8; 4] = *b"TSHT";
const TENSOR_WIRE_VERSION: u8 = 1;
fn dtype_to_wire_tag(dtype: torsh_core::dtype::DType) -> u8 {
use torsh_core::dtype::DType;
match dtype {
DType::U8 => 0,
DType::I8 => 1,
DType::I16 => 2,
DType::I32 => 3,
DType::U32 => 4,
DType::I64 => 5,
DType::U64 => 6,
DType::F16 => 7,
DType::F32 => 8,
DType::F64 => 9,
DType::Bool => 10,
DType::BF16 => 11,
DType::C64 => 12,
DType::C128 => 13,
DType::QInt8 => 14,
DType::QUInt8 => 15,
DType::QInt32 => 16,
}
}
fn wire_tag_to_dtype(tag: u8) -> Option<torsh_core::dtype::DType> {
use torsh_core::dtype::DType;
Some(match tag {
0 => DType::U8,
1 => DType::I8,
2 => DType::I16,
3 => DType::I32,
4 => DType::U32,
5 => DType::I64,
6 => DType::U64,
7 => DType::F16,
8 => DType::F32,
9 => DType::F64,
10 => DType::Bool,
11 => DType::BF16,
12 => DType::C64,
13 => DType::C128,
14 => DType::QInt8,
15 => DType::QUInt8,
16 => DType::QInt32,
_ => return None,
})
}
pub fn serialize_tensor_le(tensor: &Tensor) -> TorshResult<Vec<u8>> {
let data = tensor.to_vec().map_err(|e| {
TorshDistributedError::serialization_error(format!(
"standard tensor serialization: failed to read tensor data: {e}"
))
})?;
let shape = tensor.shape();
let dims = shape.dims();
let dtype = tensor.dtype();
let element_size = dtype.size();
let mut out =
Vec::with_capacity(TENSOR_WIRE_HEADER_LEN + dims.len() * 8 + 8 + data.len() * element_size);
out.extend_from_slice(&TENSOR_WIRE_MAGIC);
out.push(TENSOR_WIRE_VERSION);
out.push(dtype_to_wire_tag(dtype));
out.extend_from_slice(&(dims.len() as u32).to_le_bytes());
for &dim in dims {
out.extend_from_slice(&(dim as u64).to_le_bytes());
}
out.extend_from_slice(&(data.len() as u64).to_le_bytes());
for &value in &data {
out.extend_from_slice(&value.to_le_bytes());
}
Ok(out)
}
pub fn deserialize_tensor_le(bytes: &[u8]) -> TorshResult<Tensor> {
if bytes.len() < TENSOR_WIRE_HEADER_LEN {
return Err(TorshDistributedError::serialization_error(format!(
"standard tensor deserialization: input too short ({} bytes, need at least {})",
bytes.len(),
TENSOR_WIRE_HEADER_LEN
)));
}
let magic = [bytes[0], bytes[1], bytes[2], bytes[3]];
if magic != TENSOR_WIRE_MAGIC {
return Err(TorshDistributedError::serialization_error(
"standard tensor deserialization: bad magic bytes (not a ToRSh tensor stream)",
));
}
let version = bytes[4];
if version != TENSOR_WIRE_VERSION {
return Err(TorshDistributedError::serialization_error(format!(
"standard tensor deserialization: unsupported wire version {version} (expected {TENSOR_WIRE_VERSION})"
)));
}
let dtype_tag = bytes[5];
let dtype = wire_tag_to_dtype(dtype_tag).ok_or_else(|| {
TorshDistributedError::serialization_error(format!(
"standard tensor deserialization: unknown dtype tag {dtype_tag}"
))
})?;
if dtype != torsh_core::dtype::DType::F32 {
return Err(TorshDistributedError::serialization_error(format!(
"standard tensor deserialization: dtype {dtype:?} unsupported (decoder produces f32 only)"
)));
}
let ndim = u32::from_le_bytes([bytes[6], bytes[7], bytes[8], bytes[9]]) as usize;
let dims_byte_len = ndim
.checked_mul(8)
.ok_or_else(|| TorshDistributedError::serialization_error("ndim overflow"))?;
let dims_start = TENSOR_WIRE_HEADER_LEN;
let dims_end = dims_start
.checked_add(dims_byte_len)
.ok_or_else(|| TorshDistributedError::serialization_error("header length overflow"))?;
let numel_end = dims_end
.checked_add(8)
.ok_or_else(|| TorshDistributedError::serialization_error("header length overflow"))?;
if bytes.len() < numel_end {
return Err(TorshDistributedError::serialization_error(format!(
"standard tensor deserialization: truncated header (have {} bytes, need {} for {}-D shape)",
bytes.len(),
numel_end,
ndim
)));
}
let mut dims = Vec::with_capacity(ndim);
let mut product: usize = 1;
for chunk in bytes[dims_start..dims_end].chunks_exact(8) {
let dim = u64::from_le_bytes([
chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
]) as usize;
product = product
.checked_mul(dim)
.ok_or_else(|| TorshDistributedError::serialization_error("numel product overflow"))?;
dims.push(dim);
}
let numel = u64::from_le_bytes([
bytes[dims_end],
bytes[dims_end + 1],
bytes[dims_end + 2],
bytes[dims_end + 3],
bytes[dims_end + 4],
bytes[dims_end + 5],
bytes[dims_end + 6],
bytes[dims_end + 7],
]) as usize;
if numel != product {
return Err(TorshDistributedError::serialization_error(format!(
"standard tensor deserialization: numel mismatch (header says {numel}, dims product is {product})"
)));
}
let element_size = dtype.size();
let data_byte_len = numel
.checked_mul(element_size)
.ok_or_else(|| TorshDistributedError::serialization_error("payload length overflow"))?;
let data_start = numel_end;
let data_end = data_start
.checked_add(data_byte_len)
.ok_or_else(|| TorshDistributedError::serialization_error("payload length overflow"))?;
if bytes.len() != data_end {
return Err(TorshDistributedError::serialization_error(format!(
"standard tensor deserialization: payload length mismatch (stream has {} bytes, expected {} = {}-byte header + {} payload)",
bytes.len(),
data_end,
data_start,
data_byte_len
)));
}
let mut values = Vec::with_capacity(numel);
for chunk in bytes[data_start..data_end].chunks_exact(element_size) {
values.push(f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
}
Tensor::from_data(values, dims, torsh_core::device::DeviceType::Cpu).map_err(|e| {
TorshDistributedError::serialization_error(format!(
"standard tensor deserialization: failed to reconstruct tensor: {e}"
))
})
}
pub mod utils {
use super::*;
pub fn create_high_throughput_scheduler() -> CommunicationScheduler {
let config = SchedulerConfig {
max_concurrent_ops: 8,
strategy: SchedulingStrategy::ShortestJobFirst,
enable_compression: true,
compression_threshold: 512 * 1024, ..Default::default()
};
CommunicationScheduler::new(config)
}
pub fn create_low_latency_scheduler() -> CommunicationScheduler {
let config = SchedulerConfig {
max_concurrent_ops: 2,
strategy: SchedulingStrategy::PriorityBased,
adaptive_scheduling: true,
timeout_ms: 5000,
..Default::default()
};
CommunicationScheduler::new(config)
}
pub fn create_bandwidth_aware_scheduler(bandwidth_limit: u64) -> CommunicationScheduler {
let config = SchedulerConfig {
bandwidth_limit_bps: bandwidth_limit,
strategy: SchedulingStrategy::Adaptive,
adaptive_scheduling: true,
enable_compression: true,
..Default::default()
};
CommunicationScheduler::new(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{init_process_group, BackendType};
#[test]
fn test_scheduler_config() {
let config = SchedulerConfig::default();
assert_eq!(config.max_concurrent_ops, 4);
assert_eq!(config.strategy, SchedulingStrategy::PriorityBased);
assert!(config.enable_priorities);
}
#[test]
fn test_priority_ordering() {
assert!(Priority::Critical > Priority::High);
assert!(Priority::High > Priority::Normal);
assert!(Priority::Normal > Priority::Low);
}
#[tokio::test]
async fn test_scheduler_creation() {
let config = SchedulerConfig::default();
let scheduler = CommunicationScheduler::new(config);
assert_eq!(scheduler.queue_size(), 0);
assert!(scheduler.get_available_bandwidth() > 0);
}
#[tokio::test]
async fn test_bandwidth_monitor() {
let mut monitor = BandwidthMonitor::new(1_000_000_000);
assert_eq!(monitor.get_available_bandwidth(), 1_000_000_000);
monitor.update_bandwidth(1024, Duration::from_millis(1));
assert!(monitor.get_available_bandwidth() > 0);
}
#[tokio::test]
async fn test_task_scheduling() -> TorshResult<()> {
let config = SchedulerConfig {
max_concurrent_ops: 1,
timeout_ms: 1000,
..Default::default()
};
let scheduler = CommunicationScheduler::new(config);
let process_group =
Arc::new(init_process_group(BackendType::Gloo, 0, 1, "127.0.0.1", 12345).await?);
let tensor = torsh_tensor::creation::ones(&[4, 4])?;
scheduler.start().await?;
let result = scheduler
.schedule_task(
CommunicationOp::AllReduce,
tensor.clone(),
process_group,
Priority::Normal,
)
.await;
assert!(result.is_ok());
scheduler.stop().await?;
Ok(())
}
#[test]
fn test_utils_schedulers() {
let high_throughput = utils::create_high_throughput_scheduler();
assert_eq!(high_throughput.config.max_concurrent_ops, 8);
let low_latency = utils::create_low_latency_scheduler();
assert_eq!(low_latency.config.max_concurrent_ops, 2);
let bandwidth_aware = utils::create_bandwidth_aware_scheduler(500_000_000);
assert_eq!(bandwidth_aware.config.bandwidth_limit_bps, 500_000_000);
}
#[tokio::test]
async fn test_scheduler_stats() -> TorshResult<()> {
let scheduler = CommunicationScheduler::new(SchedulerConfig::default());
let stats = scheduler.get_stats();
assert_eq!(stats.total_tasks, 0);
assert_eq!(stats.completed_tasks, 0);
assert_eq!(stats.current_queue_size, 0);
Ok(())
}
#[test]
fn test_standard_tensor_serialization_roundtrip() -> TorshResult<()> {
let values = vec![1.5_f32, -2.25, 3.75, 0.0, 100.5, -0.125];
let shape = vec![2_usize, 3];
let tensor = Tensor::from_data(
values.clone(),
shape.clone(),
torsh_core::device::DeviceType::Cpu,
)?;
let bytes = serialize_tensor_le(&tensor)?;
assert!(!bytes.is_empty(), "serialized tensor must not be empty");
let expected_len = TENSOR_WIRE_HEADER_LEN + shape.len() * 8 + 8 + values.len() * 4;
assert_eq!(
bytes.len(),
expected_len,
"serialized length must match the documented layout"
);
let magic = [bytes[0], bytes[1], bytes[2], bytes[3]];
assert_eq!(
magic, TENSOR_WIRE_MAGIC,
"stream must start with the TSHT magic"
);
let restored = deserialize_tensor_le(&bytes)?;
let restored_shape = restored.shape();
assert_eq!(
restored_shape.dims(),
shape.as_slice(),
"shape must round-trip exactly"
);
let restored_values = restored.to_vec()?;
assert_eq!(
restored_values.len(),
values.len(),
"element count mismatch"
);
for (idx, (&orig, &got)) in values.iter().zip(restored_values.iter()).enumerate() {
assert_eq!(
orig.to_bits(),
got.to_bits(),
"element {idx} mismatch: {orig} != {got}"
);
}
let zero_payload = vec![0u8; bytes.len()];
if let Ok(bogus) = deserialize_tensor_le(&zero_payload) {
assert_ne!(
bogus.to_vec()?,
values,
"all-zero bytes must never reproduce the original data"
);
}
assert!(
deserialize_tensor_le(&[]).is_err(),
"empty input must be rejected"
);
assert!(
deserialize_tensor_le(&bytes[..TENSOR_WIRE_HEADER_LEN - 1]).is_err(),
"short header must be rejected"
);
assert!(
deserialize_tensor_le(&bytes[..bytes.len() - 1]).is_err(),
"truncated payload must be rejected"
);
let mut bad_magic = bytes.clone();
bad_magic[0] ^= 0xFF;
assert!(
deserialize_tensor_le(&bad_magic).is_err(),
"corrupted magic must be rejected"
);
let mut trailing = bytes.clone();
trailing.push(0u8);
assert!(
deserialize_tensor_le(&trailing).is_err(),
"trailing garbage must be rejected"
);
Ok(())
}
}