Skip to content

Commit 3d596fe

Browse files
lvyufengclaude
andauthored
fix: answer gloo requests with the flagos backend and stage collectives over host memory (flagos-ai#435)
On a host with no FlagCX, no vendor torch and no vendor communication library there was no working collective at all. `init_process_group(backend="flagos")` raised "no suitable inner backend" from `ProcessGroupFlagOS._build_inner`, and `init_process_group(backend="gloo")` -- what torchrun and the transformers FSDP2 tests ask for -- built a `ProcessGroupGloo` whose C++ collectives reject flagos tensors with `ProcessGroupGloo::allreduce: unsupported device type flagos` (issue flagos-ai#263). Six `Qwen3ModelTest::test_fsdp2_*` nodeids failed on that. Two additions close the gap. Both live in `torch_fl/comm/process_group.py` and both are gated by a runtime switch. * `_try_build_staged_gloo` adds a last tier to `_build_inner`. gloo ships inside c10d, so it is always present; the tier builds a real `ProcessGroupGloo` and wraps it in `_HostStagedGloo`, a `ProcessGroup` facade that moves every flagos operand through host memory (device -> host -> collective -> device) and synthesises the two virtuals gloo does not implement (`allgather_into_tensor_coalesced`, `reduce_scatter_tensor_coalesced`) from gloo's `_allgather_base` / `_reduce_scatter_base`. It costs a copy per collective, so it runs only after FlagCX and the vendor-native backend have declined, and it warns once when it is used. `FLAGOS_DIST_STAGED_GLOO=0` declines the tier so the final diagnostic names the reason instead. * `redirect_gloo_requests` answers the request on the requested side. gloo cannot be repaired from the gloo side: gloo's own `BackendConfig` expansion is what registers a `ProcessGroupGloo` under the flagos device type, its creator branch asserts `isinstance(backend_class, ProcessGroupGloo)`, and `c10d.Backend` cannot be subclassed from Python at all. So the request is rewritten to `"flagos"` before the backend config is built: `import torch_fl` interposes on `torch.distributed.init_process_group` and `new_group` (and mirrors the wrappers onto `torch.distributed.distributed_c10d`, or a `from ... import init_process_group` call site would miss the redirect), but only when `torch._C._get_accelerator().type` is the flagos device, because gloo is a legitimate choice on a cpu/cuda host. `FLAGOS_DIST_REDIRECT_GLOO=0` keeps the requested backend verbatim while debugging. Measured on the MUSA MTT S5000 box with no FlagCX, no `torch_musa.distributed` and no NCCL -- the worst case. Two ranks, `torch_fl/comm/process_group.py` as committed: init_process_group(backend="flagos") -> ProcessGroupFlagOS/_HostStagedGloo all_reduce -> [3.0, 3.0] on both init_process_group(backend="gloo") -> redirected; all_reduce, all_gather_into_tensor, reduce_scatter_tensor, cpu all_reduce, barrier and _c10d_functional.all_gather_into_tensor all correct on both ranks new_group([0, 1], backend="gloo") -> redirected; subgroup all_reduce -> [3.0, 3.0] on both FLAGOS_DIST_REDIRECT_GLOO=0 -> plain ProcessGroupGloo again, fails on the flagos tensor: the switch is load-bearing Transformers FSDP2 re-measurement, same harness, before/after, six nodeids: {"FAIL": 6} -> {"PASS": 3, "FAIL": 3}. The three that now pass are `test_fsdp2_save_load`, `test_fsdp2_sharding_structure_0_untied` and `test_fsdp2_sharding_structure_1_tied`. The other three get all the way through the collectives (`[Gloo] Rank 1 is connected to 1 peer ranks`) and then fail on a separate, newly root-caused defect at `_fsdp_param_group.py::FSDPCommContext.lazy_init`: `torch_fl.flagos.Stream.__init__` routes every non-GCU platform without a CUDA runtime to the Ascend ACL stream, so on MUSA it raises `RuntimeError: libascendcl.so not found. ACL runtime is required`. That is a MUSA stream gap, not the collective path this change fixes; it is recorded as such in `docs/reference/hf-coverage.md` and `docs/architecture/distributed-flagcx.md` section 0.4 rather than claimed as fixed. The new `tests/unit/test_gloo_redirect.py` drives the decision table with fakes (no GPU, no rendezvous): both switch values, both accelerator spellings, positional and keyword backend arguments, that arguments are copied rather than mutated, that the interposition is idempotent and reaches the defining module, and that a staged-tier gloo failure is reported as a reason rather than raised. Tested: ruff check . -> All checks passed! ruff format --check . -> 313 files already formatted pytest tests/unit/test_env_registry.py tests/unit/test_vendor_routing.py tests/unit/test_nccl_extension_fallback.py tests/unit/test_gloo_redirect.py -> 87 passed No operator routing changes: this change reroutes no operator through a vendor-native backend or FlagGems, so the `docs/reference/operator-support.md` per-platform record does not apply. For the same reason `tests/manual/flaggems_overload_survey.py` was not re-run. Fixes flagos-ai#263 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 05be850 commit 3d596fe

6 files changed

Lines changed: 871 additions & 20 deletions

File tree

‎docs/architecture/distributed-flagcx.md‎

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ backend:
2222
(allreduce / allgather / reduce_scatter / alltoall / broadcast / gather / scatter / reduce /
2323
send / recv / barrier, …). Each override converts privateuseone tensors into the device view
2424
the inner backend needs (`_C._flagos_to_cuda_view`), delegates to `self._inner`, and returns
25-
the inner backend's Work. Inner backend priority: FlagCX → HCCL (ascend) → NCCL
26-
(nvidia/metax).
25+
the inner backend's Work. Inner backend priority: FlagCX → the vendor's native backend
26+
(HCCL for ascend, NCCL for nvidia/metax, MCCL for MUSA) → host-staged gloo, the last tier
27+
and the only one that needs no vendor library (§0.4).
2728

2829
- **Registration**: `import torch_fl` calls `register_flagos_backend()`, which runs
2930
`Backend.register_backend("flagos", creator, devices=["privateuseone"])` and sets
@@ -80,6 +81,58 @@ Integrate against this contract; do not guess:
8081

8182
The manual suite does not yet exercise point-to-point operations, gather/scatter roots, all-to-all, multi-node rendezvous, process failure recovery, or more than two devices. Those paths remain unvalidated on Enflame hardware.
8283

84+
### 0.4 Host-staged gloo and gloo redirection (measured on MUSA MTT S5000)
85+
86+
A host with no FlagCX, no vendor torch and no vendor communication library used to have no
87+
working collective at all: `ProcessGroupFlagOS` raised "no suitable inner backend" from
88+
`backend="flagos"`, and `backend="gloo"` — what torchrun and the transformers FSDP2 tests ask
89+
for — built a `ProcessGroupGloo` whose C++ collectives reject flagos tensors with
90+
`ProcessGroupGloo::allreduce: unsupported device type flagos` (issue
91+
[#263](https://github.com/flagos-ai/Torch-FL/issues/263)).
92+
93+
Two additions close that gap. Both are in `torch_fl/comm/process_group.py` and both are gated
94+
by a runtime switch:
95+
96+
- **`_try_build_staged_gloo` — the last tier of `_build_inner`.** gloo ships inside c10d, so it
97+
is always present; this tier builds a real `ProcessGroupGloo` and wraps it in
98+
`_HostStagedGloo`, a `ProcessGroup` facade that moves every flagos operand through host memory
99+
(device → host → collective → device) and synthesises the two virtuals gloo does not implement
100+
(`allgather_into_tensor_coalesced`, `reduce_scatter_tensor_coalesced`) from gloo's own
101+
`_allgather_base` / `_reduce_scatter_base`. It costs a copy per collective, so it runs only
102+
after FlagCX and the vendor-native backend have declined, and it warns once when it is used.
103+
`FLAGOS_DIST_STAGED_GLOO=0` declines the tier so the failure is explicit instead.
104+
- **`redirect_gloo_requests` — answered on the requested side.** gloo cannot be repaired from
105+
the gloo side: gloo's own `BackendConfig` expansion is what registers a `ProcessGroupGloo`
106+
under the flagos device type (`pg._device_types` reads `[('flagos', 'flagos'), ('cpu',
107+
'cpu')]` on a plain gloo group), its creator branch asserts `isinstance(backend_class,
108+
ProcessGroupGloo)`, and `c10d.Backend` cannot be subclassed from Python at all. So the request
109+
is answered with the `flagos` backend *before* the backend config is built: `import torch_fl`
110+
interposes on `torch.distributed.init_process_group` and `torch.distributed.new_group` and
111+
rewrites a `"gloo"` backend argument to `"flagos"`, but only when the process accelerator is
112+
the flagos device (`torch._C._get_accelerator().type`), because gloo is a legitimate choice on
113+
a cpu/cuda host. `FLAGOS_DIST_REDIRECT_GLOO=0` keeps the requested backend verbatim while
114+
debugging a distributed problem.
115+
116+
**Measured** (two ranks, no FlagCX / no `torch_musa.distributed` / no NCCL, i.e. the worst case;
117+
`torch_fl/comm/process_group.py` as landed):
118+
119+
| Request | Result |
120+
| --- | --- |
121+
| `init_process_group(backend="flagos")` | `ProcessGroupFlagOS` over `_HostStagedGloo`; `all_reduce` → `[3.0, 3.0]` on both ranks |
122+
| `init_process_group(backend="gloo")` | redirected to `flagos`; `all_reduce`, `all_gather_into_tensor`, `reduce_scatter_tensor`, cpu `all_reduce`, `barrier`, and `torch.ops._c10d_functional.all_gather_into_tensor` all correct on both ranks |
123+
| `new_group([0, 1], backend="gloo")` | redirected to `flagos`; subgroup `all_reduce` → `[3.0, 3.0]` on both ranks |
124+
| `FLAGOS_DIST_REDIRECT_GLOO=0` | both ranks get a plain `ProcessGroupGloo` again and fail on the flagos tensor — the switch is load-bearing |
125+
126+
**Partially validated on the FSDP2 suite.** Of the six `Qwen3ModelTest::test_fsdp2_*` nodeids
127+
that #263 counted, three now pass (`test_fsdp2_save_load`, `test_fsdp2_sharding_structure_0_untied`,
128+
`test_fsdp2_sharding_structure_1_tied`); the other three get past the collectives and then fail in
129+
FSDP2's stream setup on a **separate** defect — `torch_fl.flagos.Stream.__init__` routes every
130+
non-GCU platform without a CUDA runtime to the Ascend ACL stream, so on MUSA it raises
131+
`RuntimeError: libascendcl.so not found. ACL runtime is required` at
132+
`_fsdp_param_group.py::FSDPCommContext.lazy_init`. MUSA has no Python-side stream/event
133+
implementation (`torch_fl/accelerator/` has `ascend`, `gcu`, `metax`, `dcu`, `cuda`, `bpu`,
134+
`ppu`), so those three remain blocked on that work, not on the collective path.
135+
83136
---
84137

85138
## 1. Background and prior state

‎docs/reference/environment-variables.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,18 @@ Logging and tracing. None of these changes routing.
112112
| `FLAGOS_TRACE` | Runtime | `0` (off) | Verbose logging in the device profiler shim compiled into this build. One switch covers every accelerator: exactly one device tracer is compiled per build, so the name is never ambiguous |
113113
| `FLAGOS_TRACER_LIBRARY` | Runtime | Auto-discovered | Override the tracer library the profiler shim `dlopen`s, when the default path does not match the installed driver |
114114

115+
### Distributed
116+
117+
How `torch.distributed` picks a backend for the flagos device. The `"flagos"`
118+
backend is registered at import; these switches cover the two cases where a
119+
caller does *not* ask for it by name. See
120+
[distributed over FlagCX](../architecture/distributed-flagcx.md).
121+
122+
| Variable | Scope | Default | Purpose |
123+
|----------|-------|---------|---------|
124+
| `FLAGOS_DIST_REDIRECT_GLOO` | Runtime | `1` (on) | Answer a plain `init_process_group(backend="gloo")` or `new_group` request with the flagos backend when the process accelerator is the flagos device. `torch.distributed` routes any device type it does not recognise to gloo, and a `ProcessGroupGloo` rejects flagos tensors with `unsupported device type flagos` ([#263](https://github.com/flagos-ai/Torch-FL/issues/263)). Set `0` to keep the requested backend |
125+
| `FLAGOS_DIST_STAGED_GLOO` | Runtime | `1` (on) | Allow the host-staged gloo inner backend: the last fallback tier of the `"flagos"` backend when no vendor communicator (FlagCX/NCCL/HCCL/MCCL) is available. It needs no vendor library at all but copies every flagos operand device->host->device per collective. Set `0` to fail loudly instead of staging. One warning is emitted the first time a group is built on this tier |
126+
115127
### Vendor compatibility
116128

117129
Import-time shims that adapt a vendor's torch or driver to the flagos device.

‎docs/reference/hf-coverage.md‎

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ root-cause filing. Raw JSON remains outside the repository at
8383

8484
### What has changed since this baseline
8585

86-
Four of the five causes have a fix; the fifth is the only reason this baseline is
87-
not fully resolved.
86+
All five causes have a fix. For one of them (#263) that is not the same thing as
87+
all of its nodeids passing: the collective failure the row names is gone, and the
88+
three nodeids that used to be blocked behind it now reach a separate defect in
89+
FSDP2's stream setup instead.
8890

8991
- **#250, #265** (device context poisoning, and its `test_model_parallelism`
9092
trigger) — closed 2026-09-15, the same day PR #280 (`6b2af4b`, "stage
@@ -108,18 +110,30 @@ not fully resolved.
108110
MUSA integer division through mudnn and promote true division").
109111
- **#264** (TorchInductor/Triton requires CUDA libraries) — PR #419 (`48829b8`,
110112
"ask torch, not the redirected probe, before aliasing cuda to flagos").
111-
- **#263** (ProcessGroupGloo rejects `flagos` tensors) — still open. Its six
112-
FSDP2 nodeids are the remaining known failures of this baseline, and the failure
113-
reproduces today: `RuntimeError: ProcessGroupGloo::allreduce: unsupported device
114-
type flagos`.
113+
- **#263** (ProcessGroupGloo rejects `flagos` tensors) — PR #435 (2026-09-27, "answer gloo
114+
requests with the flagos backend and stage collectives over host memory"). gloo cannot be
115+
repaired from the gloo side, so the request is answered with the `flagos` backend before the
116+
backend config is built, and `ProcessGroupFlagOS` grew a last tier that stages
117+
collectives through host memory over a real gloo group
118+
(`docs/architecture/distributed-flagcx.md` §0.4 has the mechanism and the
119+
measurements). Re-measured with the same harness on this change over `05be850`:
120+
`{"PASS": 3, "FAIL": 3}` of the six FSDP2 nodeids, against six failures at
121+
baseline. The three that pass are `test_fsdp2_save_load`,
122+
`test_fsdp2_sharding_structure_0_untied` and `test_fsdp2_sharding_structure_1_tied`.
123+
The three that fail are `test_fsdp2_plan_vs_ddp_0_untied`, `test_fsdp2_plan_vs_ddp_1_tied`
124+
and `test_fsdp2_save_load_dcp`, and they fail *after* the collectives, in
125+
`_fsdp_param_group.py::FSDPCommContext.lazy_init`, with `RuntimeError: libascendcl.so
126+
not found. ACL runtime is required` — `torch_fl.flagos.Stream` sends every non-GCU
127+
platform without a CUDA runtime to the Ascend ACL stream. That is a MUSA gap with no
128+
issue of its own yet, not the collective path this row tracks.
115129

116130
Because `Affected tests` and the baseline header above describe `64e60dd`, the
117131
whole table is a dated snapshot and not a statement about the current tree.
118132
Re-running the baseline is what refreshes it — and is also what fills in the
119133
`Fingerprint` column, per the note above. The closures above are recorded from the
120-
issues and the commits that landed; only the #268 re-measurement re-ran any of
121-
this baseline's tests, and it covers eight nodeids out of twenty, so the other
122-
four causes are **not revalidated** here.
134+
issues and the commits that landed; two re-measurements have re-run any of this
135+
baseline's tests since — #268's eight borrowed nodeids and #263's six — so the rows
136+
for #250/#265, #264 and #266 are **not revalidated** here.
123137

124138
### Failed test inventory
125139

0 commit comments

Comments
 (0)