Session 23¶
Topic 3: Neural networks and PyTorch¶
- introduction to PyTorch
- automatic differentiation
- basic neural network models and training
- optimization hyperparameters
- distributed training with data parallelism π
- distributed training on multiple compute nodes
Today¶
Distributed training with data parallelism¶
- basics of distributed training
- distributed training with PyTorch DDP
- hands-on: converting single-GPU code to multi-GPU
Distributed Training with PyTorch DDP¶
From learning β scaling¶
so far:
- tensors β¦ store data
- autograd β¦ automatic differentiation
- models β¦ parameterized functions
- training β¦ how the model learns
- optimization paramβs β¦ control training
β now we ask: how to scale to larger datasets
Why do we need distributed training?¶
training is only getting more expensive:
- bigger datasets and
- larger models β¦
- lead to longer training times β³
goal: accelerate training by using more compute:
- one GPU
- several GPUs
- multiple compute nodes with several GPUs each
Parallelize the training dataset¶
we already know this sequence in the training loop:
β can we run this on multiple GPUs simultaneously?
Idea: data parallelism¶
Setup: copy the same model to all GPUs.
To train, split the data across GPUs:
- each GPU gets one mini-batch (data shard)
- each GPU computes its gradient locally (backprop.)
- then all GPUs synchronize their gradients
- each GPU updates model parameters using the same gradient
β all models stay consistent
What is PyTorch DDP?¶
Distributed Data Parallel (DDP) is PyTorchβs solution for multi-GPU training / multi-process training.
Example:
2-node Γ 2-GPU-per-node setup β world size = 4
DDP execution model¶
for each GPU:
- separate Python process
- separate data shard
- identical model copy
across GPUs:
- gradients are averaged across processes (i.e., GPUs)
- same parameter updates for each process β models stay identical
Workflow in PyTorch code¶
we:
- initialize distributed processes
- split dataset across GPUs
- run training loop (independently)
PyTorch handles most of the hard parts like:
- synchronize gradients automatically
Step 1: launch one process per GPU¶
torch.multiprocessing.spawn(
worker_fn, # our own training function
nprocs=args.num_gpus,
args=(args,) # extra arguments for worker_fn()
)
- each process automatically gets a local rank
- a local rank is mapped to a specific GPU
Step 2: initialize process group¶
now, inside worker_fn()
setup required for communication across GPUs:
def worker_fn(local_rank, args):
global_rank = args.node_id * args.num_gpus + local_rank
dist.init_process_group(
backend="nccl", # use NVIDIA Collective Communication Library (NCCL)
world_size=WORLD_SIZE,
rank=global_rank,
device_id=local_rank,
)
atexit.register(dist.destroy_process_group)
world_sizeβ total number of processesrankβ unique ID per process
Step 3: assign GPU to process¶
assign each process a different GPU:
def worker_fn(local_rank, args):
...
device = torch.device(f"cuda:{local_rank}")
Step 4: distribute the data¶
train_sampler = DistributedSampler(
train_set,
num_replicas=WORLD_SIZE,
rank=global_rank
)
num_replicasβ total number of processesrankβ unique ID per process
β each process sees different data; avoids duplication
Step 5: wrap model with DDP¶
first, ensure all models start with the same weights
torch.manual_seed(base_seed)
model = WideResNet(num_classes)
...
model = nn.parallel.DistributedDataParallel(
model,
device_ids=[local_rank]
)
then, wrap the model with DDP for automatic gradient synchronization
Step 6: core training loop (unchanged!)¶
outputs = model(inputs)
loss = loss_fn(outputs, labels)
loss.backward()
optimizer.step()
β¨ DDP automatically:
- averages gradients across GPUs
- keeps models in sync
Subtle but important details¶
- call
train_sampler.set_epoch(epoch)at each epoch β reshuffles data each epoch - use
dist.barrier()β synchronizes processes - reduce metrics across GPUs, for example,
# sum the image/sec over all workers (result on main rank only)
torch.distributed.reduce(images_per_sec, MAIN_RANK)
# average validation accuracy across workers (result on all ranks)
torch.distributed.all_reduce(v_accuracy, op=dist.ReduceOp.AVG)
Demo: Fashion-MNIST with DDP¶
code/pytorch/fashion_mnist_ddp
we will:
- start with a singe-GPU code
- write the code for distributed training
- train a CNN on Fashion-MNIST
Hands-on: what to experiment with¶
vary:
- number of GPUs (
num_gpus) - batch size (
batch_size) - learning rate (
lr)
β¦ and observe scaling behavior
questions to explore:
- does training get faster?
- does accuracy change?
Key takeaways¶
- DDP = multi-process + synchronized gradients
- each GPU runs its own training loop
- PyTorch handles communication
Looking ahead¶
- training neural nets: from a single machine β data center