Slurm topology-aware placement¶
Scope: making Slurm pack a tightly-coupled job onto the fewest, closest network leaves so its collectives stay rail-local. Covers topology.conf with the topology/tree and topology/block plugins, --switches=<count>@<time>, and why topology-unaware placement starves all-reduce. Slurm basics (partitions, GRES, the srun torchrun launch) live in Slurm for GPU Clusters and are not repeated here.
Reference templates on real APIs; pin versions and validate before production use. Config and commands below are reference templates, not hardware-tested.
What it is¶
Without a topology plugin, "Slurm's native mode of resource selection considers nodes as a one-dimensional array" 1: it fills the partition by node index and ignores the fabric. Topology-aware placement teaches the scheduler the shape of the network so it can allocate the nodes of a job under as few switches (or within as few blocks) as possible, minimising the cross-spine hops every collective pays for.
Two plugins matter for GPU clusters, selected by TopologyPlugin in slurm.conf:
topology/treemodels a hierarchical (fat-tree) network and allocates "resources to jobs on a hierarchical network to minimize network contention" using a best-fit on leaf switches 1. The fabric is declared intopology.confas leaf switches (aSwitchNameplus itsNodes) and higher-level switches (aSwitchNameplus its childSwitches) 2.topology/blockallocates "resources to jobs within a strictly enforced, hierarchical block structure" and "prioritizes the placement of jobs to minimize fragmentation across the cluster, as opposed to the tree topology, which focuses on fitting jobs on the first available resources" 1. It starts from contiguous base blocks (bblocks) that aggregate into larger blocks at the sizes named byBlockSizes2. On NVLink-domain hardware a block is typically one rack: CoreWeave maps "each GB200 or GB300 NVL72-based system" to "a Block containing 18 Nodes in the same NVLink domain" 4.
The block hierarchy is the natural fit for rail/NVLink-domain clusters because its enforced boundaries match physical scale-up domains; the tree plugin is the long-standing general fat-tree model. Pick one per cluster (or per partition, see below); they are configured with different topology.conf parameters.
Why it's needed (and when)¶
A synchronous data/model-parallel step is a barrier: the all-reduce finishes only when the slowest rank-to-rank path finishes. Scatter the same eight nodes across two spines and every iteration's gradient sync now traverses the spine instead of a single leaf, so the slowest path, and therefore step time, is set by the worst placement, not the best. Topology-unaware placement does exactly this: it packs by node index, so a job that could fit under one leaf gets spread across the fabric whenever those index-adjacent nodes happen to sit on different switches. The collective is then bottlenecked on oversubscribed inter-switch links, GPUs sit busy-but-idle waiting on the barrier, and MFU drops: the fabric, not compute, gates the job (HPC Networking Fabric, Performance Optimization and Tuning).
Use topology-aware placement when:
- The job is tightly coupled: synchronous training, large all-reduce/all-gather, anything where one slow link stalls the world-size (Distributed Training Platform).
- The fabric is blocking or rail-aligned above the leaf/rack, i.e. inter-switch bandwidth is less than the sum of node bandwidth, so spanning switches actually costs throughput.
It matters less for embarrassingly-parallel work (independent single-node array jobs, batch inference) where ranks do not communicate; there, packing by switch only helps adjacency, not correctness. The scheduler trade-off (fewer switches vs. faster start) is exposed to the user by --switches (below). For the Slurm-vs-Kubernetes placement comparison see Slurm vs Kubernetes for GPUs; the scheduler internals stay in Slurm for GPU Clusters / Kubernetes for GPU Clusters.
How it's set up & managed¶
Tree plugin¶
Set the plugin in slurm.conf, then describe the fabric in topology.conf. Leaf stanzas list Nodes; aggregation stanzas list child Switches 2. Slurm's hostlist parser means names need not be consecutive, e.g. Nodes=tux[0-3,12,18-20] and Switches=s[0-2,4-8,12] are valid 2.
# topology.conf (topology/tree)
# Three leaf switches, each feeding one rack of GPU nodes, joined by a spine.
SwitchName=leaf0 Nodes=gpu[001-016]
SwitchName=leaf1 Nodes=gpu[017-032]
SwitchName=leaf2 Nodes=gpu[033-048]
SwitchName=spine Switches=leaf[0-2]
With this map, a --nodes=16 job that fits under one leaf is placed there; the best-fit only crosses the spine when a single leaf cannot satisfy the request 1. The optional LinkSpeed field lets you weight links of differing performance 2.
Block plugin¶
The block plugin enforces boundaries instead of merely preferring them. Declare base blocks and the enforceable BlockSizes. Each block must contain at least the planning base-block count of nodes, and "successive BlockSizes must be a power of two larger than the prior values" 2.
# topology.conf (topology/block)
# 4 base blocks of 32 nodes; planning base block = 32, next enforced level = 128.
BlockName=block1 Nodes=gpu[001-032]
BlockName=block2 Nodes=gpu[033-064]
BlockName=block3 Nodes=gpu[065-096]
BlockName=block4 Nodes=gpu[097-128]
BlockSizes=32,128
Allocation is: find the smallest BlockSizes level that satisfies the request, pick a subset of lower-level blocks inside that aggregating block, then best-fit onto the underlying base blocks 1. Size the planning base block to the physical scale-up domain (one rack / NVLink domain) so "Nodes from the same job [land] within the same rack whenever possible, which maximizes NVLink fabric performance" 4.
Per-partition topology¶
A cluster can run more than one topology: "Each partition can be configured to use a specific topology by specifying the Topology in its partition configuration line" 1. This lets a rail-aligned training partition use topology/block while a general partition uses topology/tree (verify the partition-line Topology= syntax against slurm.conf for your version).
Applying changes¶
topology.conf is read at controller start and on reconfigure. After editing, reload and confirm Slurm parsed the fabric (this reflects config, not a hardware probe; keep the file in sync with the real cabling):
scontrol reconfigure
scontrol show topology # tree: switch tree; block: configured blocks
scontrol show topology leaf0 # inspect one switch/block
Validated usage & tests¶
Request fewer switches and bound the wait. --switches=count[@max-time-to-wait] "defines the maximum count of leaf switches desired for the job allocation"; if Slurm "finds an allocation containing more switches than the count specified, the job remains pending until it either finds an allocation with desired switch count or the time limit expires" 3. The job does not start sooner on more switches; it waits, then starts anyway when the timer lapses. Accepted time formats include minutes, minutes:seconds, hours:minutes:seconds, days-hours, days-hours:minutes and days-hours:minutes:seconds 3. The cap is administrator-limited via SchedulerParameters max_switch_wait 31.
# Confine an 8-node job to a single leaf switch; wait up to 30 minutes for it,
# otherwise start on whatever the scheduler can give.
sbatch --nodes=8 --switches=1@30:00 train.sbatch
Expected: while a one-switch allocation is unavailable, squeue shows the job PENDING; scontrol show job <id> reports a reason consistent with waiting on the optimal switch count. Once a single-leaf set frees up (or 30 minutes elapse) the job starts. Do not assume a specific reason string or timing; read what your version prints.
Confirm where a running job actually landed and that its node set sits under one leaf / in one block:
scontrol show job <id> | grep -E "NodeList|SwitchCount|Switches"
scontrol show hostnames "$SLURM_JOB_NODELIST" # the allocated nodes
scontrol show topology | grep -A2 leaf0 # cross-check against the map
On the block plugin, request a block-local shape explicitly. --segment=N sets the segment size; CoreWeave's guidance is to "default to --segment=1" so "your job [is] more flexible because it can run on any available Nodes across all Blocks", and --exclusive=topo makes "only the job being submitted [run] in a Block" 4 (both are block-plugin features; verify availability for your Slurm version).
# 16-node job, prefer to stay inside one block, exclusive within the block.
srun --nodes=16 --segment=1 --exclusive=topo --gres=gpu:8 ./train_mpi
The application-level proof is a collective benchmark: run nccl-tests all_reduce_perf across the job's nodes and compare bus bandwidth for a single-leaf/single-block placement against a deliberately spread one. The single-domain placement should show higher, more stable busbw; the spread placement should show lower busbw from cross-spine hops. Record your own numbers; do not assume a figure (HPC Networking Fabric, Slurm for GPU Clusters).
Failure modes¶
- No
topology.conf/ wrong plugin: placement is index-based; tightly-coupled jobs scatter across spines and every all-reduce eats extra hops. Symptom: high step time and low MFU with healthy GPUs. Triage in Topology-Unaware Scheduling Starvation. topology.confdoes not match the cabling: the map is config, not a probe; a stale file makes the scheduler "pack" onto switches that are not actually adjacent, so the optimisation is silently wrong. Re-derive the map from the live fabric andscontrol reconfigure(HPC Networking Fabric).--switchestoo strict: an unsatisfiable count with a long@time(or none) parks the jobPENDINGwaiting for a one-switch hole that never opens; loosen the count, shorten the wait, or rely onmax_switch_waitto cap it 31.- Job larger than one block/leaf: on the block plugin a job that exceeds a block is split into equal segments across blocks ("Segments cannot span Blocks"), which can strand capacity and reintroduce cross-block collective cost 4; size jobs to the block, or accept the split knowingly.
BlockSizesnot power-of-two-stacked / base block mis-sized: the controller rejects or mis-plans the hierarchy; keep successive sizes a power of two larger and the base block equal to the physical scale-up domain 24.
A persistent placement stall or a confirmed topology mis-map escalates to Topology-Unaware Scheduling Starvation. Pair topology gating with health gating so a job is never placed onto a marked-bad node (GPU Health Gating).
References¶
- Slurm Topology Guide (plugins, best-fit,
--switches,max_switch_wait, per-partition topology): https://slurm.schedmd.com/topology.html topology.conf(5) —SwitchName/Switches/Nodes/LinkSpeed,BlockName/BlockSizes, hostlist ranges: https://slurm.schedmd.com/topology.conf.htmlsbatch(1) —--switches=count[@max-time-to-wait]semantics and time formats: https://slurm.schedmd.com/sbatch.htmlslurm.conf(5) —TopologyPlugin,TopologyParam, partitionTopology=,SchedulerParameters: https://slurm.schedmd.com/slurm.conf.html- CoreWeave — Topology/Block scheduling on GPU clusters (NVLink-domain blocks,
--segment,--exclusive=topo): https://docs.coreweave.com/products/sunk/optimize_workloads/topology-scheduling
Related: Slurm for GPU Clusters · HPC Networking Fabric · Topology-Unaware Scheduling Starvation · Glossary
-
Slurm Topology Guide — https://slurm.schedmd.com/topology.html ↩↩↩↩↩↩↩↩
-
Slurm
topology.conf(5) — https://slurm.schedmd.com/topology.conf.html ↩↩↩↩↩↩↩ -
Slurm
sbatch(1) — https://slurm.schedmd.com/sbatch.html ↩↩↩↩ -
CoreWeave, Topology/Block Scheduling in Slurm — https://docs.coreweave.com/products/sunk/optimize_workloads/topology-scheduling ↩↩↩↩↩