DDP Troubleshooting

Start with fdl probe

fdl probe (single host) or fdl @cluster probe (cluster) is the first stop for any “it should work, why doesn’t it” question. It surfaces:

NCCL init failure

ncclCommInitAll failed typically means NCCL can’t establish peer-to-peer between devices.

nvidia-smi topo -m            # check device connectivity
fdl probe                     # check NCCL availability + libtorch wiring

Falls back to shared memory transport if peer-to-peer is unavailable. Or switch to a Cpu* mode in ElCheConfig to bypass NCCL entirely.

NCCL hangs on a cloud rig that formed fine

The most misattributed cluster failure, because the two planes have different network shapes and only one of them is exercised before the hang.

The control plane is hub-and-spoke: every worker talks to the controller and to nobody else. So a cohort forms, fdl status looks healthy, and admission passes with every worker reachable through a single open port (or a single ssh forward).

The NCCL data plane is a full mesh. Every rank opens TCP to every other rank on ephemeral ports, so an N-host run needs all-to-all reachability among the workers, not just worker-to-controller. On a default cloud security group, or any topology where workers reach the controller but not each other, formation succeeds and then the first collective blocks forever with no error: NCCL waits rather than failing.

Three ways out:

Tunneled workers (--ssh, join.tunnel_only) are CPU-mode only for exactly this reason: a port forward carries the hub-and-spoke plane and cannot carry a mesh.

NCCL version skew across hosts

If one host has libtorch shipping NCCL 2.27 and another has 2.26, the handshake fails. Build a matching libnccl on the easier side and LD_PRELOAD it via the worker’s env: block:

fdl nccl build              # auto-detects target version + archs

Parameter count mismatch

GpuWorker rank N: model has M params but config has K. The model factory produced a model with a different parameter count than the initial model used to extract starting parameters. Make sure model_factory(dev) produces an identical architecture for every device.

CUDA context corruption

CUBLAS_STATUS_EXECUTION_FAILED or SIGABRT after NCCL init usually means ncclCommInitRank was called from multiple threads on heterogeneous GPUs. The framework uses the init-on-main + split() pattern everywhere, but if you’re driving NcclComms manually, make sure you follow the same pattern.

Also covered by the “no CUDA before Trainer::run” invariant - any CUDA tensor created in main() before the launcher trampoline poisons spawned children’s contexts.

OOM on smaller GPU

Any anchor-based mode (NcclCadence, CpuAsync, CpuCadence) routes through ElChe, which assigns proportionally fewer batches to the slower/smaller GPU. The DataLoader’s per-device backend selection also helps: the large GPU goes resident while the small GPU streams.

.elche(ElCheConfig::nccl_cadence().max_anchor(50))   // or any anchor-based preset

CPU averaging timeout

The CPU averaging path now waits indefinitely for survivors and lets the elastic-membership machinery handle the dead-rank decision. If you need a hard time bound (e.g. CI gating), max_failure + ShutdownWithSave is the right knob - it triggers a clean checkpoint exit rather than hanging.

Cluster progressive hangs

If fdl @cluster runs hang several epochs in, the cause is usually:

  1. Stale child processes from a previous aborted run holding GPU memory or rendezvous ports. fdl @cluster cleans these up pre-spawn, but a kill -9 on the launcher bypasses cleanup.
  2. Shared-mount staleness when the project mount is NFS or virtiofs and the controller and a worker see different file states. fdl probe flags mount-state divergence.

Walk-in (fdl join) failures, by their messages

Every preparation failure is classed: transient exits 1 and re-dials under --persist; permanent exits 2 and stops (the systemd recipe pairs 2 with poweroff — see the cluster guide).