Skip to content
HPC Cluster Orchestration

Multi-Node Slurm GPU Script Generator

Configure production-grade SBATCH launch scripts for PyTorch DDP, FSDP, DeepSpeed, and Megatron-LM. Eliminate rendezvous deadlocks, bind dataloader workers to local NUMA sockets, tune fabric-specific NCCL parameters, and trap preemption signals before walltime expiration.

SLURM Batch Script Generator

Configure high performance computing clusters, generate production ready SLURM submission scripts, and optimize multi-node distributed training jobs.

Cluster Configuration

2 nodes
distributed-training.sbatch
#!/bin/bash
#SBATCH --job-name=distributed-training
#SBATCH --partition=gpu
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=1
#SBATCH --gpus-per-node=8
#SBATCH --cpus-per-task=32
#SBATCH --time=04:00:00
#SBATCH --output=./logs/distributed-training_%j.out
#SBATCH --error=./logs/distributed-training_%j.err

set -e
echo "Starting job on $(hostname) at $(date)"

module load cuda/12.2
module load openmpi/4.1.5

export OMP_NUM_THREADS=32
export NCCL_DEBUG=INFO
export PYTHONUNBUFFERED=1

# Interconnect settings for infiniband-ndr
export NCCL_IB_HCA=mlx5_0:1,mlx5_1:1,mlx5_2:1,mlx5_3:1
export NCCL_IB_DISABLE=0
export NCCL_NET_GDR_LEVEL=5

nodes=( $(scontrol show hostnames $SLURM_JOB_NODELIST) )
head_node=${nodes[0]}
head_node_ip=$(scontrol show hostnames $SLURM_JOB_NODELIST | head -n 1 | xargs getent ahosts | awk '{print $1}' | head -n 1)

export LOGLEVEL=INFO

echo "Node list: ${nodes[*]}"
echo "Head node IP: $head_node_ip"

srun torchrun \
  --nproc_per_node=8 \
  --nnodes=2 \
  --node_rank=$SLURM_PROCID \
  --master_addr=$head_node_ip \
  --master_port=29500 \
  train.py --workload=pytorch-ddp --gpu=h100

Local VS Code Integration

Diagnose Slurm job timeouts and rank crashes locally in VS Code with the free Denpex extension (over 2,000 installs on Open VSX).

Try Denpex for 30 days. FREE.

No credit card. No automatic subscription. See whether Denpex helps with an incident you already understand.

Start your free 30-day Team evaluation

Up to 50 diagnoses a day for 30 days. Verify your work email to activate an eligible company evaluation, then replay a resolved incident and compare the evidence and next steps with your team's confirmed cause.

Use your company email (e.g. alex@yourcompany.com). Eligible company domains can activate a free 30-day Team evaluation (up to 50 diagnoses/day) after email and domain checks, regardless of mail host. Uncertain domains can request review from support@denpex.com. Free local diagnoses remain available without an account.

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 the B: prefix, Slurm signals active srun job steps directly or drops the signal if substeps are transitioning. Specifying B:USR1@180 guarantees delivery to the top-level bash wrapper shell.
  • Backgrounding srun with wait: Bash does not execute trap handlers while blocked on a foreground child process. Your script must launch srun ... & in the background and block on wait $! so the shell immediately catches SIGUSR1 and 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 / AttributeInfiniBand NDR / HDRRoCE v2 (Converged Ethernet)AWS EFA (Libfabric)
Transport LayerNative IB Verbs (mlx5)RDMA over UDP/IPv4 (mlx5)AWS OFI NCCL Plugin (SRD)
Critical Flag 1NCCL_IB_DISABLE=0NCCL_IB_GID_INDEX=3FI_PROVIDER=efa
Critical Flag 2NCCL_IB_HCA=mlx5NCCL_IB_TC=106FI_EFA_USE_DEVICE_RDMA=1
Congestion ControlCredit-based lossless link + adaptive routingPFC (802.1Qbb) + ECN (DCQCN)Multi-path Scalable Reliable Datagram (SRD)
Common MisconfigurationIncluding management HCA port in NCCL_IB_HCADefaulting to GID 0 (RoCE v1 MAC-based) causing switch dropsOmitting 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.

Pair your Slurm launch script with accurate memory sizing and automated telemetry triage before and during your training runs.

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_group because 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 as eth0 or docker0) instead of the high-speed fabric interface. Explicitly pinning NCCL_SOCKET_IFNAME and resolving MASTER_ADDR via scontrol show hostnames prevents these hangs.
How do I catch Slurm walltime timeouts in PyTorch to save a checkpoint before termination?
Add #SBATCH --signal=B:USR1@180 to your batch header. The B: prefix instructs Slurm to send SIGUSR1 directly to the batch shell 180 seconds before the job reaches its time limit. When your script launches srun in the background and waits on its PID, a bash trap forwards SIGUSR1 to the worker processes so rank 0 can flush an asynchronous checkpoint and exit cleanly before SIGKILL arrives.
Should I use srun or torchrun for multi-node distributed training on Slurm?
Use srun with --ntasks-per-node=1 to launch one torchrun daemon per node, or use srun directly with --ntasks-per-node equal to your GPU count when relying on SLURM_PROCID and SLURM_LOCALID environment mapping. Launching one torchrun agent per node via srun provides 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 requires FI_PROVIDER=efa and FI_EFA_USE_DEVICE_RDMA=1.