"""NCCL and arx settings for a rank that mentat placed on a ring of boxes.
Runs at interpreter start (fabric_ring.pth) and does nothing unless mentat
gave this process MENTAT_FABRIC_LAYOUT=ring, which it does for each rank of a
TP=RING4 claim. On a ring each box cables one port to the previous rank and
the other to the next, so a rank sends to next and receives from prev on
different ports, and the diagonal ranks share no cable. This sets:
- NCCL_IB_HCA to both PCIe roots' functions of both ports, NCCL_ALGO=Ring so
NCCL only talks to neighbours, and NCCL_GRAPH_FILE to a ring whose channels
receive on the ports toward prev and send on the ports toward next. NCCL
cannot infer that wiring, since it assumes every NIC reaches every peer.
- the GID from each port's own address, since each cable has its own subnet
and no one GID index fits every device.
- VLLM_ARX_RING, ARX_RING_PREV_HCAS and ARX_RING_NEXT_HCAS for arx.
NCCL reads its environment on first use, so this must run before vLLM
imports it.
"""
import os
def _port_devices(iface: str) -> list[str]:
"""RDMA devices behind the port that carries iface, one per PCIe root.
Each ConnectX-7 port is two PCI functions on two roots with the same
bus:device.function (0000:01:00.1 and 0002:01:00.1), and each function
has its own RDMA device. Sorted by PCI address, which is also the order
libibverbs and NCCL list them in.
"""
pci = os.path.basename(os.path.realpath(f"/sys/class/net/{iface}/device"))
bdf = pci.split(":", 1)[1]
devs = []
for fn in sorted(os.listdir("/sys/bus/pci/devices")):
if fn.split(":", 1)[1] != bdf:
continue
ib = f"/sys/bus/pci/devices/{fn}/infiniband"
if os.path.isdir(ib):
devs += [(fn, d) for d in os.listdir(ib)]
return [d for _, d in sorted(devs)]
def _graph_xml(nccl_index: dict[str, int], prev: list[str], nxt: list[str], nchannels: int) -> str:
"""A ring graph for one GPU per node: channel c receives on prev[c % 2]
and sends on next[c % 2], alternating PCIe roots across channels."""
chans = "".join(
f''
f''
for c in range(nchannels)
)
# The speeds and path types are what NCCL computes for its own ring on
# these boxes. It picks protocols and chunk sizes from them, which set
# the order its reductions sum in.
return (
''
f''
f"{chans}\n"
)
def _setup() -> None:
if os.environ.get("MENTAT_FABRIC_LAYOUT") != "ring":
return
prev = _port_devices(os.environ["MENTAT_FABRIC_PREV_IFACE"])
nxt = _port_devices(os.environ["MENTAT_FABRIC_NEXT_IFACE"])
if len(prev) != 2 or len(nxt) != 2:
raise RuntimeError(f"fabric_ring: expected two RDMA devices per port, got prev={prev} next={nxt}")
# On a switch both "ports" can be the same one; list each device once.
devs = sorted(set(prev + nxt), key=lambda d: os.path.realpath(f"/sys/class/infiniband/{d}/device"))
index = {d: i for i, d in enumerate(devs)}
nchannels = int(os.environ.get("NCCL_MAX_NCHANNELS") or 8)
path = f"/tmp/nccl-ring-graph.{os.getpid()}.xml"
with open(path, "w") as f:
f.write(_graph_xml(index, prev, nxt, nchannels))
os.environ.update({
"NCCL_IB_HCA": "=" + ",".join(devs),
"NCCL_IB_MERGE_NICS": "0",
"NCCL_ALGO": "Ring",
"NCCL_CROSS_NIC": "1",
"NCCL_GRAPH_FILE": path,
"NCCL_IB_ADDR_FAMILY": "AF_INET",
"NCCL_IB_ROCE_VERSION_NUM": "2",
"VLLM_ARX_RING": "1",
"ARX_RING_PREV_HCAS": ",".join(prev),
"ARX_RING_NEXT_HCAS": ",".join(nxt),
})
os.environ.pop("NCCL_IB_GID_INDEX", None)
_setup()