PyTorch

Distributed Tra in ing with DDP: Setup, Process Groups, Gradient Synchronization, and Scaling Laws

Training across multiple GPUs requires careful synchronization. Learn torch.nn.parallel.DistributedDataParallel, process groups, and how to scale training from 1 to N GPUs.

SS
Soham Sharma
AI Engineer, Botmartz · July 17, 2026 · 3 min read
Read Time
3 min
Failure Modes
5
Code Snippets
3
Runnable Notebook
1
Distributed Training with DDP: Setup, Process Groups, Gradient Synchronization, and Scaling Laws

Distributed Data Parallel (DDP) replicates the model across GPUs and synchronizes gradients. Each GPU processes a shard of the batch, computes gradients, and all-reduces (synchronizes) them before the weight update. Scaling is nearly linear: 2 GPUs → ~2× throughput, 8 GPUs → ~8× throughput. This post covers DDP setup, process groups, gradient synchronization, and common pitfalls.

Single-Machine, Multi-GPU Setup

DDP runs one process per GPU. Each process holds one replica of the model.

import torch

import torch.nn as nn import torch.distributed as dist from torch.nn.parallel import DistributedDataParallel as DDP

def setup_ddp(rank, world_size): """Setup process group.""" dist.init_process_group( backend='nccl', # NVIDIA Collective Communications Library rank=rank, world_size=world_size, init_method='tcp://localhost:29500' ) torch.cuda.set_device(rank)

def train_ddp(rank, world_size): setup_ddp(rank, world_size)

# Model on GPU model = nn.Linear(100, 10).cuda(rank) model = DDP(model, device_ids=[rank])

optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# DistributedSampler ensures no overlap between processes dataset = range(1000) sampler = torch.utils.data.DistributedSampler( dataset, num_replicas=world_size, rank=rank, shuffle=True ) loader = torch.utils.data.DataLoader(dataset, sampler=sampler, batch_size=64)

model.train() for epoch in range(2): for batch in loader: x = torch.randn(len(batch), 100, device=rank) y = torch.randint(0, 10, (len(batch),), device=rank)

optimizer.zero_grad() output = model(x) loss = nn.functional.cross_entropy(output, y) loss.backward()

# DDP automatically synchronizes gradients during backward optimizer.step()

dist.destroy_process_group()

# Launch with: torch.distributed.launch --nproc_per_node=2 script.py

Each process calls train_ddp with its rank (0, 1, ... , world_size-1). The DistributedSampler partitions data so each GPU gets a different shard. DDP's backward hook synchronizes gradients across all GPUs before the optimizer step.

Process Groups and All-Reduce

DDP uses all-reduce to synchronize gradients. All-reduce computes the sum (or other operation) across all processes and broadcasts back.

import torch

import torch.distributed as dist

# Assume distributed.init_process_group was called

# Create a tensor on each GPU rank = dist.get_rank() tensor = torch.tensor([rank * 10], dtype=torch.float32, device='cuda')

print(f"Rank {rank}, before all-reduce: {tensor.item()}")

# All-reduce: sum across all ranks dist.all_reduce(tensor, op=dist.ReduceOp.SUM)

print(f"Rank {rank}, after all-reduce: {tensor.item()}")

Output (rank 0):

Rank 0, before all-reduce: 0.0

Rank 0, after all-reduce: 30.0

Output (rank 1):

Rank 1, before all-reduce: 10.0

Rank 1, after all-reduce: 30.0

Output (rank 2):

Rank 2, before all-reduce: 20.0

Rank 2, after all-reduce: 30.0

After all-reduce, all ranks have the sum (0 + 10 + 20 = 30). DDP uses this to synchronize gradients: after all processes compute gradients on their batch, all-reduce sums them, then each process divides by world_size to get the mean gradient.

Scaling and Batch Size

With DDP, global batch size = batch_size_per_gpu × world_size. If you have 8 GPUs and batch_size_per_gpu=64, global batch_size=512. Larger batches require larger learning rates.

import torch

# Scaling rule: learning_rate ∝ batch_size base_lr = 0.01 base_batch_size = 32

world_size = 8 batch_size_per_gpu = 32 global_batch_size = batch_size_per_gpu * world_size

# Linear scaling rule scaled_lr = base_lr * (global_batch_size / base_batch_size)

print(f"Base LR: {base_lr} (batch_size={base_batch_size})") print(f"Scaled LR: {scaled_lr:.4f} (global_batch_size={global_batch_size})")

Output:

Base LR: 0.01 (batch_size=32)

Scaled LR: 0.08 (global_batch_size=256)

With 8 GPUs and batch_size_per_gpu=32, the global batch size is 256. The learning rate should increase proportionally (8× in this case).

Gotchas and Pitfalls

Gotcha 1: Forgetting to Use DistributedSampler

Without DistributedSampler, all GPUs process the same data, doubling training time and breaking results.

import torch

from torch.utils.data import DataLoader, DistributedSampler

dataset = range(1000)

# WRONG: no DistributedSampler loader_wrong = DataLoader(dataset, batch_size=64, shuffle=True)

# CORRECT: with DistributedSampler sampler = DistributedSampler(dataset, shuffle=True) loader_correct = DataLoader(dataset, batch_size=64, sampler=sampler)

print("DistributedSampler ensures each GPU gets unique data")

Fix: Always use DistributedSampler in DDP training.

Conclusion

DDP scales training across multiple GPUs by replicating the model and synchronizing gradients. Each GPU processes a shard of the batch, and all-reduce computes the mean gradient. Linear scaling laws apply: 8× GPUs ≈ 8× speedup (with appropriately scaled learning rate and batch size). Understanding DDP is essential for training large models efficiently. Next: we'll explore how to profile and bottleneck-detect to understand where your training time actually goes.

Closing Takeaways

Measure retrieval precision and recall in isolation before touching the model.
Chunk along document structure, not arbitrary character counts.
Combine vector and keyword search — hybrid retrieval beats either alone.
Treat evaluation as continuous infrastructure, not a launch-week report.
Try It Yourself
A runnable Google Colab notebook with the eval harness and hybrid search code from this post.
#PyTorch#Distributed Training#DDP#Scaling
0 views
SS
Soham Sharma
AI Engineer at Botmartz, building enterprise RAG and agent systems in production. Contributing to open-source libraries.

Discussion (0)

No approved comments yet. Be the first to share your thoughts!

Leave a Comment

Your email address will not be published. Required fields are marked *

More Engineering Insights
TensorFlow>-
Soham Sharma · 8 min read
GeneralPlaywright E2E Test Post
Integration Bot · 5 min read