Skip to main content
Source

This page is generated from skills/eks-genai/references/distributed-training.md. Edit the source, not this page.

Distributed Training on EKS

Opinionated guidance for running distributed training and fine-tuning workloads on Amazon EKS. Default → Ray Train + KubeRay for orchestration, PyTorch FSDP for parallelism, FSx for Lustre for storage, EFA + NCCL for multi-node communication.

Framework Decision

FrameworkDefault for…When to recommendWhen NOT to recommend
Ray Train + KubeRayAll distributed training on EKSMulti-node training orchestration; heterogeneous clusters; fault-tolerant (Spot); built-in checkpoint/resume; integrates with Ray Serve for train→serve handoffTeam has zero Ray experience + simple single-node fine-tune (use plain PyTorch)
PyTorch DDPSingle-node multi-GPUData-parallel training across GPUs on one node; simplest distributed API; no external dependenciesMulti-node (FSDP is strictly better for large models); model doesn't fit in single-GPU memory
PyTorch FSDPMulti-node large-model trainingModel exceeds single-GPU memory; full-shard across GPUs/nodes; native PyTorch (no extra framework); works with Ray Train as the launcherSingle-GPU fine-tune (overkill); team needs pipeline parallelism (use DeepSpeed or Megatron)
DeepSpeed (ZeRO)Memory-constrained large-modelZeRO-3 offload to CPU/NVMe; 3D parallelism (DP + TP + PP); optimizer state shardingFSDP handles the use case (simpler); Neuron (DeepSpeed Neuron support is limited)
Kubeflow Training OperatorMLOps-governed trainingMulti-team training clusters with job tracking, experiment management, hyperparameter tuningInference-only; single-team with Ray already in place

Rule of thumb: Ray Train wraps PyTorch DDP/FSDP — you get PyTorch idioms with Ray's fault tolerance, elastic scaling, and checkpoint management on top. Default to Ray Train unless the team explicitly prefers bare PyTorch + a Job manifest.

EFA + NCCL Multi-Node Communication

Multi-node distributed training requires Elastic Fabric Adapter (EFA) for high-bandwidth, low-latency inter-node communication. Without EFA, NCCL all-reduce collectives fall back to TCP — bandwidth drops from 400-3200 Gbps to ~25 Gbps.

EFA Setup on EKS

# 1. Install EFA device plugin (DaemonSet)
# Exposes vpc.amazonaws.com/efa resource to the scheduler
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: aws-efa-k8s-device-plugin
namespace: kube-system
spec:
selector:
matchLabels:
name: aws-efa-k8s-device-plugin
template:
spec:
nodeSelector:
node.kubernetes.io/instance-type: p5.48xlarge # EFA-capable instances
containers:
- name: efa-plugin
image: 602401143452.dkr.ecr.us-east-1.amazonaws.com/eks/aws-efa-k8s-device-plugin:latest

NUMA Pinning + Static CPU Manager (non-negotiable)

EFA bandwidth halves without proper NUMA topology configuration. Set on every GPU/Neuron training node:

# kubelet configuration (via EKS node config or launch template)
cpuManagerPolicy: static
topologyManagerPolicy: single-numa-node
reservedSystemCPUs: "0-3"

Training pods must request whole CPUs (not fractional) for the static CPU manager to assign NUMA-local cores.

NCCL Environment Variables

# Container environment for multi-node training
NCCL_DEBUG=INFO # set to WARN in production
NCCL_SOCKET_IFNAME=eth0
FI_PROVIDER=efa # force libfabric to use EFA
FI_EFA_USE_DEVICE_RDMA=1 # enable RDMA on EFA
NCCL_PROTO=simple # recommended for EFA

Reference: EFA with EKS User Guide; EKS AI/ML Networking Best Practices.

Checkpoint / Resume Loop

Rule: Never run distributed training on Spot without checkpoint/resume. Without it, every Spot interruption restarts training from epoch 0 — guaranteed cost-burn.

Architecture: FSx for Lustre + S3 Data Repository Association

Training Pod → checkpoint to FSx for Lustre (sub-ms write, same-AZ)
FSx for Lustre → S3 Data Repository Association (async durable offload)
S3 → durable checkpoint store (survives FSx deletion / AZ failure)

On resume:
S3 → FSx for Lustre (lazy-load via DRA) → Training Pod resumes from latest checkpoint

Checkpoint Frequency

ScenarioCheckpoint intervalRationale
Spot trainingEvery 15-30 minSpot interruption gives 2-min warning; 15 min max lost work
On-Demand long trainingEvery 60 minBalance I/O overhead vs recovery time
Capacity Blocks (guaranteed)Every 2-4 hoursInterruption risk is zero; checkpoint for fault tolerance only

FSx for Lustre Configuration for Checkpointing

apiVersion: fsx.services.k8s.aws/v1alpha1
kind: FileSystem
metadata:
name: training-lustre
spec:
fileSystemType: LUSTRE
storageCapacity: 4800 # GiB; minimum 1200 for Persistent-SSD
lustreConfiguration:
deploymentType: PERSISTENT_2
perUnitStorageThroughput: 500 # MB/s per TiB
dataRepositoryAssociations:
- dataRepositoryPath: s3://my-bucket/checkpoints/
fileSystemPath: /checkpoints
s3:
autoExportPolicy:
events: ["NEW", "CHANGED", "DELETED"]
autoImportPolicy:
events: ["NEW", "CHANGED", "DELETED"]
subnetIds:
- subnet-same-az-as-gpu # CRITICAL: same AZ as compute nodes

Critical rule: FSx in the same AZ as GPU/Neuron nodes — cross-AZ latency dwarfs FSx's native microsecond-class performance. Pre-warm FSx with training data via an administrative pod before launching Spot capacity.

Reference: Architecting scalable checkpoint storage for large-scale ML training on AWS; EKS AI/ML Storage Best Practices.

Gang Scheduling for Multi-Tenant Training

When multiple teams share a training cluster, standard Kubernetes scheduling can cause deadlocks — Job A gets 4 of 8 needed GPUs while Job B gets the other 4, and neither can start. Gang scheduling guarantees all-or-nothing pod placement.

Volcano

apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
name: distributed-training
spec:
minAvailable: 8 # all 8 pods must schedule together
schedulerName: volcano
queue: training-queue
plugins:
sla: ["--max-wait=30m"] # release resources if gang can't form in 30 min
tasks:
- replicas: 8
name: worker
template:
spec:
containers:
- name: trainer
resources:
limits:
nvidia.com/gpu: "8"
vpc.amazonaws.com/efa: "4"

Kueue (Kubernetes-native)

Kueue is the Kubernetes SIG-Scheduling project for job queueing — lighter-weight than Volcano, integrates with Karpenter for just-in-time node provisioning:

apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
name: training-queue
spec:
clusterQueue: gpu-cluster-queue
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
name: gpu-cluster-queue
spec:
resourceGroups:
- coveredResources: ["nvidia.com/gpu"]
flavors:
- name: p5-spot
resources:
- name: nvidia.com/gpu
nominalQuota: 64

Decision rule: Use Volcano for complex priority/preemption + gang scheduling in large multi-tenant training clusters. Use Kueue for Kubernetes-native queueing with Karpenter integration on smaller/mid-size clusters. Inference-only clusters don't need either — Karpenter handles scheduling.

Spot Training Rule

ConditionSpot acceptable?Why
Checkpoint/resume wired + FSx + S3 DRA✅ YesMax 15-30 min lost work on interruption
No checkpoint logic❌ NeverEvery interruption = restart from scratch = cost-burn
Latency-sensitive eval loops (need uninterrupted hours)⚠️ ConditionallyCapacity Blocks preferred; Spot only if budget-constrained
Dev / experimentation✅ YesAcceptable interruption profile for iterative work

Use Capacity Blocks for ML for planned multi-day training runs that need guaranteed capacity without Spot interruption risk. Pricing is substantially below on-demand for multi-day reservations.

Reference: EC2 Capacity Blocks for ML Pricing.

Train → Eval → Register Handoff

After training completes, the model must be evaluated and registered before serving. The canonical pipeline on EKS:

Ray Train (training)
→ checkpoint to FSx/S3
→ Argo Workflows triggers eval job
→ eval job loads checkpoint, runs validation set
→ if metric threshold passed → register model
→ MLflow Model Registry (version + metadata + lineage)
→ OR: push to ECR as serving image (bake-in pattern)
→ OR: upload to S3 model bucket (lazy-load pattern)
→ Argo Workflows triggers serving rollout
→ update vLLM Deployment / RayService with new model version
→ canary rollout via LiteLLM weighted routing

MLflow on EKS

MLflow runs as a Deployment on the CPU NodePool — tracks experiments, stores model artifacts (S3 backend), manages model versions. Deploy via Helm:

helm install mlflow community-charts/mlflow \
--set backendStore.postgres.enabled=true \
--set artifactRoot=s3://my-mlflow-artifacts/

Argo Workflows

Argo Workflows (the "A" in JARK) orchestrates the DAG: train → eval → register → deploy. Each step is a Kubernetes pod — training steps request GPU resources, eval/register steps run on CPU.

Reference: awslabs/ai-on-eks infra/jark-stack/terraform; Deploy Generative AI Models on Amazon EKS (JARK stack).

Ray Train Configuration Example

import ray
from ray.train.torch import TorchTrainer
from ray.train import ScalingConfig, CheckpointConfig, RunConfig

trainer = TorchTrainer(
train_func,
scaling_config=ScalingConfig(
num_workers=8,
use_gpu=True,
resources_per_worker={"GPU": 1, "CPU": 8},
),
run_config=RunConfig(
checkpoint_config=CheckpointConfig(
num_to_keep=3, # keep last 3 checkpoints
checkpoint_frequency=10, # every 10 training steps
),
storage_path="s3://my-bucket/ray-checkpoints/",
),
)
result = trainer.fit()

Ray Train handles:

  • Worker placement across nodes (respects gang scheduling)
  • Automatic checkpoint to S3 (or FSx mount)
  • Fault-tolerant resume on Spot interruption (workers re-spawn, load latest checkpoint)
  • Integration with Ray Tune for hyperparameter search

Karpenter Integration for Training

Set do-not-disrupt: "true" on training pods to prevent Karpenter consolidation from interrupting multi-hour training runs:

metadata:
annotations:
karpenter.sh/do-not-disrupt: "true" # protect from consolidation
spec:
nodeSelector:
karpenter.sh/nodepool: gpu-training
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule

Sources