torch.distributed.elastic: global rank follows lexicographic FQDN order, silently de-localizing ring collectives on clusters with mixed-width hostnames
- Dominant language
- Python
- Stars
- 103k
- Forks
- 29.5k
- PR merge metrics
- PR metrics pending
Description
### What happens
`DynamicRendezvousHandler` assigns global ranks by sorting the participant set (`torch/distributed/elastic/rendezvous/dynamic_rendezvous.py`, current main):
- `:225` `class _NodeDesc` — `@dataclass(order=True)`, first field `addr: str` (docstring: *"The FQDN of the node or user specified local node address."*)
- `:269` `return _NodeDesc(local_addr or socket.getfqdn(), os.getpid(), local_id)`
- `:833` `for rank, node in enumerate(sorted(state.participants)):`
So the sort key is the node's **FQDN string**. On clusters whose hostnames embed numbers without zero padding, string order and numeric order diverge:
```
input : node7 node66 node8 node70
rank ord: node66 node7 node70 node8 # what torchrun assigns
numeric : node7 node8 node66 node70 # what an operator expects
```
`gpu-1 gpu-10 gpu-2 gpu-20` interleaves the same way; zero-padded names (`node007`) are immune. Ring-based collectives derive neighbor relations from global rank order, so this silently decides which node pairs exchange data over which links. Nothing in the stack validates it.
### 3-line reproduction (no cluster, no GPUs)
```python
names = ["node7", "node66", "node8", "node70"]
print(sorted(names)) # rank order today
print(sorted(names, key=lambda s: [int(t) if t.isdigit() else t
for t in __import__("re").split(r"(\d+)", s)]))
```
### Why it can matter
We surveyed 12 real naming conventions; 6 are affected. Worst common case: unpadded `node1..node256` across 16 network domains raises the fraction of ring edges crossing a domain boundary from 0.0625 to 0.2422 (3.9×) and creates 29 nodes whose *both* ring neighbors are in a different domain. AWS IP-based private DNS names (`ip-10-24-34-0..compute.internal`) are unpadded and, per EC2 docs, mandatory on IPv4-only subnets — exactly the strings `socket.getfqdn()` returns.
On our own hardware (Ascend 910C/HCCL, MoE RL post-training, EP=128 over 16 nodes) an equivalent permutation arising in another launcher cost **+8.4–9.3%** step time versus a contiguous order on the same nodes. We are explicitly **not** extrapolating that number to NCCL or other fabrics; the portable part is the permutation and that ring adjacency follows rank order. Explicit `--node_rank` and static rendezvous bypass this sorting.
### Suggested options (not proposing to flip the default)
Changing rank assignment silently would affect data sharding and checkpoint-to-rank association, so:
1. **Document it**: elastic/rendezvous docs state that global rank follows lexicographic FQDN order and that zero-padded/equal-width hostnames keep rank order aligned with numeric node order.
2. **Warn on detection**: when the participant set contains numeric fields of differing width (lexicographic ≠ natural order), emit a one-time warning naming the affected hosts. No behavior change.
3. **Opt-in natural ordering**: an env var / handler parameter selecting a numeric-aware key. Default unchanged.
Happy to implement whichever maintainers consider appropriate, one per PR.
Related: verl reaches the same outcome via sorting Ray placement groups by IP string; patch filed separately (verl-project/verl#7160).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
cc @awgu @wanchaol @fegin @fduwjj @wz337 @wconstab @d4l3k @pragupta @msaroufim @dcci @aditvenk @weifengpy @kapilsh
Contributor guide
Assessment
This issue has not been assessed yet.