use bytes::Bytes;
use ipfrs_core::Cid;
use ipfrs_transport::{
AggregationStrategy, GradientAggregator, GradientMessage, Priority, Session, SessionConfig,
TensorMetadata, TensorStream,
};
use multihash::Multihash;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use tokio::runtime::Runtime;
fn create_cid(seed: u64) -> Cid {
let data = seed.to_le_bytes();
let hash = Multihash::wrap(0x12, &data).unwrap();
Cid::new_v1(0x55, hash)
}
fn create_gradient_data(_layer_id: &str, worker_id: u8, size: usize) -> Vec<u8> {
let mut data = vec![0u8; size];
for (i, byte) in data.iter_mut().enumerate() {
*byte = ((i as u64 + worker_id as u64) % 256) as u8;
}
data
}
struct Worker {
id: u8,
local_data_size: usize,
}
impl Worker {
fn new(id: u8, local_data_size: usize) -> Self {
Worker {
id,
local_data_size,
}
}
fn train_local_epoch(&mut self, epoch: u32) -> Vec<GradientMessage> {
println!(
" Worker {} training epoch {} with {} samples",
self.id, epoch, self.local_data_size
);
let layer_names = ["layer1", "layer2", "layer3", "output"];
let mut gradients = Vec::new();
for (idx, layer_name) in layer_names.iter().enumerate() {
let data = create_gradient_data(layer_name, self.id, 1024 * (idx + 1));
let message = GradientMessage::new(
layer_name.to_string(),
data,
vec![32, 32 * (idx + 1)], "float32".to_string(),
)
.with_metadata("layer".to_string(), layer_name.to_string())
.with_metadata("epoch".to_string(), epoch.to_string())
.with_metadata("worker".to_string(), self.id.to_string())
.with_metadata("samples".to_string(), self.local_data_size.to_string());
gradients.push(message);
}
gradients
}
}
struct ParameterServer {
aggregator: GradientAggregator,
aggregation_strategy: AggregationStrategy,
runtime: Runtime,
}
impl ParameterServer {
fn new(total_workers: usize, strategy: AggregationStrategy) -> Self {
let aggregator = GradientAggregator::new(strategy, total_workers);
let runtime = Runtime::new().unwrap();
ParameterServer {
aggregator,
aggregation_strategy: strategy,
runtime,
}
}
fn receive_gradients(
&mut self,
worker_id: u8,
gradients: Vec<GradientMessage>,
) -> Result<(), String> {
println!(
" Parameter server receiving {} gradients from worker {}",
gradients.len(),
worker_id
);
for gradient in gradients {
self.runtime
.block_on(self.aggregator.add_gradient(gradient))
.map_err(|e| format!("Failed to add gradient: {:?}", e))?;
}
Ok(())
}
fn is_layer_ready(&self, layer_name: &str) -> bool {
self.runtime.block_on(self.aggregator.is_ready(layer_name))
}
fn aggregate_layer(&mut self, layer_name: &str) -> Result<Option<GradientMessage>, String> {
self.runtime
.block_on(self.aggregator.aggregate(layer_name))
.map(Some)
.map_err(|e| format!("Failed to aggregate: {:?}", e))
}
fn stats(&self) -> String {
let stats = self.runtime.block_on(self.aggregator.stats());
format!(
"Layers tracked: {}, Total gradients: {}, Expected contributors: {}",
stats.layers_count, stats.total_gradients, stats.expected_contributors
)
}
}
struct TrainingCoordinator {
workers: Vec<Worker>,
parameter_server: ParameterServer,
session: Session,
}
impl TrainingCoordinator {
fn new(num_workers: usize, aggregation_strategy: AggregationStrategy) -> Self {
let mut workers = Vec::new();
for i in 0..num_workers {
let data_size = 1000 + i * 500; workers.push(Worker::new(i as u8, data_size));
}
let parameter_server = ParameterServer::new(num_workers, aggregation_strategy);
let session_config = SessionConfig {
timeout: Duration::from_secs(600), default_priority: Priority::High,
max_concurrent_blocks: 1000,
progress_notifications: true,
};
let session = Session::new(1, session_config, None);
TrainingCoordinator {
workers,
parameter_server,
session,
}
}
fn run_epoch(&mut self, epoch: u32) -> Result<(), String> {
println!("\n=== Epoch {} ===", epoch);
let epoch_start = Instant::now();
println!("\nPhase 1: Local Training");
let mut all_gradients = HashMap::new();
for worker in &mut self.workers {
let gradients = worker.train_local_epoch(epoch);
all_gradients.insert(worker.id, gradients);
}
println!("\nPhase 2: Gradient Exchange");
for (worker_id, gradients) in all_gradients {
self.parameter_server
.receive_gradients(worker_id, gradients)?;
}
println!("\nPhase 3: Gradient Aggregation");
let layer_names = vec!["layer1", "layer2", "layer3", "output"];
let mut aggregated_gradients = Vec::new();
for layer_name in &layer_names {
if self.parameter_server.is_layer_ready(layer_name) {
if let Some(aggregated) = self.parameter_server.aggregate_layer(layer_name)? {
println!(" Aggregated gradients for {}", layer_name);
println!(
" Shape: {:?}, Size: {} bytes",
aggregated.shape,
aggregated.data.len()
);
aggregated_gradients.push(aggregated);
}
}
}
println!("\nPhase 4: Parameter Broadcast");
println!(
" Broadcasting {} updated parameters to {} workers",
aggregated_gradients.len(),
self.workers.len()
);
let blocks_transferred = aggregated_gradients.len() * self.workers.len();
let cid = create_cid(epoch as u64);
let data = Bytes::from(vec![0u8; 1024]);
for _ in 0..blocks_transferred {
let _ = self.session.mark_received(&cid, &data);
}
let epoch_duration = epoch_start.elapsed();
println!("\nEpoch {} completed in {:?}", epoch, epoch_duration);
println!(
" Parameter server stats: {}",
self.parameter_server.stats()
);
let stats = self.session.stats();
if stats.total_blocks > 0 {
println!(
" Session progress: {:.1}% ({}/{})",
stats.progress(),
stats.blocks_received,
stats.total_blocks
);
}
Ok(())
}
fn run_training(&mut self, num_epochs: u32) -> Result<(), String> {
println!("=== Distributed Training Started ===");
println!("Workers: {}", self.workers.len());
println!(
"Aggregation strategy: {:?}",
self.parameter_server.aggregation_strategy
);
let training_start = Instant::now();
for epoch in 1..=num_epochs {
self.run_epoch(epoch)?;
}
let total_duration = training_start.elapsed();
println!("\n=== Training Completed ===");
println!("Total epochs: {}", num_epochs);
println!("Total time: {:?}", total_duration);
println!("Average time per epoch: {:?}", total_duration / num_epochs);
let session_stats = self.session.stats();
println!("\nFinal Session Statistics:");
println!(" Total blocks: {}", session_stats.total_blocks);
println!(" Received blocks: {}", session_stats.blocks_received);
println!(" Total bytes: {}", session_stats.bytes_transferred);
println!(" Progress: {:.1}%", session_stats.progress());
Ok(())
}
}
fn main() {
println!("=== Distributed Training Example - Federated Learning ===\n");
println!("--- Scenario 1: FederatedAvg (3 workers) ---");
let mut coordinator = TrainingCoordinator::new(3, AggregationStrategy::FederatedAvg);
if let Err(e) = coordinator.run_training(3) {
eprintln!("Training failed: {}", e);
}
println!("\n\n--- Scenario 2: WeightedAverage (5 workers) ---");
let mut coordinator2 = TrainingCoordinator::new(5, AggregationStrategy::WeightedAverage);
if let Err(e) = coordinator2.run_training(2) {
eprintln!("Training failed: {}", e);
}
println!("\n\n--- Scenario 3: Large Model Tensor Streaming ---");
demonstrate_tensor_streaming();
println!("\n=== Example Completed ===");
}
fn demonstrate_tensor_streaming() {
println!("Simulating large model parameter streaming...");
let root_cid = create_cid(9999);
let chunk_cids: Vec<Cid> = (0..10).map(|i| create_cid(1000 + i)).collect();
let metadata = TensorMetadata::new(root_cid)
.with_shape(vec![4096, 4096]) .with_dtype("float32".to_string())
.with_size(4096 * 4096 * 4) .with_chunks(chunk_cids.clone())
.with_priority_hint(1000) .with_deadline(Instant::now() + Duration::from_secs(30));
let stream = TensorStream::new(metadata);
println!(" Tensor shape: {:?}", stream.metadata.shape);
if let Some(size) = stream.metadata.size_bytes {
println!(" Total size: {} MB", size / 1024 / 1024);
}
println!(" Chunks: {}", chunk_cids.len());
println!(" Initial progress: {:.1}%", stream.progress() * 100.0);
println!(" Stream initialized and ready to receive chunks");
println!(" (In production, use async mark_received() to track chunk reception)")
}