Skip to content

fix: ask torch, not the redirected probe, before aliasing cuda to flagos - #419

Merged
lvyufeng merged 1 commit into
flagos-ai:mainfrom
lvyufeng:fix/cuda-alias-guard-reads-real-cuda
Sep 26, 2026
Merged

lvyufeng merged 1 commit into
flagos-ai:mainfrom
lvyufeng:fix/cuda-alias-guard-reads-real-cuda

Conversation

@lvyufeng

@lvyufeng lvyufeng commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

AI Agent Information

Summary

_alias_cuda_to_flagos() decided whether to install by asking
torch.cuda.is_available(). On a build with no CUDA runtime that probe is no
longer torch's answer -- torch_fl.compile.inductor_backend._patch_native_cuda_probe()
repoints it at torch.flagos.is_available so Inductor's CUDA-shaped FakeTensor
probe finds the accelerator, and that import runs before the alias does. The guard
therefore read "yes, there is CUDA" on exactly the builds the alias exists for,
returned early, and left torch.cuda.current_device, synchronize,
device_count and get_device_properties as stock CPU torch's. Dynamo's
cuda_extra_check calls torch.cuda.current_device() while deciding whether to
compile for CUDA, so torch.compile(model, fullgraph=True) on a flagos module
died with AssertionError: Torch not compiled with CUDA enabled -- #264.

The fix adds torch_fl._real_cuda_is_available(), which reads the function
_patch_native_cuda_probe() saved as torch.cuda._flagos_original_is_available
(falling back to torch.cuda.is_available when nothing redirected it), and calls
it from the guard. On MUSA the alias now installs, device="cuda" resolves to the
flagos device, and torch.compile compiles. No conf, route or kernel changes:
where the alias now runs it hands out the same flagos device
FLAGOS_BACKEND_CONFIG already routed.

Change Type

  • Bug Fix
  • New Feature
  • Performance Optimization
  • Refactoring
  • Documentation
  • Testing
  • CI/Infrastructure
  • Breaking Change

Platforms Affected

  • CUDA
  • MetaX
  • Ascend
  • PPU
  • Platform-agnostic (all platforms)
  • MUSA (measured; see Verification)

Problem Analysis

What was broken/missing?

On MUSA, import torch_fl left the process in a half-shimmed state: the six
torch.cuda.* entries that _alias_cuda_to_flagos() owns were still stock CPU
torch's, so every "cuda"-shaped call raised, while torch.cuda.is_available()
reported True. torch.compile(model, fullgraph=True) could not compile, and
tests/manual/flaggems_overload_survey.py could not run its child unmodified
(recorded as an evidence gap in docs/reference/operator-support.md).

Why did it happen?

Two torch_fl functions write torch.cuda.is_available, in this order:

  1. _phase_vendor_compat() -> _patch_flaggems_philox() -> from flag_gems.utils import random_utils -> import flag_gems, whose fused/FLA kernels call
    torch.flagos.current_device() at module scope -> flagos._lazy_init() ->
    torch_fl.compile.flagtree_shim -> torch_fl.compile.inductor_backend, whose
    module-level _patch_native_cuda_probe() sets
    torch.cuda.is_available = torch.flagos.is_available and saves the replaced
    function as torch.cuda._flagos_original_is_available;
  2. _phase_ecosystem() -> _alias_cuda_to_flagos(), whose guard read
    torch.cuda.is_available().

The guard was written to mean "is there real CUDA here". After (1) it no longer
answers that question, and it answers "yes" on precisely the CUDA-less vendor
builds the alias exists to serve. The saved original was written and read by
nothing, which is the tell: the intent was recorded, the read was missing.

Investigation process:

  1. Reproduced torch.compile Triton backend requires libcuda.so even on flagos device #264 on MTT S5000 with import torch_fl alone: torch.compile(m, fullgraph=True) raised AssertionError: Torch not compiled with CUDA enabled.
  2. Traced the call into dynamo's cuda_extra_check ->
    torch.cuda.current_device() -> torch.cuda._lazy_init.
  3. Instrumented the shim state after import torch_fl and found
    torch.cuda.is_available() == True with all four aliased entries unchanged and
    torch_fl._cuda_alias_active == False.
  4. Traced the redirect's import chain with a __import__ hook: flag_gems is
    imported by _phase_vendor_compat()'s _patch_flaggems_philox(), and its FLA
    kernels reach flagos._lazy_init() -> torch_fl.compile -> the patch, so the
    redirect is in place a whole phase before _phase_ecosystem() calls the alias.
  5. Confirmed the saved torch.cuda._flagos_original_is_available returns False
    on MUSA, i.e. it is the answer the guard wanted.
  6. Wrote tests/unit/test_cuda_alias_guard.py and ran it against the pre-fix tree
    (git show HEAD:...) to prove it catches the defect: 6 failed. Post-fix: 6
    passed.

Solution Design

Implementation approach:

torch_fl/__init__.py gains a small helper next to the alias, and the guard calls
it:

def _real_cuda_is_available() -> bool:
    probe = getattr(torch.cuda, "_flagos_original_is_available", None)
    if probe is None:
        probe = torch.cuda.is_available
    return bool(probe())

Key design decisions:

  • Read the saved function, not the live probe. The live probe has two writers
    and only one of them is "torch's opinion". The saved copy is the only thing in
    the process that still answers "does torch have a CUDA runtime", and it already
    existed -- this change gives it a reader.
  • Fall back to torch.cuda.is_available. When nothing redirected it (no
    native accelerator, or no flagos device), the live probe is torch's answer, so
    the guard keeps its old behaviour there.
  • No change to what the alias does, only to whether it runs. The
    FLAGOS_ALIAS_CUDA=0 opt-out, the TorchFunctionMode, the torch.device
    wrapper and _cuda_alias_active are untouched.
  • Tests assert the shape of the guard, not just an outcome. One test parses
    the guard with ast and fails if it reads torch.cuda.is_available() again,
    because an outcome-only test on a CUDA host cannot see the difference.

Code changes by file:

  • torch_fl/__init__.py: new _real_cuda_is_available() (docstring records the
    measured failure mode); _alias_cuda_to_flagos()'s docstring paragraph on when
    it is a no-op now distinguishes "torch reports CUDA" from "the redirect says
    so"; the guard reads the helper.
  • torch_fl/compile/inductor_backend.py: comments only -- _patch_native_cuda_probe()
    now documents that the function it replaces is read back by
    torch_fl._real_cuda_is_available, and the save site says so too.
  • tests/unit/test_cuda_alias_guard.py (new): 6 tests over the helper's
    precedence, the guard's shape, and the child-process effect with
    FLAGOS_ALIAS_CUDA set/unset.
  • docs/reference/operator-support.md: new MUSA section with the measured
    before/after, and a new ## Update History row.

Changes by commit:

  1. 5112740 - fix: ask torch, not the redirected probe, before aliasing cuda to flagos - torch_fl/__init__.py, torch_fl/compile/inductor_backend.py, tests/unit/test_cuda_alias_guard.py and docs/reference/operator-support.md, the last two being the new test file and the operator-support record.

The branch was rebased three times after this PR was opened -- onto 17b5d30,
then onto 892432b, and now onto f84eb66, which is upstream's head; it had been
opened on 8cffc6f against 877afb1. Each rebase resolved one conflict in
docs/reference/operator-support.md's Update History table, because every upstream
landing adds a row to it, and needed no code change. The single commit on the
branch was amended as its message and its docs provenance were corrected, and the
tip is 5112740. Upstream #410 adds csrc/aten/sparse_csr_ops.cc and edits
csrc/aten/device_boxing.h, and #416 edits csrc/profiler/cupti_shim.h, so the
extension was rebuilt from the rebased source with
FLAGOS_ACCELERATOR=musa FLAGOS_BUILD_VENDOR=1 FLAGOS_BUILD_FLAGGEMS=1 FLAGOS_BUILD_FLAGGEMS_CPP=0 python setup.py build_ext --inplace, leaving
torch_fl/lib/libtorch_fl.so at md5 e34ada57822e793d42c3dd1c18b5c4f8 -- the same
value the previous base's rebuild produced, since the two newest upstream commits
reach csrc/ only under csrc/aten/backends/gcu/, which this build does not
compile. Every measurement below was re-taken on that tree, with the pre-fix arm
being upstream f84eb66 with torch_fl/__init__.py at the pre-fix revision
(899f97fdf54c24936f4bf852d84dba29) and this PR's test file copied in so the unit
baseline collects it -- upstream's comment-only inductor_backend.py needs no
revert, so those are the only two differences between the arms.

Verification

Pre-submission Checklist

  • Linting passed (ruff check, ruff format --check)
  • Type checking passed (if applicable) - N/A, no type checker configured
  • All tests pass (unit + integration)
  • Manual testing completed (include reproduction of original issue)
  • No debug/temporary code (no print statements, commented code, TODOs)
  • Documentation updated (README, CLAUDE.md, docstrings as needed)
  • Commit messages follow conventions (type: description format)
  • All text in English (required per CLAUDE.md)

Linting Results

$ /publi-flash/lvyufeng/env/miniconda3/envs/musa_test/bin/ruff check --config pyproject.toml
All checks passed!

$ /publi-flash/lvyufeng/env/miniconda3/envs/musa_test/bin/ruff format --check --config pyproject.toml
311 files already formatted

(ruff is not installed in the py310-musa environment this was measured in; the
0.15.12 binary above is used at the repository root with the project config.)

Test Results

# Command:
pytest tests/unit/test_cuda_alias_guard.py -v

# Output (including test count):
tests/unit/test_cuda_alias_guard.py::test_the_guard_asks_the_helper_not_the_probe_it_wrote PASSED [ 16%]
tests/unit/test_cuda_alias_guard.py::test_the_helper_reports_what_torch_said_before_the_redirect PASSED [ 33%]
tests/unit/test_cuda_alias_guard.py::test_the_helper_falls_back_when_nothing_redirected PASSED [ 50%]
tests/unit/test_cuda_alias_guard.py::test_the_alias_installs_when_the_probe_was_redirected[1] PASSED [ 66%]
tests/unit/test_cuda_alias_guard.py::test_the_alias_installs_when_the_probe_was_redirected[] PASSED [ 83%]
tests/unit/test_cuda_alias_guard.py::test_the_alias_stays_off_when_it_is_opted_out_of PASSED [100%]
6 passed, 26 warnings in 31.00s

# The same file against the pre-fix tree (torch_fl/__init__.py md5 899f97fd):
6 failed

# Command:
pytest tests/unit -q

# Output (including test count):
FAILED tests/unit/test_flaggems_pointwise_dispatch.py::TestFastKeyMatchesStock::test_same_tuple_for_a_typical_launch
FAILED tests/unit/test_flaggems_pointwise_dispatch.py::TestFastKeyMatchesStock::test_same_tuple_when_nothing_is_specialised
FAILED tests/unit/test_flaggems_pointwise_dispatch.py::TestFastKeyMatchesStock::test_specialization_hook_lands_in_the_key
FAILED tests/unit/test_flaggems_pointwise_dispatch.py::TestFastKeyMatchesStock::test_cached_key_is_built_once_per_entry
FAILED tests/unit/test_flaggems_pointwise_dispatch.py::TestHoistedDescriptorCacheKey::test_stock_agrees_on_non_descriptors
FAILED tests/unit/test_flaggems_pointwise_dispatch.py::TestGate::test_does_not_install_off_dcu
FAILED tests/unit/test_musa_rng_bridge.py::test_flaggems_philox_uses_flagos_reservations
FAILED tests/unit/test_musa_rng_bridge.py::test_flaggems_philox_reaches_vendor_backend_modules
FAILED tests/unit/test_musa_rng_bridge.py::test_flaggems_philox_survives_modules_that_raise_on_getattr
9 failed, 780 passed, 109 skipped, 70 warnings in 67.58s (0:01:07)

# The pre-fix arm, same base and same library, is:
15 failed, 774 passed, 109 skipped, 70 warnings in 45.96s
# whose extra six FAILED lines are exactly:
FAILED tests/unit/test_cuda_alias_guard.py::test_the_guard_asks_the_helper_not_the_probe_it_wrote
FAILED tests/unit/test_cuda_alias_guard.py::test_the_helper_reports_what_torch_said_before_the_redirect
FAILED tests/unit/test_cuda_alias_guard.py::test_the_helper_falls_back_when_nothing_redirected
FAILED tests/unit/test_cuda_alias_guard.py::test_the_alias_installs_when_the_probe_was_redirected[1]
FAILED tests/unit/test_cuda_alias_guard.py::test_the_alias_installs_when_the_probe_was_redirected[]
FAILED tests/unit/test_cuda_alias_guard.py::test_the_alias_stays_off_when_it_is_opted_out_of

--collect-only lists 897 tests on this base against 886 on the previous one, and
all eleven additions are upstream's -- nine in #422's new
tests/unit/test_nccl_extension_fallback.py and two in #423's
tests/unit/test_transformers_automation.py. Both arms here collect all 897, so
the comparison is unaffected; the runs' own totals are one higher than that count
because they also carry the module-level collection skip
tests/unit/bpu/test_qdq.py reports, whose onnx import this environment lacks.
The failing sets differ by exactly the six tests in the new file: 774 + 6 = 780 and
15 - 6 = 9, so no other test moved in either direction, and the nine that remain
are the same nine the previous base's after arm failed, id for id. They are
pre-existing and unrelated: six come from libentry._descriptor_cache_key being
absent in /tmp/FlagGems/src/flag_gems/utils/libentry.py, and three from
test_musa_rng_bridge.py, which pass in isolation and fail only when
test_ascend_platform_marker.py runs first in the same session.
tests/unit/bpu/test_device_alias.py, which also covers the alias, is 6 skipped
on MUSA (its conftest.py skips unless the accelerator is bpu).

Manual Verification

# Command to reproduce original issue (issue #264's own reproducer):
python -c "
import torch
import torch_fl
model = torch.nn.Linear(10, 10).to('flagos')
compiled_model = torch.compile(model, fullgraph=True)
x = torch.randn(1, 10, device='flagos')
print('OK', tuple(compiled_model(x).shape))"

# Output BEFORE fix:
AssertionError: Torch not compiled with CUDA enabled

# Output AFTER fix:
OK (1, 10)
# Command: the shim state after a bare `import torch_fl`
python -c "
import torch, torch_fl
print('alias_active:', torch_fl._cuda_alias_active)
print('torch.device is torch._C.device:', torch.device is torch._C.device)
print('torch.device(\"cuda\"):', torch.device('cuda'))
print('current_device:', torch.cuda.current_device())
print('zeros(2).cuda():', torch.zeros(2).cuda())"

# Output BEFORE fix:
alias_active: False
torch.device is torch._C.device: True
torch.device("cuda"): device(type='cuda')
current_device: AssertionError: Torch not compiled with CUDA enabled
zeros(2).cuda(): AssertionError: Torch not compiled with CUDA enabled

# Output AFTER fix:
alias_active: True
torch.device is torch._C.device: False
torch.device("cuda"): device(type='flagos')
current_device: 0
zeros(2).cuda(): tensor([0., 0.], device='flagos:0')

Both arms kept Triton's driver selection intact (mthreads is_active=True,
driver.active = MusaDriver), so the alias disturbs nothing in the compiler
stack. Note that the OSError: libcuda.so.1 in the issue's report is not
reproducible on this tree; see Related Work.

Breaking Changes

N/A -- Breaking Change is not checked.

Code Quality Verification

Style Consistency

  • Matched existing code style in modified files
  • Followed naming conventions (checked similar code)
  • Comment density matches surrounding code
  • Used project's existing utilities/helpers (no reinventing)

Edge Cases Considered

  1. A native accelerator with no flagos device. _patch_native_cuda_probe()
    returns before redirecting (torch.flagos.is_available() is false), so no
    saved function exists, the helper falls back to the live probe, gets False,
    and the alias installs -- unchanged from before.
  2. A CUDA or boxing build. The patch returns before redirecting anything, the
    helper reports the stock probe as True, and the alias stays off. The boxing
    path submits real CUDA work, which is why the alias must not hijack cuda
    there.
  3. FLAGOS_ALIAS_CUDA=0. Still honoured; asserted in the child-process test.
  4. An earlier torch.cuda.is_available writer. The helper reads
    torch.cuda._flagos_original_is_available if present, so a redirect from any
    source is seen, not just this one.
  5. Repeated calls. The alias runs once per process; nothing in the helper
    mutates state.

Potential Risks

  1. The alias now installs on MUSA where it did not before, which is the point,
    but it is a behaviour change beyond the compile path: code that used to fail
    loudly on device="cuda" now silently lands on flagos, and torch.device is
    no longer torch._C.device. Measured on MUSA: the full unit suite and the
    FlagGems overload survey both show no new failure (see below); FLAGOS_ALIAS_CUDA=0
    restores the old behaviour for anyone who needs the loud failure.
  2. The saved function could go stale if torch_fl ever redirects
    torch.cuda.is_available at a second site without saving. The helper's fallback
    is the live probe, so a second writer without a save would reproduce the old
    bug rather than a new one, and the ast test would not catch it.

Rollback Plan

Revert the commit. The change is three lines of behaviour (__init__.py) plus a
comment-only edit and a new test file; no conf, kernel or route is involved, and
the pre-fix state is reachable without a revert by setting FLAGOS_ALIAS_CUDA=0.

Related Work

  • Fixes torch.compile Triton backend requires libcuda.so even on flagos device #264 (torch.compile on MUSA), whose own reproducer this change repairs.
    Its diagnosis does not hold up on this tree: the OSError: libcuda.so.1 it
    reports was taken at 2e64a8d and no longer reproduces, the reproducer now
    dying earlier in dynamo with AssertionError: Torch not compiled with CUDA enabled, and neither proposed fix (skip the test, or write a MUSA Triton
    backend) addresses that -- the measured cause is the guard above, and a comment
    on the thread corrects it.
  • Related to refactor: unify platform detection and document the capability matrix #405 (platform detection / capability matrix).
  • Related to fix: keep the FlagGems RNG bridge installed when a swept module raises #406, whose survey evidence gap on MUSA this change closes.
  • Related to torch.compile Triton backend requires libcuda.so even on flagos device #264's second affected test, which is not repaired here and is
    left to a follow-up: measured on the harness, Qwen3ModelTest::test_generate_compile_model_forward_fullgraph
    fails before and after, but for different reasons -- AssertionError: Torch not compiled with CUDA enabled pre-fix, and post-fix RuntimeError: Could not find an active GPU backend from torch._inductor.runtime.triton_helpers.set_driver_to_gpu()
    in an Inductor compile worker. Triton's mthreads driver reports inactive in
    that worker because it is a fresh interpreter that neither imports torch_fl
    nor -- under the harness's TORCH_DEVICE_BACKEND_AUTOLOAD=0 -- autoloads
    torch_musa, so hasattr(torch, "musa") is false there. The sibling test
    test_generate_compilation_all_outputs does go FAIL -> PASS with this fix
    once the harness shims are out of the way (HF_TEST_NO_DEVICE_SHIMS=1).
  • The branch was rebased onto f84eb66 after opening, so #410 (compressed
    sparse on SparseCsrPrivateUse1), #414 (HF harness device shims), #415
    (bool neg), #416 (generated CUPTI cbid table), #422 (report the missing
    _flagos_nccl extension instead of a NoneType AttributeError) and #423
    (GCU new_ones from a generated kernel, plus the HF harness's nodeid repair)
    are now below it, as is the CI-only #420. None of them interacts with the
    guard; all are reflected in the re-taken measurements below.

Explicitly Not Included

  • The eight fp16 SDPA numeric failures on MUSA
    (test_eager_matches_sdpa_inference_0{0..7}_fp16_*, four nan, four ~1e-5).
    Unchanged by this PR and still a separate open defect with no issue filed.
  • Hardware other than MUSA. No other accelerator was available to this change;
    their operator-support rows are carried over unchanged and are not
    revalidated
    .
  • torch_fl/compile/device_interface.py's two docstrings that attribute the
    probe redirect to the alias rather than to _patch_native_cuda_probe(). The
    attribution is imprecise both before and after this change; touched only for
    accuracy if the reviewer asks.

Human Review Notes

Areas needing special attention:

  1. The guard now reads torch.cuda._flagos_original_is_available, a private
    attribute written by another module -- is that the right coupling, or would you
    rather _patch_native_cuda_probe() expose a public predicate?
  2. The behavioural widening on MUSA (alias active by default): confirm you agree
    the alias should be on for native accelerators, not just opted into.
  3. The new test file's subprocess tests need a working accelerator to be
    meaningful; on a CPU-only CI host they reduce to the three in-process tests.

Questions for reviewer:

  1. Should docs/reference/operator-support.md record the survey runtime increase
    (the after-arm survey is slower, since every overload now syncs through
    torch.flagos.synchronize), or is that out of scope for the report?
  2. Is a separate issue wanted for the eight fp16 failures, or should they stay
    attached to torch.compile Triton backend requires libcuda.so even on flagos device #264's thread?

Additional Context

Measured A/B, MUSA MTT S5000, one process per arm

torch_fl/__init__.py md5 899f97fdf54c24936f4bf852d84dba29 (before) against
9f941ef7f0899efa8dc73202b03348b4 (after).

Probe, after a bare import torch_fl before after
torch_fl._cuda_alias_active False True
torch.cuda.current_device is torch.flagos.current_device False True
torch.cuda.synchronize is torch.flagos.synchronize False True
torch.cuda.device_count is torch.flagos.device_count False True
torch.cuda.get_device_properties is torch.flagos.get_device_properties False True
torch.device is torch._C.device True False
torch.device('cuda') device(type='cuda') device(type='flagos')
torch.cuda.current_device() AssertionError 0
torch.randn(2, 2, device='cuda') AssertionError flagos:0 tensor
torch.zeros(2).cuda() AssertionError flagos:0 tensor
issue #264's reproducer AssertionError OK (1, 10)
torch.cuda.is_available() True True
Triton driver.active MusaDriver MusaDriver

Harness A/B (`tests/manual/transformers_hf_tests.py --model qwen3 --offline

--pytest-arg=-k --pytest-arg=test_eager_matches_sdpa_inference`)

                                            before                      after
default (device shims on)      PASS=24  SKIP_OTHER=1        PASS=24  SKIP_OTHER=1
HF_TEST_NO_DEVICE_SHIMS=1      FAIL=8  PASS=16  SKIP_OTHER=1  FAIL=8  PASS=16  SKIP_OTHER=1

The FAIL id sets are byte-identical between the two arms, and the eight are the
fp16 SDPA defect listed under "Explicitly Not Included". The default mode no longer
discriminates: #414, newly below this branch, gave the harness device shims that
make it pass on either arm, so HF_TEST_NO_DEVICE_SHIMS=1 is the mode that still
reaches the shimmed probe. Neither mode is where this change shows -- the
reproducer and the alias-state probe are.

Survey A/B (full MUSA cohort, flaggems_overload_survey.py v6)

Conf SHA-256 87d150533c73e4ca40a24c2588aed51387d257044290d1dd85e8cc9a9d40ffad,
unchanged by this PR. Both arms are on f84eb66, the current upstream head; the
before arm carries the measurement-only shim the docs section above records and the
after arm ran the survey unmodified. Both arms ran against the extension rebuilt
from that source.

before: registered 467, tested 388, STRICT 303, BASIC_ONLY 47, FAILED 38, UNTESTED 79
after:  registered 467, tested 388, STRICT 303, BASIC_ONLY 47, FAILED 38, UNTESTED 79

All 467 overloads present in both arms, no verdict differing -- and the two runs
agree below the verdict too: every route carries the same status on every profile,
so all 3269 cells agree, and all 467 per-route running totals print identically,
[107/467] clamp_.Tensor strict=52 basic_only=11 failed=6 untested=38 and
[169/467] flip strict=99 basic_only=16 failed=10 untested=44 included. That is
also what makes the run comparable despite only one arm carrying the shim.

At case level this pair moved nothing: both arms' census reads PASS 1881, INVALID_CASE 1086, ERROR 151, WRONG 132, CRASH 14, TIMEOUT 5. The pairs recorded
for the two previous bases each moved exactly one cell, always a 2d-f32 profile of
one of the two index_copy overloads, inside that overload's unchanged FAILED
verdict, and in opposite directions: index_copy_ PASS -> WRONG on the
pre-rebase base, index_copy WRONG -> PASS on 892432b. Across all six runs the
census takes exactly two values, PASS 1881 / WRONG 132 and PASS 1882 / WRONG 131, with INVALID_CASE 1086, ERROR 151, CRASH 14 and TIMEOUT 5 identical in
every one, so the only quantity that moves is which of those two profiles sits in
PASS. Six isolated re-runs of each overload on the post-fix tree with nothing else
changed give index_copy PASS four times and WRONG twice and index_copy_
WRONG five times and PASS once, with max_diff between 1.9 and 4.6 whenever the
comparison does fail, so the case is unstable run to run rather than fixed or broken
by the route: index_copy's synthesized index argument is randint(0, 2, ...), so
duplicate indices make the comparison order-dependent. That is a harness artifact,
not a route or kernel effect.

docs/reference/operator-support.md gains the full section and Update History row
with the same figures.

Fixes #264


🤖 Generated with Claude Code

@lvyufeng

Copy link
Copy Markdown
Collaborator Author

Branch rebased and re-measured on 17b5d30

Upstream moved from 877afb1 to 17b5d30 (#413, #414, #415) while this
change was being measured, so the branch was rebased and every number below was
re-taken on the new base. The tip is 2ed6119 (force-pushed to the fork; this PR
is no longer conflicting) and the PR body now records that run.

#415 edits csrc/aten/common.cc and csrc/aten/dispatcher.h, so the extension
was rebuilt from the rebased source with FLAGOS_ACCELERATOR=musa FLAGOS_BUILD_VENDOR=1 FLAGOS_BUILD_FLAGGEMS=1 FLAGOS_BUILD_FLAGGEMS_CPP=0 python setup.py build_ext --inplace, leaving torch_fl/lib/libtorch_fl.so at md5
65020516c7bab18e96ea6f903d6ee3ff on both arms. The pre-fix arm is upstream
17b5d30 with torch_fl/__init__.py at the pre-fix revision
(899f97fdf54c24936f4bf852d84dba29 against 9f941ef7f0899efa8dc73202b03348b4)
and this PR's test file copied in so the unit baseline collects it.

What changed in the record

The unit A/B published initially understated the pre-fix arm: the earlier
9 failed, 724 passed baseline had not collected the new test file at all
(grep -c test_cuda_alias_guard was 0 in that log), so the claim that the six
extra passes were the new file was unsupported. Rebuilt and re-measured:

                        failures  passed  skipped   collected
after  (this branch)           9     752      109         870
before (pre-fix __init__.py)  15     746      109         870

The two arms collect the same 870 tests and their failing sets differ by exactly
the six tests in tests/unit/test_cuda_alias_guard.py (746 + 6 = 752,
15 - 6 = 9). The 9 that remain fail on the unmodified base as well: 6 in
tests/unit/test_flaggems_pointwise_dispatch.py (libentry._descriptor_cache_key
missing) and 3 in tests/unit/test_musa_rng_bridge.py.

The harness A/B was also re-run in both modes, because this PR now sits on #414,
whose device shims make the default mode stop discriminating:

                                        before                      after
default (device shims on)      PASS=24  SKIP_OTHER=1        PASS=24  SKIP_OTHER=1
HF_TEST_NO_DEVICE_SHIMS=1      FAIL=8  PASS=16  SKIP_OTHER=1  FAIL=8  PASS=16  SKIP_OTHER=1

The 8 FAIL ids are byte-identical between the arms in HF_TEST_NO_DEVICE_SHIMS=1
mode, i.e. the fp16 SDPA divergence these tests carry is independent of this fix.

The 467-overload MUSA survey was re-run on both arms: identical summaries, zero
verdict differences, and a case census byte-identical to the pre-rebase run. One
caveat worth stating plainly: a single index_copy overload's 2d-f32 case is
not stable across runs on this box. Six isolated re-runs of index_copy return
PASS twice and WRONG four times (max_diff 2.6-5.2), and six of index_copy_
return PASS once and WRONG five times, so the one case that moved between the
arms is that instability, not a route change. The operator-support record in this
PR states it that way rather than as a verdict flip.

The commit message was amended to carry the corrected unit figures, and the
docs/reference/operator-support.md provenance now cites the branch name instead
of a self-referential commit SHA (an amend changes it).

@lvyufeng
lvyufeng force-pushed the fix/cuda-alias-guard-reads-real-cuda branch 2 times, most recently from db4a578 to 5112740 Compare September 25, 2026 21:39
`_alias_cuda_to_flagos()` decided whether to install by asking
`torch.cuda.is_available()`. That probe has two writers in this process and
torch_fl is one of them: importing `flag_gems` during `_phase_vendor_compat()`
reaches `flagos._lazy_init()` and then `torch_fl.compile`, whose module-level
`_patch_native_cuda_probe()` repoints the probe at `torch.flagos.is_available`
and saves the function it replaced as `torch.cuda._flagos_original_is_available`.
That happens a whole phase before `_phase_ecosystem()` calls the alias, so the
guard read the redirect, saw `True` on a build with no CUDA runtime, returned
early, and left the six `torch.cuda.*` entries the alias owns as stock CPU
torch's with `_cuda_alias_active` `False`. Dynamo's `cuda_extra_check` then
called `torch.cuda.current_device()` and raised `AssertionError: Torch not
compiled with CUDA enabled`, so `torch.compile(model, fullgraph=True)` on a
`flagos` module could not compile -- issue flagos-ai#264.

The saved original was written and read by nothing. `_real_cuda_is_available()`
gives it a reader, falling back to the live probe when nothing redirected it,
and the guard calls that.

Measured on MUSA MTT S5000, both arms on upstream `f84eb66`: `_cuda_alias_active`
False -> True; the four aliased
`torch.cuda.*` entries False -> True; `torch.device('cuda')`
`device(type='cuda')` -> `device(type='flagos')`; `torch.zeros(2).cuda()` and
`torch.cuda.current_device()` `AssertionError` -> ok; flagos-ai#264's own reproducer
`AssertionError` -> `OK (1, 10)`. No conf, kernel or route changes: the full
467-overload MUSA survey reports the same summary with no verdict difference,
and the alias stays off under `FLAGOS_ALIAS_CUDA=0`.

Tested: pytest tests/unit/test_cuda_alias_guard.py (6 failed pre-fix, 6 passed
after); pytest tests/unit -q -> 9 failed, 780 passed, 109 skipped against a
15 failed, 774 passed, 109 skipped pre-fix baseline, the two differing by
exactly the new file's six tests over the same 897 collected.

Fixes flagos-ai#264

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lvyufeng
lvyufeng force-pushed the fix/cuda-alias-guard-reads-real-cuda branch from 5112740 to eff8a95 Compare September 26, 2026 08:47
@lvyufeng
lvyufeng merged commit 48829b8 into flagos-ai:main Sep 26, 2026
19 checks passed
lvyufeng added a commit that referenced this pull request Sep 26, 2026
Both reference documents carry claims that stopped being true when the
issues behind them closed, and neither had a record of what replaced them.

`docs/reference/hf-coverage.md` lists five tracked causes from the
2026-09-02 MUSA baseline and still presents all five as open work. Four are
closed: #250/#265 by PR #280 and #282, #262/#268 by PR #398, #266 by PR
#278, #264 by PR #419. Only #263 is still open. A new "What has changed
since this baseline" section records each closure with the issue and the
commit that landed, and states what was actually re-measured: the eight
SDPA nodeids #268 borrowed, with `{"PASS": 8}` under the harness's device
shims and an `allclose` residual without them that belongs with #248. The
baseline's own `Affected tests` counts are deliberately left alone — they
are dated measurements of `64e60dd`, and rewriting them against a later
tree would stop them matching the run they came from, which is what the
dedup parser reads them for.

`docs/reference/operator-support.md` records the 2026-08-30 cohort as
excluding "the known float64 `mm` gap". PR #275 moved `mm` and `mm.out`
onto the FlagGems route on 2026-09-15, so that exclusion is historical; the
later entries in the report count the op. A paragraph after the row says so
and names what replaced the mudnn capability gap — a different defect on
the route that now serves the op, the FlagGems fp64 tile exceeding the
device's 192 KiB of shared memory past `M > 32` and `N > 32`, filed as
#428.

Documentation only; no code, no routing and no test changes.

Verified: `ruff check .` -> All checks passed; `ruff format --check .` ->
311 files already formatted; `pytest tests/unit/test_transformers_automation.py -q`
-> 61 passed; `pytest tests/unit/ -q` -> 6 failed, 761 passed, 131 skipped,
and the same six fail identically with these edits stashed, so they
pre-exist on d5f82ae. `transformers_deduplicate.extract_baseline_fingerprints`
still reads the edited coverage doc without error.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

torch.compile Triton backend requires libcuda.so even on flagos device

1 participant