Skip to content

feat(gcu): run FlagGems Triton kernels on Enflame GCU - #65

Merged
zhaoyinglia merged 1 commit into
flagos-ai:mainfrom
lvyufeng:feat/flaggems-on-gcu
Aug 7, 2026
Merged

feat(gcu): run FlagGems Triton kernels on Enflame GCU#65
zhaoyinglia merged 1 commit into
flagos-ai:mainfrom
lvyufeng:feat/flaggems-on-gcu

Conversation

@lvyufeng

@lvyufeng lvyufeng commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Enflame GCU has no C++ FlagGems path — there is no liboperators.so for the tops
stack, so FLAGGEMS_KERNEL/FLAGGEMS_PYTHON stay off and the backend was on
topsaten kernels plus cpu_fallback only. This registers FlagGems' Triton
kernels through the Python layer instead, calling flag_gems.enable() straight
onto PrivateUse1: 666 aten ops, 244 of them the vendor's own gcu300
overrides, 422 generic.

The kernels reach the device through Enflame's triton_gcu plugin, which was
written against the vendor's torch_gcu plugin and names PrivateUse1 "gcu".
torch_fl claims PrivateUse1 first as "flagos", and a process can rename it only
once, so torch_fl/accelerator/gcu/_gcu_compat.py redirects the vendor backend.

What needed shimming

Device naming — the name in triton_gcu's toolkit/backend/driver, plus
FlagGems' own enflame descriptor (patched on VendorDescriptor before import,
since DeviceDetector is a singleton that copies the value out at construction).

Timing — a wall-clock stand-in Event for do_bench. flagos.Event derives
from torch.cuda.Event, which is a dummy that raises on instantiation on a
CPU-only torch wheel. Autotuning only ranks candidates against each other, so
relative wall-clock timings pick the same winner.

Two silent-miscall mismatches, each because a FlagGems op calls something
other than what it looks like:

  • bind_vendor_ops_in_generic_modules — generic ops import sub-ops by value at
    module scope (linear_backward does from .mm import mm), so the binding is
    fixed at import time and survives the vendor override. The generic
    mm_kernel_general widens its index arithmetic to int64, which the GCU backend
    refuses to legalize (failed to legalize operation 'arith.extsi') — so every
    backward through nn.Linear failed to compile while a direct torch.mm on the
    same shapes worked.
  • device_guarded_config — FlagGems allocates intermediates on input.device
    but launches Triton on the current device. This is the
    c10::OptionalDeviceGuard that python_op_caller.cc applies, in Python, at the
    one place ops registered straight onto PrivateUse1 go through. It also diverts
    all-int64 arithmetic to the CPU kernel (int64 indices alongside float data are
    fine and stay on device).

Multi-device

Two device-0 assumptions in triton_gcu had to go, both of which aborted the
process rather than raising:

  1. The autotuner's L2-flush buffer was allocated with a bare device='gcu', so
    it landed on whatever device was current at allocation. do_bench then calls
    cache.zero_() between timing runs, so autotuning on flagos:1 while
    flagos:0 was current had zero_ writing device-0 memory from a device-1
    context. The tops runtime accepts that; the SIP faults asynchronously and the
    process aborts at the next synchronization (Receive Sip error message),
    several ops after the cause.
  2. Setting COMPILE_ARCH takes a GCUDriver.__init__ branch that hardcodes
    get_current_device = lambda: 0 as an instance attribute, which shadows
    any class-level patch — and _GCUDriver.__init__ re-runs on every call, so it
    would be reinstated. Both constructors are wrapped to drop it.

Excluded ops

_GCU_EXCLUDED_OPS lists ops whose FlagGems kernel the GCU cannot compile or
compiles wrongly. Each was verified individually on hardware, each is left to
reach topsaten or cpu_fallback, and the inline comments record the specific
defect. Notable ones:

  • remainder/fmod/floor_divide — Triton's float % on GCU returns x
    instead of 0 when y divides x exactly, for ~10% of random lanes, silently.
    Non-multiple operands are correct, which is why this needs an exact-multiple
    probe to see.
  • the conv family — every forward is numerically correct, but the VJP passes a
    stride of 0 as a runtime arg and the GCU asserts inside the kernel. That is a
    SIP assert, so it aborts rather than raising and cannot be fallen back from.
  • fill_ — corrupts int64 tensors silently (fill_(42) returns
    -4846589848703729622). Writes through its operand, so the int64 CPU diversion
    cannot rescue it.
  • sort/sort_stable/msort/stack/var/diff and the layernorm/embedding
    backward kernels — the same illegal int64 widening; the matching forwards are
    correct and stay on FlagGems.

_flaggems_exclusion_names() translates aten names into what the exclusion filter
actually matches. flag_gems.enable(unused=...) looks like it takes aten op names
but compares the implementing function __name__, so "normal.Tensor_float"
excludes nothing (the function is normal_tensor_float) and the op gets registered
anyway. This is what hid sort.stable (function sort_stable) until a
registration audit caught it.

Optional by construction

When triton_gcu or its /opt/triton_gcu toolchain is absent,
is_triton_gcu_available() returns False, registration is skipped, and the build
behaves exactly as before. A registration failure warns rather than breaking
import torch_fl — the topsaten kernels and cpu_fallback are a complete,
correct path.

Test plan

Rebased onto flagos/main (11457c9) and rebuilt the extension against the four
new upstream commits.

  • Full suite: 725 passed, 33 failed, 288 skipped, 4 xpassed, 11 errors
    byte-identical with FlagGems on and off, so every failure is pre-existing
    (inductor test_compile.py, conv family, RNG generator tests, missing
    transformers, tests/manual/metax needing _cuda_setDevice). The 8 compile
    failures were confirmed to reproduce with FlagGems disabled, not assumed.
  • Multi-device: backward through a matmul on flagos:0 and flagos:1, finite
    grads, no Sip error. The previously-aborting selection now passes 3/3 runs.
  • Lint: ruff check . and ruff format --check . clean with the pinned
    ruff==0.15.12.

Verified on gcu300, single- and multi-device.

Enflame GCU has no C++ FlagGems path (there is no liboperators.so for the
tops stack, so FLAGGEMS_KERNEL/FLAGGEMS_PYTHON stay off), which left it on
topsaten kernels and cpu_fallback only. This registers FlagGems' Triton
kernels through the Python layer instead, calling flag_gems.enable()
straight onto PrivateUse1: 666 aten ops, 244 of them the vendor's own
gcu300 overrides.

The kernels reach the device through Enflame's triton_gcu plugin, which was
written against the vendor's torch_gcu plugin and names PrivateUse1 "gcu".
torch_fl claims PrivateUse1 first as "flagos", and a process can rename it
only once, so torch_fl/accelerator/gcu/_gcu_compat.py redirects the vendor
backend: the device name in its toolkit/backend/driver, a wall-clock timing
Event for do_bench (flagos.Event derives from torch.cuda.Event, a dummy on
a CPU-only wheel), and the two hardcoded device-0 lookups that made a
kernel on flagos:1 launch against device 0.

Two mismatches need correcting on the FlagGems side because an op silently
calls something other than what it looks like:

  * bind_vendor_ops_in_generic_modules -- generic ops import sub-ops by
    value at module scope (linear_backward does `from .mm import mm`), so
    the binding survives the vendor override. The generic mm widens its
    indices to int64, which the GCU backend refuses to legalize, so every
    backward through nn.Linear failed to compile while torch.mm worked.

  * device_guarded_config -- FlagGems allocates intermediates on
    input.device but launches Triton on the current device. This is the
    c10::OptionalDeviceGuard that python_op_caller.cc applies, in Python,
    at the one place these ops go through. It also diverts all-int64
    arithmetic to the CPU kernel, since the tops stack has no int64.

_GCU_EXCLUDED_OPS lists the ops whose FlagGems kernel the GCU cannot
compile or compiles wrongly, each verified individually on hardware and
each left to reach topsaten or cpu_fallback; the comments record what the
defect is. The conv family is excluded even though every forward is
correct, because its VJP trips a SIP assert that aborts the process
instead of raising.

_flaggems_exclusion_names() translates aten names into what the exclusion
filter actually matches -- flag_gems.enable(unused=...) compares the
*implementing function* name, so "normal.Tensor_float" excludes nothing
(the function is normal_tensor_float) and the op gets registered anyway.

The whole path is optional: when triton_gcu or its /opt/triton_gcu
toolchain is absent, registration is skipped and the build behaves exactly
as before. A registration failure warns rather than breaking `import
torch_fl`.

Test suite is at parity with FlagGems on and off: 521 passed, 22 failed,
9 errors (conv/RNG/inductor/transformers, all pre-existing and identical
in both runs). Verified on gcu300, single- and multi-device.
@zhaoyinglia
zhaoyinglia merged commit 3c6529a into flagos-ai:main Aug 7, 2026
15 of 16 checks passed
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