Skip to content

Life of a Training Step#

One training step, followed through all nine layers. This is the fastest way to see why the layers have to be designed together: a single gradient touches every one of them, and a weak hop anywhere slows down every GPU in the job.

The setup#

Two HGX nodes, 8 GPUs each, 16 GPUs in total. The model is trained with FSDP (fully sharded data parallel) across all 16 GPUs. Each GPU has its own 400G RDMA NIC on a rail-optimized fabric. The control plane has already placed the pods and attached the GPUs and NICs (see from pod spec to data path); from here on it plays no part.

The step, hop by hop#

sequenceDiagram
    autonumber
    participant S as Storage (S)
    participant APP as Training loop (L1)
    participant FW as PyTorch FSDP (L2/L3)
    participant K as Kernels (L4)
    participant G as GPU + HBM (L9)
    participant N as NCCL (L5)
    participant NV as NVLink (L8)
    participant T as Verbs + GPUDirect (L6)
    participant F as Fabric (L7)
    participant P as Peer node GPUs

    S->>APP: Read next batch (data loader)
    APP->>FW: forward(batch)
    FW->>N: All-gather this layer's weight shards
    N->>NV: Intra-node part over NVLink
    N->>T: Inter-node part as RDMA writes
    T->>F: NIC reads GPU memory directly (GPUDirect RDMA)
    F->>P: Packets cross the fabric to the peer NIC
    FW->>K: Launch matmul and attention kernels
    K->>G: Compute on tensor cores, read and write HBM
    APP->>FW: loss.backward()
    FW->>K: Gradient kernels
    FW->>N: Reduce-scatter gradients
    N->>NV: Intra-node reduce over NVLink
    N->>T: Inter-node reduce over RDMA
    T->>F: GPU memory to fabric, no CPU copy
    F->>P: Peers receive and sum their shard
    APP->>FW: optimizer.step()
    FW->>G: Update the local shard of weights
    APP->>S: Every N steps, write a checkpoint

Steps 3–7 and 12–16 repeat for every layer of the model, and FSDP overlaps them with compute: while layer k is computing, the weights for layer k+1 are already being gathered. When the overlap works, communication is hidden. When it doesn't, GPUs wait.

What each layer contributes#

Layer Role in this step If it is weak
S Storage Feeds the next batch; absorbs checkpoints GPUs idle at the start of each step, or for minutes at every checkpoint
L1 Application The training loop: batch, forward, backward, step Small batches leave too little compute to hide communication behind
L2/L3 Framework and parallelism FSDP decides what is gathered and reduced, and when Wrong sharding or no prefetch turns overlapped communication into blocking waits
L4 Kernels Turn each operation into GPU work Unfused or low-precision-unaware kernels waste tensor-core time
L5 NCCL Runs the all-gather and reduce-scatter collectives A poor algorithm or wrong NIC choice cuts bandwidth several-fold
L6 Transport RDMA verbs with GPUDirect: NIC reads GPU memory directly Without GPUDirect, data bounces through host memory; without RDMA, NCCL falls back to TCP
L7 Fabric Carries inter-node traffic Congestion, pause storms or one bad link slow the whole ring
L8 Interconnect NVLink for intra-node traffic; PCIe from GPU to NIC A GPU paired with a NIC on the other CPU socket crosses the inter-socket link on every byte
L9 GPU and HBM Does the math, holds weights, gradients and activations Out of memory forces smaller batches or more sharding, which means more communication

The lesson#

Every GPU waits for the slowest participant in each collective. A single mis-paired NIC on one node, a single flapping link, or a single slow storage mount sets the speed of all 16 GPUs. That is why this site treats the stack as one system and why co-design is the method rather than tuning each layer alone.

When a step is slower than expected, the slow collectives decision tree walks the same path from the bottom up.