distributed data parallel ddp

**Distributed Data Parallel (DDP)** is **the PyTorch framework for synchronous multi-GPU and multi-node training where each process maintains a full model replica and processes a different data subset — automatically synchronizing gradients via all-reduce after backward pass, overlapping communication with computation through gradient bucketing, and achieving 85-95% scaling efficiency to hundreds of GPUs by minimizing synchronization overhead and maximizing hardware utilization through careful engineering of the training loop**. **DDP Architecture:** - **Process Group**: each GPU runs independent Python process; processes communicate via NCCL (GPU) or Gloo (CPU); torch.distributed.init_process_group(backend='nccl', init_method='env://', world_size=N, rank=i) - **Model Replication**: each process has full model copy; model = DDP(model, device_ids=[local_rank]); parameters synchronized at initialization; ensures all replicas start identically - **Data Partitioning**: DistributedSampler partitions dataset across processes; each process sees different data subset; sampler = DistributedSampler(dataset, num_replicas=world_size, rank=rank); ensures no data duplication - **Gradient Synchronization**: after backward(), DDP all-reduces gradients across processes; each process receives averaged gradient; optimizer.step() updates local model copy with synchronized gradients **Gradient Bucketing:** - **Bucket Formation**: DDP groups parameters into buckets (~25 MB each); parameters in same bucket all-reduced together; reduces communication overhead from N all-reduces (N parameters) to B all-reduces (B buckets) - **Reverse Order**: buckets formed in reverse parameter order; first bucket contains last layers; enables overlap of backward pass with all-reduce; as soon as bucket's gradients ready, all-reduce starts - **Overlap**: while backward pass computes gradients for layer i, all-reduce synchronizes gradients for layer i+1; achieves 50-80% overlap; reduces communication time from 20-30% to 5-15% of iteration time - **Bucket Size Tuning**: DDP(model, bucket_cap_mb=25); larger buckets → more overlap, higher latency; smaller buckets → less overlap, lower latency; 25 MB default optimal for most models **Communication Overlap:** - **Backward Hook**: DDP registers hooks on each parameter; hook fires when gradient ready; triggers all-reduce for parameter's bucket; enables asynchronous communication - **Computation-Communication Overlap**: GPU computes gradients for layer i while NCCL all-reduces gradients for layer i+1; both operations use different hardware resources (SMs vs copy engines); achieves true parallelism - **Synchronization Point**: optimizer.step() waits for all all-reduces to complete; ensures all gradients synchronized before weight update; maintains training correctness - **Efficiency**: well-overlapped DDP adds <10% overhead vs single-GPU; poorly overlapped (small model, slow network) adds 50-100% overhead **Initialization and Setup:** - **Environment Variables**: MASTER_ADDR, MASTER_PORT, WORLD_SIZE, RANK set by launcher (torchrun, mpirun); init_process_group() reads these; establishes communication - **Local Rank**: GPU index on current node; local_rank = int(os.environ['LOCAL_RANK']); used for device placement: model.to(local_rank) - **Torchrun**: torchrun --nproc_per_node=8 train.py; launches 8 processes on single node; handles environment variable setup; simplifies multi-GPU training - **Multi-Node**: torchrun --nnodes=4 --nproc_per_node=8 --master_addr=node0 --master_port=29500 train.py; launches 32 processes across 4 nodes; requires network connectivity **Gradient Accumulation with DDP:** - **No-Sync Context**: with model.no_sync(): loss.backward(); — disables gradient synchronization; gradients accumulate locally; use for all but last accumulation step - **Final Step**: loss.backward(); — without no_sync, triggers all-reduce; synchronizes accumulated gradients; optimizer.step() updates weights - **Implementation**: for i in range(accumulation_steps): with model.no_sync() if i < accumulation_steps-1 else nullcontext(): loss = model(data[i]); loss.backward(); optimizer.step() - **Efficiency**: reduces all-reduce frequency by K× (K=accumulation steps); reduces communication overhead; improves scaling efficiency for small models **Performance Optimization:** - **Batch Size**: larger per-GPU batch size improves GPU utilization; reduces communication-to-computation ratio; target >32 samples per GPU; use gradient accumulation if memory limited - **Model Size**: larger models have more computation per all-reduce; better overlap; small models (<100M parameters) have poor scaling; consider model parallelism instead - **Network Bandwidth**: NVLink (600 GB/s) enables near-perfect scaling; InfiniBand (200 Gb/s) enables 85-95% scaling; Ethernet (10-100 Gb/s) limits scaling to 50-80% - **Gradient Compression**: DDP supports FP16 gradient all-reduce; 2× bandwidth reduction; minimal accuracy impact; enable with autocast() **Comparison with DataParallel:** - **DataParallel (DP)**: single-process, multi-thread; GIL limits parallelism; broadcasts model every iteration; collects gradients on one GPU; 50-70% scaling efficiency; deprecated - **DDP**: multi-process; no GIL; model replicated once; gradients all-reduced; 85-95% scaling efficiency; recommended for all multi-GPU training - **Migration**: replace DataParallel(model) with DDP(model, device_ids=[local_rank]); add init_process_group() and DistributedSampler; 2-3× speedup on 8 GPUs **Debugging DDP:** - **Hang Detection**: TORCH_DISTRIBUTED_DEBUG=DETAIL enables verbose logging; identifies communication deadlocks; shows which rank is stuck - **Gradient Mismatch**: set_detect_anomaly(True) detects NaN/Inf; compare gradients across ranks; mismatch indicates non-deterministic operations (dropout without seed) - **Performance Profiling**: torch.profiler shows communication time; nsight systems visualizes overlap; identify communication bottlenecks - **Rank-Specific Logging**: if rank == 0: print(...); prevents duplicate logging; only master rank logs; reduces log clutter **Advanced Features:** - **Gradient as Bucket View**: DDP(model, gradient_as_bucket_view=True); gradients stored in contiguous bucket memory; reduces memory copies; 5-10% speedup - **Static Graph**: DDP(model, static_graph=True); assumes model graph doesn't change; enables optimizations; use for models without dynamic control flow - **Find Unused Parameters**: DDP(model, find_unused_parameters=True); handles models with conditional branches; adds overhead; only use when necessary (e.g., mixture of experts) - **Broadcast Buffers**: DDP(model, broadcast_buffers=True); synchronizes batch norm running statistics; ensures consistent inference across ranks **Scaling Efficiency:** - **Strong Scaling**: fixed total batch size, increase GPUs; efficiency = T₁/(N×Tₙ); DDP achieves 85-95% for large models; 50-70% for small models - **Weak Scaling**: batch size scales with GPUs; efficiency = T₁/Tₙ; DDP achieves 90-98%; near-linear scaling; preferred for training large models - **Bottlenecks**: small models → communication dominates; slow network → synchronization overhead; small batch size → poor GPU utilization Distributed Data Parallel is **the workhorse of multi-GPU training — by carefully engineering gradient synchronization, communication overlap, and efficient bucketing, DDP achieves 85-95% scaling efficiency with minimal code changes, making it the default choice for training models from ResNet-50 to GPT-3 and enabling researchers to leverage hundreds of GPUs for faster iteration and larger-scale experiments**.

Go deeper with CFSGPT

Get AI-powered deep-dives, save terms, and run advanced simulations — free account.

Create Free Account