Skip to content

fix(rng): make default_generators iterable instead of looping forever - #52

Merged
zhaoyinglia merged 1 commit into
flagos-ai:mainfrom
lvyufeng:metax-2.10
Aug 4, 2026
Merged

fix(rng): make default_generators iterable instead of looping forever#52
zhaoyinglia merged 1 commit into
flagos-ai:mainfrom
lvyufeng:metax-2.10

Conversation

@lvyufeng

@lvyufeng lvyufeng commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

What

The three default_generators shims — torch_fl/accelerator/metax/_metax_compat.py, torch_fl/accelerator/cuda/_cuda_compat.py, and torch_fl/flagos/__init__.py — stand in for a value upstream types as a tuple (torch/cuda/__init__.py:158), so callers are entitled to iterate it, slice it, or wrap it in list(). All three were list-like proxies defining only __getitem__/__len__, and __getitem__ was unbounded.

With no __iter__, Python falls back to the legacy iteration protocol: call __getitem__(0, 1, 2, ...) until IndexError. An unbounded __getitem__ never raises, so:

for g in torch.cuda.default_generators:   # never terminates

became an infinite loop that allocated a fresh CUDA generator on every step. It presented as a hang rather than an error, which is why nothing surfaced it — found while walking the generator list on an 8-card C550, where the process sat at 200% CPU indefinitely (34 min before I killed it). The flagos variant did not hang but was equally wrong: out-of-range indices surface as a RuntimeError from C++, which aborts iteration instead of ending it.

All three now define __iter__ and bounds-check __getitem__, raising IndexError past the device count and wrapping negative indices the way the tuple they replace does. Slices are supported for the same reason.

Testing

New test_default_generators_iterable is parametrized over both the torch.cuda and flagos shims and asserts all three properties (iteration terminates at device_count, slicing works, out-of-range raises IndexError). Verified to hang on the unfixed tree (stashed the fix, pytest timed out) and pass on the fixed one — not merely green-on-green.

Verified on 8xC550 (MetaX boxing, torch 2.10.0+metax3.8.1.0, triton-metax 3.6.0):

suite vendor (FLAGOS_METAX_BOXING=1) FlagGems (+FLAGOS_USE_FLAGGEMS=1)
test_rng_dispatch.py 104 passed / 2 skipped / 1 xfailed 106 passed / 1 xfailed
-m main_ops 104 passed / 28 skipped / 1 xfailed

This branch also confirms the unified-RNG work from #39/#49 is complete on MetaX — the boxing build compiles 103 GetFlagosDefaultCudaGenerator injection sites, and python_op_caller.cc rides in via FLAGGEMS_PYTHON=ON. Beyond the repo suite I ran an independent probe of 91 RNG ops covering every overload family (.out / .names_out / .generator / *_like), fp16/bf16/fp64/int32, and the nn.init.* entry points, checking run + same-seed-same-draw + different-seed-differs for each: 89/91 vendor, 91/91 FlagGems. The 2 vendor misses are exactly the documented native_dropout schema gap (already an xfail).

Distribution moments were checked against theory rather than just reproducibility, since a mis-bound overload can be perfectly reproducible and still wrong: randn skew +0.004 / excess kurtosis −0.0005, poisson(4) mean 4.0001 var 3.9925, randint chi² 6.99 (df=9), gamma(3) mean 2.997 var 2.992, dirichlet rows sum to 1, cauchy median +0.001, consecutive draws uncorrelated (r = −0.009), get_state/set_state round-trip exact. All 8 cards pass independently, and seeding card 0 does not perturb card 1.

ruff==0.15.12: ruff check . and ruff format --check . both pass.

Not caused by this change

Under -m "flaggems and main_ops" on MetaX, test_mm_dispatch / test_mean_dispatch test_dispatch_log_flaggems_runtime fail: they assert -> flagos_python, but backends_metax_flaggems.conf deliberately routes mm/bmm/mean.dim to cuda. Confirmed reproducing on the unmodified tree.

🤖 Generated with Claude Code

The three `default_generators` shims (metax compat, cuda compat, and the
flagos module) stand in for a value upstream types as a *tuple*, so callers
are entitled to iterate it, slice it or wrap it in `list()`. All three were
list-like proxies defining only `__getitem__`/`__len__`, and `__getitem__`
was unbounded.

With no `__iter__`, Python falls back to the legacy iteration protocol:
call `__getitem__(0, 1, 2, ...)` until IndexError. An unbounded
`__getitem__` never raises, so `for g in torch.cuda.default_generators`
became an infinite loop that allocated a fresh CUDA generator on every
step. It presented as a hang rather than an error, which is why nothing
surfaced it -- found while walking the generator list on an 8-card C550,
where the process sat at 200% CPU indefinitely. The flagos variant did not
hang but was equally wrong: out-of-range indices surface as a RuntimeError
from C++, which aborts iteration instead of ending it.

All three now define `__iter__` and bounds-check `__getitem__`, raising
IndexError past the device count and wrapping negative indices the way the
tuple they replace does. Slices are supported for the same reason.

Regression test is parametrized over both the `torch.cuda` and `flagos`
shims and asserts all three properties; verified to hang on the unfixed
tree and pass on the fixed one.

Verified on 8xC550 (metax boxing): RNG suite 104 passed/2 skipped/1 xfailed
(vendor) and 106 passed/1 xfailed (FlagGems). Wider probe of 91 RNG ops
covering every overload family, dtype and nn.init entry point: 89/91 clean
on the vendor path (the 2 are the known native_dropout schema gap) and
91/91 under FlagGems. Distribution moments checked against theory on all
8 cards. Both ruff gates pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@zhaoyinglia
zhaoyinglia merged commit 09c8ec1 into flagos-ai:main Aug 4, 2026
5 of 6 checks passed
AlexMa616 pushed a commit to BrianPei/PyTorch-Plugin-FL that referenced this pull request Aug 4, 2026
…flagos-ai#52)

The three `default_generators` shims (metax compat, cuda compat, and the
flagos module) stand in for a value upstream types as a *tuple*, so callers
are entitled to iterate it, slice it or wrap it in `list()`. All three were
list-like proxies defining only `__getitem__`/`__len__`, and `__getitem__`
was unbounded.

With no `__iter__`, Python falls back to the legacy iteration protocol:
call `__getitem__(0, 1, 2, ...)` until IndexError. An unbounded
`__getitem__` never raises, so `for g in torch.cuda.default_generators`
became an infinite loop that allocated a fresh CUDA generator on every
step. It presented as a hang rather than an error, which is why nothing
surfaced it -- found while walking the generator list on an 8-card C550,
where the process sat at 200% CPU indefinitely. The flagos variant did not
hang but was equally wrong: out-of-range indices surface as a RuntimeError
from C++, which aborts iteration instead of ending it.

All three now define `__iter__` and bounds-check `__getitem__`, raising
IndexError past the device count and wrapping negative indices the way the
tuple they replace does. Slices are supported for the same reason.

Regression test is parametrized over both the `torch.cuda` and `flagos`
shims and asserts all three properties; verified to hang on the unfixed
tree and pass on the fixed one.

Verified on 8xC550 (metax boxing): RNG suite 104 passed/2 skipped/1 xfailed
(vendor) and 106 passed/1 xfailed (FlagGems). Wider probe of 91 RNG ops
covering every overload family, dtype and nn.init entry point: 89/91 clean
on the vendor path (the 2 are the known native_dropout schema gap) and
91/91 under FlagGems. Distribution moments checked against theory on all
8 cards. Both ruff gates pass.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
zhaoyinglia pushed a commit that referenced this pull request Aug 4, 2026
* ci: add configured MetaX build and integration workflows

* ci: fix MetaX workflow input expressions

* ci: persist isolated MetaX Python path

* ci: preserve platform environment in integration tests

* ci: isolate CUDA wheel from vendor torch

* ci: install CUDA integration test dependencies

* ci: tolerate CUDA images without standalone libc10_cuda

* ci: pin CUDA CI image digest

* ci: build MetaX integration wheel locally

* ci: diagnose CUDA library layout

* ci: fix native library staging

* ci: load isolated native runtimes

* ci: build CUDA integration wheel locally

* ci: stage CUDA runtime dependencies

* ci: diagnose MetaX runtime symbols

* ci: retain MetaX CUDA runtime dependency

* fix: provide MetaX generator ABI shim

* fix: use vendor CUDA generator for MetaX boxing

* ci: capture first MetaX operator failure

* fix(rng): close the last two generator-injection gaps and put RNG under CI (#49)

The unified-RNG contract -- one `torch.manual_seed` reaching every RNG op on the
flagos device -- had no CI protection at all, and two codegen templates still
leaked.

Injection gaps. Generator-less RNG overloads carry no `Generator?` in their
schema, so `_generator_inject_line` never fires and they fall back to ATen's own
default CUDA generator, unreachable from `torch.manual_seed` on a CPU-torch
wheel. `torch.randint_like(...)` and every RNG out-variant dispatch to exactly
such overloads. The fix has to be made once per template because the ATen
insertion point differs each time: `*_like` takes the generator before `dtype`
(it carries `self`, so it lands on gen_functional_pure rather than gen_factory),
while out-variants take it before the first of names/memory_format/out. That
second one covered 11 kernels -- rand/randn `.out` and `.names_out`,
rand_like/randn_like `.out`, randint `.out`/`.low_out`, randint_like
`.out`/`.low_dtype_out`, randperm `.out`. The three base sets are now
module-level constants, one per template, so all three positions read together.

`_is_in_bad_fork`. `torch.random._seed_custom_device` needs both it and
`manual_seed_all` to seed a custom device; only the latter existed, so every
`torch.manual_seed()` warned "Set seed for `flagos` device does not take effect"
-- false, since seeding really goes through the patched
`torch.cuda.manual_seed_all`. Adding it silences the warning and makes
`flagos.initial_seed()` track the global seed.

Tests. The old file was marked `flaggems_python` with no `main_ops`, while CI
selects only `-m main_ops` and `-m "flaggems and main_ops"` -- so it never ran.
It also skipped itself entirely without FLAGOS_USE_FLAGGEMS, on the premise that
the native path could not be reproducible; C++ generator injection made that
premise obsolete. Rewritten to run under both configs (the native-path injection
is only exercised by the vendor run) and to cover 39 ops grouped by the mechanism
each stresses, plus the seed plumbing itself, multi-device generators, and
distribution correctness. CI now collects 105 RNG tests in the vendor job and 2
in the FlagGems job, up from 0; no workflow change was needed, the markers are
the wiring.

Two gaps injection cannot reach are recorded as non-strict xfails rather than
left implicit: `native_dropout` has no `Generator?` anywhere in its ATen schema,
so the vendor path has no argument to inject into; and FlagGems `multinomial`
launches its Triton kernel against the wrong device for any index != 0, a
device-context bug on that path rather than an RNG one.

Verified on 8xA100 under both configs: 102 passed/2 skipped/1 xfailed (vendor)
and 104 passed/1 xfailed (FlagGems). Regression clean -- main_ops 115 passed,
flaggems main_ops 14 passed, flaggems_python 27 passed, native 445 passed/3
xpassed (pre-existing conv1d segfault excluded). Both ruff gates pass.

* fix(rng): make default_generators iterable instead of looping forever (#52)

The three `default_generators` shims (metax compat, cuda compat, and the
flagos module) stand in for a value upstream types as a *tuple*, so callers
are entitled to iterate it, slice it or wrap it in `list()`. All three were
list-like proxies defining only `__getitem__`/`__len__`, and `__getitem__`
was unbounded.

With no `__iter__`, Python falls back to the legacy iteration protocol:
call `__getitem__(0, 1, 2, ...)` until IndexError. An unbounded
`__getitem__` never raises, so `for g in torch.cuda.default_generators`
became an infinite loop that allocated a fresh CUDA generator on every
step. It presented as a hang rather than an error, which is why nothing
surfaced it -- found while walking the generator list on an 8-card C550,
where the process sat at 200% CPU indefinitely. The flagos variant did not
hang but was equally wrong: out-of-range indices surface as a RuntimeError
from C++, which aborts iteration instead of ending it.

All three now define `__iter__` and bounds-check `__getitem__`, raising
IndexError past the device count and wrapping negative indices the way the
tuple they replace does. Slices are supported for the same reason.

Regression test is parametrized over both the `torch.cuda` and `flagos`
shims and asserts all three properties; verified to hang on the unfixed
tree and pass on the fixed one.

Verified on 8xC550 (metax boxing): RNG suite 104 passed/2 skipped/1 xfailed
(vendor) and 106 passed/1 xfailed (FlagGems). Wider probe of 91 RNG ops
covering every overload family, dtype and nn.init entry point: 89/91 clean
on the vendor path (the 2 are the known native_dropout schema gap) and
91/91 under FlagGems. Distribution moments checked against theory on all
8 cards. Both ruff gates pass.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: nate.river <lvyufeng@cqu.edu.cn>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants