Why Generic Slurm Scripts Fail at Multi-Node Scale
A boilerplate batch script copied from an introductory university cluster wiki works for a single GPU job, but it breaks down rapidly when scaling across 8 to 512 GPUs. Distributed training workloads fail in four repeatable ways when hardware topology and Slurm resource isolation are left at their defaults.
1. Missing NUMA CPU Affinity
Dual-socket H100 and A100 servers divide PCIe root complexes and NVLink switches across distinct NUMA domains. Without explicit --cpus-per-task and --cpu-bind=cores flags, the Linux scheduler migrates PyTorch DataLoader workers across the inter-socket UPI or Infinity Fabric link. Host-to-device cudaMemcpyAsync throughput drops by 35% to 50%, starving tensor cores during every step.
2. Forgotten --exclusive Node Allocation
When #SBATCH --exclusive is omitted on shared partitions, Slurm co-schedules smaller jobs onto residual CPU cores or unallocated GPUs on the same host. Co-located workloads contend for PCIe switch bandwidth, system RAM capacity, and NVMe scratch IOPS. In collective communication, the slowest single rank dictates the step time of the entire synchronous ring or tree.
3. Unconfigured NCCL Interface Variables
By default, NCCL probes all network interfaces to establish bootstrap sockets. If nodes expose virtual bridges (docker0, virbr0) or out-of-band BMC interfaces (usb0), ranks attempt to route handshakes across non-routable subnets. Leaving NCCL_SOCKET_IFNAME and NCCL_IB_HCA unpinned causes silent initialization stalls that expire only after the 30-minute default watchdog timeout.
4. Master Address Resolution Races
Hardcoding MASTER_ADDR=$(hostname) inside a child task causes every node in the allocation to form a separate single-node cluster with itself. Always resolve the deterministic head node in the parent batch script via scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1 and export it before invoking srun.
Preemption and Timeout Signal Handling with SIGUSR1
On high-utilization clusters, jobs are terminated abruptly either by reaching their --time wallclock limit or through preemption on spot and scavenging partitions. When Slurm kills a job with SIGTERM followed 30 seconds later by SIGKILL, distributed frameworks rarely have enough time to synchronize gradients and flush multi-gigabyte sharded checkpoints to Lustre or WEKA storage.
Adding #SBATCH --signal=B:USR1@180 instructs the Slurm controller to dispatch SIGUSR1 to the batch script 180 seconds before hard termination. Two implementation details are mandatory for this mechanism to work reliably:
- The
B:Batch Flag: Without theB:prefix, Slurm signals activesrunjob steps directly or drops the signal if substeps are transitioning. SpecifyingB:USR1@180guarantees delivery to the top-level bash wrapper shell. - Backgrounding
srunwithwait: Bash does not executetraphandlers while blocked on a foreground child process. Your script must launchsrun ... &in the background and block onwait $!so the shell immediately catchesSIGUSR1and forwards it to the Python training processes.
Python Signal Handler Pattern for PyTorch Distributed
import os
import signal
import torch
import torch.distributed as dist
preemption_requested = False
def handle_sigusr1(signum, frame):
global preemption_requested
preemption_requested = True
signal.signal(signal.SIGUSR1, handle_sigusr1)
# Inside your training step loop:
if preemption_requested:
# Synchronize decision across all ranks so no rank enters the next collective alone
flag_tensor = torch.tensor([1], dtype=torch.int32, device="cuda")
else:
flag_tensor = torch.tensor([0], dtype=torch.int32, device="cuda")
dist.all_reduce(flag_tensor, op=dist.ReduceOp.MAX)
if flag_tensor.item() == 1:
save_sharded_checkpoint(model, optimizer, step)
dist.barrier()
dist.destroy_process_group()
raise SystemExit(0)Always broadcast or all_reduce the signal flag across the process group before exiting. Because OS signal delivery across dozens of compute nodes occurs asynchronously over several milliseconds, acting on SIGUSR1 locally without cross-rank consensus causes early ranks to exit while trailing ranks enter the next forward pass and hang in an orphaned collective.
Interconnect-Specific NCCL Configuration: InfiniBand NDR vs RoCE v2 vs AWS EFA
Collective communication libraries require fundamentally different transport flags depending on the physical fabric connecting your GPU nodes. Applying InfiniBand verbs flags to an AWS EFA cluster or running RoCE v2 without explicit GID and traffic class pinning degrades AllReduce and AllGather bandwidth from 400 Gbps down to TCP fallback speeds.
| Parameter / Attribute | InfiniBand NDR / HDR | RoCE v2 (Converged Ethernet) | AWS EFA (Libfabric) |
|---|---|---|---|
| Transport Layer | Native IB Verbs (mlx5) | RDMA over UDP/IPv4 (mlx5) | AWS OFI NCCL Plugin (SRD) |
| Critical Flag 1 | NCCL_IB_DISABLE=0 | NCCL_IB_GID_INDEX=3 | FI_PROVIDER=efa |
| Critical Flag 2 | NCCL_IB_HCA=mlx5 | NCCL_IB_TC=106 | FI_EFA_USE_DEVICE_RDMA=1 |
| Congestion Control | Credit-based lossless link + adaptive routing | PFC (802.1Qbb) + ECN (DCQCN) | Multi-path Scalable Reliable Datagram (SRD) |
| Common Misconfiguration | Including management HCA port in NCCL_IB_HCA | Defaulting to GID 0 (RoCE v1 MAC-based) causing switch drops | Omitting NCCL_PROTO=simple on older OFI plugin versions |
InfiniBand NDR (400G)
Exclude storage or management ConnectX cards by specifying active compute HCAs explicitly (for example, NCCL_IB_HCA=mlx5_0,mlx5_1,mlx5_2,mlx5_3 or prefix exclusion with ^mlx5_bond_0). Enable NCCL_NET_GDR_LEVEL=5 when GPUs and HCAs share a dedicated PCIe switch for full GPUDirect RDMA peer memory reads.
RoCE v2 Lossless Fabrics
RoCE v2 encapsulates RDMA packets inside UDP headers. Always verify your cluster GID table with show_gids; NCCL_IB_GID_INDEX=3 selects IPv4 RoCE v2 on standard Mellanox OFED stacks. Match NCCL_IB_TC and NCCL_IB_SL to your switch QoS profile so traffic lands in the lossless Priority Flow Control queue.
AWS EFA (p4d / p5 Instances)
AWS Elastic Fabric Adapter uses the Scalable Reliable Datagram protocol rather than InfiniBand verbs. Export FI_PROVIDER=efa and FI_EFA_USE_DEVICE_RDMA=1 to engage GPUDirect RDMA over libfabric. Ensure aws-ofi-nccl is present in LD_LIBRARY_PATH inside your container or conda environment.
Related Cluster Sizing and Fault Diagnosis Resources
Pair your Slurm launch script with accurate memory sizing and automated telemetry triage before and during your training runs.
LLM VRAM & Distributed Memory Calculator
Calculate exact per-GPU memory requirements across ZeRO-1, ZeRO-2, ZeRO-3, FSDP, and Tensor Parallelism before choosing your node count and batch size in Slurm.
Post-Mortem TriageGPU & NCCL Log Analyzer
Paste your Slurm slurm-%j.err output to pinpoint the root rank behind NCCL watchdog timeouts, IB completion queue errors, and CUDA illegal memory accesses.
NVIDIA Xid Error Encyclopedia
Look up kernel NVRM: Xid fault codes including Xid 48 (double-bit ECC), Xid 79 (fallen off the bus), and Xid 119/120 (GSP RPC timeout) to decide when to drain and cordon a faulty Slurm node.
Denpex Extension on OpenVSX & VS Code
Inspect Slurm logs, decode Xid and NCCL tracebacks, and validate SBATCH directives directly inside Cursor, Windsurf, VSCodium, or remote SSH sessions.
Frequently Asked Questions
- Why do multi-node PyTorch jobs hang at initialization in Slurm?
- Multi-node PyTorch jobs most commonly hang during
torch.distributed.init_process_groupbecause of master address resolution races, firewall blocks on the ephemeral rendezvous port, or NCCL attempting to route RDMA traffic over a management Ethernet interface (such aseth0ordocker0) instead of the high-speed fabric interface. Explicitly pinningNCCL_SOCKET_IFNAMEand resolvingMASTER_ADDRviascontrol show hostnamesprevents these hangs. - How do I catch Slurm walltime timeouts in PyTorch to save a checkpoint before termination?
- Add
#SBATCH --signal=B:USR1@180to your batch header. TheB:prefix instructs Slurm to sendSIGUSR1directly to the batch shell 180 seconds before the job reaches its time limit. When your script launchessrunin the background and waits on its PID, a bash trap forwardsSIGUSR1to the worker processes so rank 0 can flush an asynchronous checkpoint and exit cleanly beforeSIGKILLarrives. - Should I use srun or torchrun for multi-node distributed training on Slurm?
- Use
srunwith--ntasks-per-node=1to launch onetorchrundaemon per node, or usesrundirectly with--ntasks-per-nodeequal to your GPU count when relying onSLURM_PROCIDandSLURM_LOCALIDenvironment mapping. Launching onetorchrunagent per node viasrunprovides elastic fault tolerance and deterministic local rank spawning while still respecting Slurm cgroup and NUMA allocations. - What is the difference between NCCL configuration on InfiniBand, RoCE v2, and AWS EFA?
- InfiniBand uses native verbs and benefits from Sharp collective offload and adaptive routing. RoCE v2 runs RDMA over converged Ethernet and requires explicit GID index selection (typically
NCCL_IB_GID_INDEX=3) plus DSCP traffic class marking (NCCL_IB_TC) so switches enforce Priority Flow Control. AWS EFA bypasses standard IB verbs entirely in favor of the libfabric OFI plugin and requiresFI_PROVIDER=efaandFI_EFA_USE_DEVICE_RDMA=1.