Files
llmgw/docs/fabrics.md
T
Ilker ManapandClaude Opus 5.5 7bb360fc78 Measure two nodes' fabric rail by rail: llmgw ctl fabric-test
doctor checks a node alone, and every check can pass while two nodes still
cannot reach each other. ctl fabric-test A B asks B's agent for an
ib_write_bw server and A's for the client, adapter by adapter as NCCL pairs
them, one rail at a time, and grades each against its link's rate. It goes
through the agents' streams, so there is no SSH between nodes; the agent
installs perftest the first time, and names the RoCE v2 GID itself.

The result is read from perftest's table by its heading, not a column
number. Agents now list the requests they answer beyond the usual
(Register.features), and the control plane refuses a test on one too old to
answer instead of waiting minutes for it. The forwarding dispatcher carries
the new capability, and its test says so: a missing forward would have made
the command quietly unavailable in production.

Not yet run over real InfiniBand or RoCE; this cluster has neither.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 21:29:23 +02:00

10 KiB
Raw Blame History

InfiniBand fabrics and NVLink

For a site whose GPU nodes are joined by InfiniBand or RoCE, by NVLink, or both — several independent fabrics, NVL72 racks, eight-GPU HGX boards. It says what llmgw does with them, what it needs from you before it can, how to check a node in one command, and where to look when a replica spanning nodes will not run.

Read install.md first; this is the part of it that only matters once there is a fabric.

Where this stands. Every piece here was written against the real formats of sminfo, saquery, ibnetdiscover, nvidia-smi topo -m and nvidia-smi -q, and tested against them — but none of it has yet run on a site with the hardware. Run llmgw doctor --json on two or three nodes first and keep the reports: if a parser meets a format it does not know, that report is what fixes it, and the fix is a control plane upgrade, not one per node.

What llmgw does with them

Two kinds of interconnect. InfiniBand or RoCE joins nodes (scale-out). NVLink joins GPUs — inside one machine on an HGX board, or across the nodes of a rack on an NVL72 or an NVLink Switch system (scale-up). A replica spanning nodes is placed, in order of preference:

  1. inside one multi-node NVLink domain, if any has enough usable nodes: tensor parallelism between its nodes then runs over NVLink;
  2. on one physical InfiniBand or RoCE fabric, preferring nodes under one leaf switch and putting nodes with a degraded cable last.

It is never split across two fabrics or two NVLink domains. Within a node, the GPUs a shard gets are the set with the most NVLink pairs, then the nearest to the fabric's adapters, then on one NUMA node.

A fabric has a name and an identity. The name is what agent.yaml says; the identity is what the adapters report — on InfiniBand the subnet managers every port on one fabric sees, on RoCE the IP network of its RoCE v2 address. The name is how you address a fabric; the identity is how llmgw catches the name being wrong: two independent fabrics given one name are placed as two, and the cluster page says so.

Before a node joins

On every GPU node:

What Why Check
NVIDIA driver everything nvidia-smi
nvidia-fabricmanager, on an NVSwitch machine without it CUDA does not start on those GPUs systemctl is-active nvidia-fabricmanager
rdma-core, ib_uverbs loaded user-space RDMA at all ls /sys/class/infiniband, lsmod | grep ib_uverbs
nvidia_peermem loaded GPUDirect RDMA; without it NCCL copies every transfer through host memory lsmod | grep nvidia_peermem
infiniband-diags tells llmgw which fabric a port is on; the agent installs it if missing which sminfo

On an NVL72 or NVLink Switch system, also:

What Why Check
nvidia-imex running, every node of the domain in /etc/nvidia-imex/nodes_config.cfg GPUs in different nodes share memory through it systemctl is-active nvidia-imex
an IMEX channel (options nvidia NVreg_CreateImexChannel0=1) an engine opens one ls /dev/nvidia-caps-imex-channels
every GPU joined to its domain the fabric manager does this nvidia-smi -q | grep -A6 Fabric shows State: Completed

On each InfiniBand fabric:

  • A subnet manager, and a standby. Without one, ports come up to INIT and stay there; without a standby, the fabric keeps its routes if the master stops but cannot recover from any change after. llmgw watches this but does not run one.
  • Its own name in agent.yaml on every node attached to it. Optionally, a distinct subnet_prefix in each subnet manager's configuration, which makes GIDs unique across fabrics; llmgw does not need it.

Declaring the fabrics

In /etc/llmgw/agent.yaml, the adapters on each fabric, by name:

fabrics:
  - name: ib-compute
    hcas: [mlx5_0, mlx5_1, mlx5_2, mlx5_3, mlx5_6, mlx5_7, mlx5_8, mlx5_9]
  - name: ib-storage
    hcas: [mlx5_4]

or --fabric ib-compute:mlx5_0,mlx5_1,… when onboarding (node-onboarding.md). An adapter belongs to one fabric; a node may be on several. A replica on ib-compute is given only ib-compute's adapters (NCCL_IB_HCA).

The rate is measured, not taken from the file: a fabric's rate on a node is the slowest of its active adapters, because a collective runs at the speed of its slowest link.

The agent checks the declaration against the adapters on every heartbeat. An adapter that is missing, or whose port is down, takes that fabric off the node within seconds — the node stops being offered multi-node work on it — and returns it when the port comes back.

Checking a node: llmgw doctor

sudo llmgw doctor                 # uses /etc/llmgw/agent.yaml when present
sudo llmgw doctor --json > $(hostname)-doctor.json

It reads the node and says what is wrong and how to fix it, most serious first, and exits non-zero when anything fails:

FAIL  nvlink  this machine has NVSwitch but nvidia-fabricmanager is inactive: CUDA will not initialise on its GPUs
              → systemctl enable --now nvidia-fabricmanager (its version must match the driver's)
WARN  fabric  nvidia_peermem is not loaded: GPUDirect RDMA is off, and NCCL copies every transfer through host memory
              → modprobe nvidia_peermem
OK    gpu     8 GPUs: 8× NVIDIA H100 80GB HBM3
OK    nvlink  NVLink groups: [0 1 2 3 4 5 6 7]
OK    fabric  mlx5_0/1 ACTIVE at 400 Gb/s (InfiniBand), subnet manager 0x248a07030030f0c0 (LID 1, SMINFO_MASTER)
OK    fabric  subnet managers of 0x248a07030030f0c0's fabric: 0x248a07030030f0c0 (master, priority 15), 0x248a07030030f1d4 (standby, priority 14)
OK    agent   fabric ib-compute: mlx5_0,mlx5_1,… usable at 400 Gb/s
OK    agent   NCCL bootstraps over ens3 (10.1.0.21)

followed by each GPU's NVLink peers and nearest adapter, each port's state, rate and errors, and the NCCL environment a shard on each fabric would be given. --json writes every fact it read as well as what it concluded — send that file when the report is not enough.

Checking two nodes: llmgw ctl fabric-test

Every check on one node can pass while two nodes still cannot reach each other — a cable into the wrong switch, a partition key, a RoCE network with no route — and a replica spanning them then hangs on its first all-reduce. Before placing one, on the control plane:

llmgw ctl fabric-test gpu01 gpu02
gpu01 → gpu02 on ib-compute, 41s

RAIL              LINK      MEASURED    VERDICT  DETAIL
mlx5_0 → mlx5_0   400 Gb/s  388.2 Gb/s  ok       97% of 400 Gb/s
mlx5_1 → mlx5_1   400 Gb/s  241.0 Gb/s  slow     60% of 400 Gb/s: check the cable, and the card's PCIe width and NUMA node
mlx5_2 → mlx5_2   400 Gb/s  0.0 Gb/s    fail     gpu01: ib_write_bw: Couldn't connect to 10.1.0.22:18517

It runs ib_write_bw (perftest, installed on a node the first time) from each of the first node's adapters on the fabric to the same adapter on the second — mlx5_0 to mlx5_0, the way NCCL pairs them — one rail at a time, through the agents, so no SSH between nodes is needed. Each rail is graded against its link's rate: ok at 80% or more, slow at 50%, fail below that or when nothing got through. --fabric picks one when the nodes share several, --seconds the length of each rail's run. The traffic is real: run it when the fabric is quiet.

It measures RDMA between adapters, not GPUDirect or NCCL itself: a pass says the fabric carries traffic between these two nodes at its rate, which is what a first all-reduce needs and what nothing on one node can show.

Watching them

  • Cluster page, Fabrics. Each declared fabric and what it physically is; a table of subnet managers per fabric — master, priority, standbys, the last change of master — and every disagreement: one name on two fabrics, two names on one, an adapter up but undeclared, ports up with no subnet manager, a fabric with no standby, a recent failover.
  • /admin/fabrics. Each fabric's map, from ibnetdiscover run by one of its nodes every five minutes: switches in their tiers, adapters under their leaves, cables coloured by rate, degraded ones in red, rail alignment, and the cluster's own adapters live with their traffic. Click anything for its detail.
  • /admin/nvlink. Each multi-node NVLink domain as a rack of trays: which GPUs are joined, which node's IMEX is down, which link runs degraded.
  • A node's page. Its ports with state, rate, errors and what they are physically on; its GPU-by-GPU NVLink matrix; its load.
  • Metrics, for alerts:
Metric Alert when
llmgw_ib_port_active a declared port is 0
llmgw_ib_port_counter_total{counter="symbol_error"} and link_downed rising
llmgw_ib_port_rate_gbps below its usual rate
llmgw_fabric_subnet_managers{state="standby"} absent or 0
llmgw_fabric_master_sm its sm_guid changes (a failover)
llmgw_fabric_problems{level="fail"} above 0
llmgw_gpu_nvlink_joined 0
llmgw_imex_up 0

What an engine is given

For a shard on an InfiniBand or RoCE fabric:

Variable Value
NCCL_IB_HCA that fabric's adapters on this node, and no others
NCCL_IB_DISABLE 0 (1 for a single-node shard)
NCCL_SOCKET_IFNAME, GLOO_SOCKET_IFNAME the node's management interface: mgmt_interface, or the one holding its management address
NCCL_IB_GID_INDEX on RoCE only: the RoCE v2 entry with an IPv4 address
NCCL_MNNVL_ENABLE, NCCL_CUMEM_ENABLE 1, for a replica inside a multi-node NVLink domain

A profile's env overrides any of them. vLLM launches a multi-node replica with its own multiprocessing (engines.vllm.launch_mode: mp, the default): every node runs vllm serve with its rank and the leader's address, the workers headless. launch_mode: ray is there for a vLLM too old for that.

When a replica spanning nodes will not run

It Look at
is not placed llmgw ctl explain-placement <model>: a fabric split by identity, a node whose IMEX is down, a node refused because its adapters are cabled to two fabrics
starts and hangs on its first collective llmgw ctl fabric-test between two of its nodes; the cluster page's fabric problems; doctor on each node — a port down, the wrong interface, RoCE without a v2 GID
runs, slowly nvidia_peermem; the map for a degraded cable; the node page's NVLink matrix for a group that crossed PCIe
worked yesterday the subnet manager table for a failover; link_downed on the node's ports