feat(gcu): run FlagGems Triton kernels on Enflame GCU - #65
Merged
Conversation
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
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Enflame GCU has no C++ FlagGems path — there is no
liboperators.sofor the topsstack, so
FLAGGEMS_KERNEL/FLAGGEMS_PYTHONstay off and the backend was ontopsaten kernels plus
cpu_fallbackonly. This registers FlagGems' Tritonkernels through the Python layer instead, calling
flag_gems.enable()straightonto PrivateUse1: 666 aten ops, 244 of them the vendor's own gcu300
overrides, 422 generic.
The kernels reach the device through Enflame's
triton_gcuplugin, which waswritten against the vendor's
torch_gcuplugin and names PrivateUse1"gcu".torch_fl claims PrivateUse1 first as
"flagos", and a process can rename it onlyonce, so
torch_fl/accelerator/gcu/_gcu_compat.pyredirects the vendor backend.What needed shimming
Device naming — the name in
triton_gcu's toolkit/backend/driver, plusFlagGems' own enflame descriptor (patched on
VendorDescriptorbefore import,since
DeviceDetectoris a singleton that copies the value out at construction).Timing — a wall-clock stand-in Event for
do_bench.flagos.Eventderivesfrom
torch.cuda.Event, which is a dummy that raises on instantiation on aCPU-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 atmodule scope (
linear_backwarddoesfrom .mm import mm), so the binding isfixed at import time and survives the vendor override. The generic
mm_kernel_generalwidens its index arithmetic to int64, which the GCU backendrefuses to legalize (
failed to legalize operation 'arith.extsi') — so everybackward through
nn.Linearfailed to compile while a directtorch.mmon thesame shapes worked.
device_guarded_config— FlagGems allocates intermediates oninput.devicebut launches Triton on the current device. This is the
c10::OptionalDeviceGuardthatpython_op_caller.ccapplies, in Python, at theone 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_gcuhad to go, both of which aborted theprocess rather than raising:
device='gcu', soit landed on whatever device was current at allocation.
do_benchthen callscache.zero_()between timing runs, so autotuning onflagos:1whileflagos:0was current hadzero_writing device-0 memory from a device-1context. 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.
COMPILE_ARCHtakes aGCUDriver.__init__branch that hardcodesget_current_device = lambda: 0as an instance attribute, which shadowsany class-level patch — and
_GCUDriver.__init__re-runs on every call, so itwould be reinstated. Both constructors are wrapped to drop it.
Excluded ops
_GCU_EXCLUDED_OPSlists ops whose FlagGems kernel the GCU cannot compile orcompiles wrongly. Each was verified individually on hardware, each is left to
reach topsaten or
cpu_fallback, and the inline comments record the specificdefect. Notable ones:
remainder/fmod/floor_divide— Triton's float%on GCU returnsxinstead of
0whenydividesxexactly, for ~10% of random lanes, silently.Non-multiple operands are correct, which is why this needs an exact-multiple
probe to see.
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 diversioncannot rescue it.
sort/sort_stable/msort/stack/var/diffand the layernorm/embeddingbackward 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 filteractually matches.
flag_gems.enable(unused=...)looks like it takes aten op namesbut compares the implementing function
__name__, so"normal.Tensor_float"excludes nothing (the function is
normal_tensor_float) and the op gets registeredanyway. This is what hid
sort.stable(functionsort_stable) until aregistration audit caught it.
Optional by construction
When
triton_gcuor its/opt/triton_gcutoolchain is absent,is_triton_gcu_available()returns False, registration is skipped, and the buildbehaves exactly as before. A registration failure warns rather than breaking
import torch_fl— the topsaten kernels andcpu_fallbackare a complete,correct path.
Test plan
Rebased onto
flagos/main(11457c9) and rebuilt the extension against the fournew upstream commits.
byte-identical with FlagGems on and off, so every failure is pre-existing
(inductor
test_compile.py, conv family, RNG generator tests, missingtransformers,tests/manual/metaxneeding_cuda_setDevice). The 8 compilefailures were confirmed to reproduce with FlagGems disabled, not assumed.
flagos:0andflagos:1, finitegrads, no Sip error. The previously-aborting selection now passes 3/3 runs.
ruff check .andruff format --check .clean with the pinnedruff==0.15.12.Verified on gcu300, single- and multi-device.