Hitesh Sahu
Hitesh SahuHitesh Sahu
  1. Home
  2. ›
  3. posts
  4. ›
  5. …

  6. ›
  7. 4 3 Multi Node Training

Loading ⏳
Fetching content, this won’t take long…


💡 Did you know?

🍌 Bananas are berries, but strawberries are not.

🍪 This website uses cookies

No personal data is stored on our servers however third party tools Google Analytics cookies to measure traffic and improve your website experience. Learn more

Loading ⏳
Fetching content, this won’t take long…


💡 Did you know?

🐙 Octopuses have three hearts and blue blood.
kubernetes

    AI-AgenticAI

    AI-DeepLearning

    AI-GenAI

    AI-Infrastructure

    AI-Machine-Learning

    AI-Math

    AWS

    Azure

    Hobbies

    kubernetes
    • Kubernetes and Cloud Native Certification Path

    • Kubernetes: Control Loops, Scheduling, and GPUs

    • Kubernetes API Server Internals

    • etcd Architecture Explained

    • Kubernetes Scheduler Internals

    • Kubernetes Informers & Controllers Explained

    • Kubernetes Networking: Pods, Services, Ingress, and CNI

    • Kubernetes Storage: PV, PVC, StorageClass, and CSI

    • Helm: Kubernetes Package Manager

    • Cloud Native Observability: Prometheus, Grafana, OpenTelemetry, and Tracing

    • GPU Scheduling in Kubernetes: Device Plugins, GPU Operator & MIG

    • NVIDIA Network Operator: InfiniBand, SR-IOV, RDMA, and Multus

    • Dynamic Resource Allocation: The Future of GPU Scheduling in Kubernetes

    • Kubernetes Performance at Scale

    • Optimizing AI Inference at Scale: The Full Stack

    • Kueue: Kubernetes-Native Job Queuing and Quota Management

    • Multi-Node Distributed Training on Kubernetes

    • Kubernetes Topology Manager: NUMA-Aware GPU Scheduling

    • NVIDIA NIM: Optimized Inference Microservices on Kubernetes

    • GPU Autoscaling on Kubernetes: KEDA, HPA, and Cluster Autoscaler

    • kubernetes Index


    Management

    Programming

    Terraform

    Z_Appendix

    0-root

Cover Image for Multi-Node Distributed Training on Kubernetes
kubernetes

Multi-Node Distributed Training on Kubernetes

How Kubernetes orchestrates distributed AI training across multiple DGX nodes — Kubeflow Training Operator, PyTorchJob, gang scheduling, NCCL, AllReduce, intra-node NVLink vs inter-node InfiniBand, and fault-tolerant checkpointing.

Kubernetes
Distributed Training
DGX
PyTorch
Kubeflow
NCCL
← Previous

Kueue: Kubernetes-Native Job Queuing and Quota Management

Next →

Kubernetes Topology Manager: NUMA-Aware GPU Scheduling

Multi-Node Distributed Training on Kubernetes

A single DGX H100 node has 8 H100 GPUs and 640 GB of HBM3 memory.

That sounds like a lot.

But a Llama 3 405B model requires roughly 800 GB just to hold the weights in BF16.

It doesn't fit.

Even if it did fit, training on 8 GPUs alone would take months.

The solution is to spread the job across multiple nodes.

flowchart LR

Model["Large Model 🔢"]

-->Node1["DGX Node 1\n8x H100"]

Model

-->Node2["DGX Node 2\n8x H100"]

Model

-->Node3["DGX Node 3\n8x H100"]

Model

-->Node4["DGX Node 4\n8x H100"]

This is multi-node distributed training.


Why Vanilla Kubernetes Is Not Enough

Kubernetes knows how to schedule Pods.

But it doesn't know how to run a distributed training job.

The problem has three parts.


Problem 1: Pods Start Independently

Normally, Kubernetes starts Pods one at a time as nodes become available.

flowchart TD

PodA["Pod A ✅ — starts immediately"]

PodB["Pod B ✅ — starts when node is ready"]

PodC["Pod C ⏳ — waits for resources"]

PodD["Pod D ⏳ — waits"]

Training jobs cannot work this way.

All workers must start at the same time.

If worker 0 starts and waits for worker 3 which never comes, the job hangs forever.


Problem 2: Workers Need to Find Each Other

Each worker needs to know

  • How many total workers exist
  • What is its own rank (worker index)
  • Where is the master (rank 0)?

Kubernetes doesn't set up this coordination automatically.


Problem 3: No Retry Logic for Job Failures

If one Pod crashes, Kubernetes restarts it — but with no awareness that the entire training job needs to restart or resume from a checkpoint.


The Solution: Kubeflow Training Operator

The Kubeflow Training Operator is a Kubernetes Operator that adds distributed training primitives to Kubernetes.

flowchart LR

User["kubectl apply PyTorchJob"]

-->TrainingOperator["Training Operator"]

TrainingOperator

-->MasterPod["Master Pod\nrank=0"]

TrainingOperator

-->Worker1["Worker Pod\nrank=1"]

TrainingOperator

-->Worker2["Worker Pod\nrank=2"]

TrainingOperator

-->Worker3["Worker Pod\nrank=3"]

It introduces custom resource types:

Resource Framework
PyTorchJob PyTorch DDP, FSDP, torchrun
TFJob TensorFlow distributed
MPIJob MPI-based training (Horovod)
PaddleJob PaddlePaddle
JAXJob JAX distributed

For DGX workloads, PyTorchJob is the most common.


PyTorchJob Anatomy

A PyTorchJob defines two roles.

apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
  name: llama-training
spec:
  pytorchReplicaSpecs:
    Master:
      replicas: 1
      template:
        spec:
          containers:
          - name: trainer
            image: nvcr.io/nvidia/pytorch:24.01-py3
            resources:
              limits:
                nvidia.com/gpu: 8
    Worker:
      replicas: 7
      template:
        spec:
          containers:
          - name: trainer
            image: nvcr.io/nvidia/pytorch:24.01-py3
            resources:
              limits:
                nvidia.com/gpu: 8

This creates

1 Master Pod  × 8 GPUs  =  8 GPUs
7 Worker Pods × 8 GPUs  = 56 GPUs
                          --------
                          64 GPUs total (8 DGX nodes)

The operator automatically injects environment variables into every Pod:

Variable Value
MASTER_ADDR Master Pod's IP address
MASTER_PORT 23456
WORLD_SIZE 64 (total GPUs)
RANK 0–63 (this GPU's global index)
LOCAL_RANK 0–7 (this GPU's index within the node)

The training script reads these and initializes the process group:

import torch.distributed as dist

dist.init_process_group(backend="nccl")
# Every process now knows its rank, world_size, and master address

Gang Scheduling

The biggest operational challenge with multi-node training is resource fragmentation.

Suppose a cluster has 16 DGX nodes and you need 8 for training.

Two jobs are waiting:

  • Job A: needs 8 nodes
  • Job B: needs 8 nodes

The cluster has 6 nodes free.

Without gang scheduling:

flowchart TD

JobA["Job A (needs 8 nodes)"]

-->6Nodes["Gets 6 nodes\n⏳ waiting for 2 more"]

JobB["Job B (needs 8 nodes)"]

-->Waiting["Gets 0 nodes\n⏳ stuck behind Job A"]

Job A takes 6 nodes and waits.

Job B gets nothing.

Both are stuck.


With gang scheduling (Kueue + Kubernetes):

flowchart TD

JobA["Job A (needs 8)"]

-->Hold["Held in queue"]

JobB["Job B (needs 8)"]

-->Hold

Hold

-->Check["8 nodes free?"]

Check

-->|"Yes"| AllStart["All 8 pods start together"]

Check

-->|"No"| Wait["Wait — don't start partial job"]

The rule is simple:

All pods start together, or none start.

This prevents deadlocks and wasted GPU-hours on partial jobs.

Kueue enforces this through its admission control — a job either gets all requested resources or waits in the queue.


How Workers Communicate

Once all pods are running, they need to exchange gradients constantly.

The communication pattern depends on the parallelism strategy.


AllReduce — Data Parallelism

In Data Parallel training (PyTorch DDP):

  • Each GPU holds a complete copy of the model
  • Each GPU processes a different batch of data
  • After each forward+backward pass, gradients are averaged across all GPUs
flowchart LR

GPU0["GPU 0\ngrad: 0.3"]

GPU1["GPU 1\ngrad: 0.5"]

GPU2["GPU 2\ngrad: 0.4"]

GPU3["GPU 3\ngrad: 0.2"]

GPU0 & GPU1 & GPU2 & GPU3

-->AllReduce["AllReduce\navg = 0.35"]

AllReduce

-->GPU0 & GPU1 & GPU2 & GPU3

Every GPU ends up with the same averaged gradient.

Every GPU applies the same weight update.

All copies of the model stay synchronized.


Send/Receive — Model Parallelism

When the model doesn't fit on one GPU, layers are split across GPUs.

flowchart LR

GPU0["GPU 0\nLayers 1–8"]

-->GPU1["GPU 1\nLayers 9–16"]

-->GPU2["GPU 2\nLayers 17–24"]

-->GPU3["GPU 3\nLayers 25–32"]

Each GPU sends its output to the next GPU (pipeline parallelism).

This requires point-to-point sends, not AllReduce.


NCCL

All GPU communication in PyTorch is handled by NCCL (NVIDIA Collective Communications Library).

NCCL chooses the fastest available transport automatically:

flowchart TD

NCCL["NCCL"]

-->IntraNode["Intra-node\nSame DGX node"]

-->NVLink["NVLink / NVSwitch\n900 GB/s"]

NCCL

-->InterNode["Inter-node\nDifferent DGX nodes"]

-->InfiniBand["InfiniBand NDR\n400 Gb/s per link"]

Intra-Node: NVLink and NVSwitch

Within a single DGX H100 node, all 8 GPUs are connected via NVSwitch.

flowchart TB

GPU0 & GPU1 & GPU2 & GPU3

-->NVSwitch["NVSwitch\n900 GB/s bisection bandwidth"]

GPU4 & GPU5 & GPU6 & GPU7

-->NVSwitch

Any GPU can communicate with any other GPU at full bandwidth simultaneously.

NVSwitch is the reason DGX nodes can run tensor-parallel training across 8 GPUs efficiently.

A single NVLink connection delivers 900 GB/s total bisection bandwidth.

For comparison, a PCIe Gen5 x16 slot delivers ~64 GB/s — about 14× slower.


Inter-Node: InfiniBand

Between DGX nodes, communication travels over InfiniBand (or RoCE for Ethernet-based clusters).

flowchart LR

DGX1["DGX Node 1\n8x H100"]

-->IB["InfiniBand NDR\n400 Gb/s"]

DGX2["DGX Node 2\n8x H100"]

-->IB

DGX3["DGX Node 3\n8x H100"]

-->IB

IB

-->Switch["InfiniBand Switch"]

Each DGX H100 node has 8 InfiniBand NDR ports — one per GPU.

GPUDirect RDMA allows GPUs to read and write from remote GPUs without involving the CPU:

sequenceDiagram

participant GPU0 as GPU 0 (Node 1)
participant RDMA as RDMA NIC
participant Network as InfiniBand
participant GPU4 as GPU 4 (Node 2)

GPU0->>RDMA: DMA transfer (CPU not involved)
RDMA->>Network: Send packet
Network->>GPU4: Direct write to GPU memory

Without GPUDirect RDMA:

GPU → CPU memory → NIC → Network → CPU memory → GPU

With GPUDirect RDMA:

GPU → NIC → Network → NIC → GPU

CPU is bypassed entirely — latency drops by 3–5×.


Parallelism Strategies

Large model training combines multiple parallelism types simultaneously.

Strategy What is split Communication Example
Data Parallel (DDP) Data batches AllReduce (gradients) 8-GPU single-node
Tensor Parallel (TP) Individual layers AllReduce within layer Across 8 GPUs via NVLink
Pipeline Parallel (PP) Groups of layers Send/Recv between stages Across nodes
ZeRO (FSDP) Optimizer state + weights AllGather + ReduceScatter Scales to 1000s of GPUs
Expert Parallel (EP) MoE expert layers AlltoAll Mixtral, Grok

3D Parallelism

DGX SuperPOD deployments often combine TP + PP + DP together.

flowchart TB

subgraph Node1["DGX Node 1"]
GPU0["GPU 0"]
GPU1["GPU 1"]
GPU2["GPU 2"]
GPU3["GPU 3"]
end

subgraph Node2["DGX Node 2"]
GPU4["GPU 4"]
GPU5["GPU 5"]
GPU6["GPU 6"]
GPU7["GPU 7"]
end

GPU0 & GPU1 & GPU2 & GPU3

<-->|"Tensor Parallel\n(NVLink)"| GPU0

Node1

<-->|"Pipeline Parallel\n(InfiniBand)"| Node2
  • Tensor Parallel across 4 GPUs within a node (uses NVLink — very fast)
  • Pipeline Parallel across nodes (uses InfiniBand — still fast enough)
  • Data Parallel across replicas of the above (uses AllReduce over InfiniBand)

The key principle: put the most communication-intensive parallelism on the fastest link.


Fault Tolerance and Checkpointing

A training job running for 2 weeks across 64 GPUs has a high probability of hitting at least one failure.

Without fault tolerance:

2 weeks of training → GPU fails on day 13 → restart from scratch

With checkpointing:

Every N steps → save model weights + optimizer state → restart from last checkpoint

What Gets Checkpointed

flowchart LR

Checkpoint["Checkpoint"]

-->Weights["Model Weights"]

Checkpoint

-->OptimizerState["Optimizer State\n(Adam momentum, variance)"]

Checkpoint

-->Step["Current Step"]

Checkpoint

-->RNG["RNG State\n(for reproducibility)"]

The optimizer state is often 3× the size of the weights (Adam stores first and second moments + weights).

A 70B parameter model checkpoint in BF16:

Weights:         140 GB
Optimizer state: 420 GB
Total:           560 GB per checkpoint

Checkpoint Storage

Checkpoints cannot be stored on a single node's local disk — that node might be the one that fails.

They need a shared distributed filesystem:

flowchart LR

GPU0 & GPU1 & GPU2 & GPU3

-->SharedFS["Shared Storage\n(Lustre / WEKA / NFS)"]

GPU4 & GPU5 & GPU6 & GPU7

-->SharedFS

Each GPU writes its shard of the model to shared storage simultaneously.


Elastic Training (torchrun)

PyTorch Elastic (torchrun) allows a job to continue even if a node fails, by dynamically adjusting the world size.

flowchart TD

NormalRun["8 nodes running\nworld_size=64"]

-->NodeFails["Node 3 fails"]

NodeFails

-->Elastic["torchrun detects failure\nre-initializes with 7 nodes\nworld_size=56"]

Elastic

-->Resume["Training resumes\nfrom last checkpoint"]

Without elastic training, one failed node kills the entire job.

With elastic training, the job continues at reduced scale.


Complete Training Flow

Putting it all together:

flowchart TB

User["kubectl apply PyTorchJob"]

-->Operator["Training Operator\ncreates Pods"]

Operator

-->Kueue["Kueue\nadmission check"]

Kueue

-->|"All nodes available"| GangStart["Gang schedule\nall pods start together"]

GangStart

-->Init["torchrun initializes\nprocess group via NCCL"]

Init

-->Training["Training loop\nforward + backward"]

Training

-->|"Intra-node gradient sync"| NVLink["NVLink / NVSwitch\n900 GB/s"]

Training

-->|"Inter-node gradient sync"| IB["InfiniBand NDR\n400 Gb/s + GPUDirect RDMA"]

NVLink & IB

-->AllReduce["AllReduce\ngradient averaging"]

AllReduce

-->WeightUpdate["Weight update\nnext step"]

WeightUpdate

-->|"Every N steps"| Checkpoint["Checkpoint\nshared storage"]

WeightUpdate

-->Training

Key Takeaways

Component Role
Kubeflow Training Operator Manages PyTorchJob lifecycle — creates pods, injects env vars, handles failures
Gang Scheduling (Kueue) Ensures all pods start together — prevents deadlocks and wasted GPU-hours
NCCL Handles all GPU communication — automatically uses NVLink or InfiniBand
NVLink / NVSwitch 900 GB/s intra-node bandwidth — enables tensor parallelism within a DGX node
InfiniBand + GPUDirect RDMA 400 Gb/s inter-node bandwidth — CPU bypassed, direct GPU-to-GPU memory transfers
Checkpointing Saves model + optimizer state to shared storage — enables restart after failure
torchrun / Elastic Adjusts world size dynamically — job survives node failures without full restart

Multi-node training on Kubernetes is not just about scheduling more Pods. It requires coordinated startup, high-speed communication at every layer of the network, and fault tolerance that spans the entire training run.

Hitesh Sahu
Written by Hitesh Sahu, a passionate developer and blogger.

Tue Jul 07 2026

Share This on

← Previous

Kueue: Kubernetes-Native Job Queuing and Quota Management

Next →

Kubernetes Topology Manager: NUMA-Aware GPU Scheduling

kubernetes/4-3-Multi-Node-Training
Let's work together
+49 176-2019-2523
hiteshkrsahu@gmail.com
WhatsApp
Skype
Munich 🥨, Germany 🇩🇪, EU
Playstore
Hitesh Sahu's apps on Google Play Store
Need Help?
Let's Connect
Navigation
  Home/About
  Skills
  Work/Projects
  Lab/Experiments
  Contribution
  Awards
  Art/Sketches
  Thoughts
  Contact
Links
  Sitemap
  Legal Notice
  Privacy Policy

Made with

NextJS logo

NextJS by

hitesh Sahu

| © 2026 All rights reserved.