Repository navigation
Expand file tree
/
Copy pathgen_vendor_confs.py
More file actions
3091 lines (2973 loc) · 168 KB
/
Copy pathgen_vendor_confs.py
File metadata and controls
3091 lines (2973 loc) · 168 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# Copyright 2026 FlagOS Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Generate one full-coverage backend conf per vendor.
Every vendor conf lists the *same* op set -- the authoritative list of ops
torch_fl generates wrappers for -- so operator support is countable by reading
one file per platform instead of diffing sparse configs against each other.
Each op maps to exactly one of five keys:
flaggems_cpp FlagGems C++ path (liboperators.so), Backend::kFlagGemsCpp
flaggems FlagGems Python/Triton path, Backend::kFlagGems
tileops TileOps Python shim path, Backend::kTileOps
<vendor> vendor-native kernel (musa, ascend, gcu, metax, ...)
none no accelerated impl on this platform -> cpu_fallback
Routing priority is FlagGems-first, tileops as an intermediate Python path,
vendor-native as the fallback for what neither covers, and `none` for ops
with no accelerated implementation:
flaggems_cpp > flaggems > tileops > <vendor> > none
`flaggems_cpp` is only emitted for the confs a FLAGOS_BUILD_FLAGGEMS=ON build selects
(see FLAGGEMS_CPP_PLATFORMS); everywhere else those ops take the Python path to
the same kernels, because the C++ dispatcher slot is not compiled in.
`tileops` routes to Backend::kTileOps, which calls into
torch_fl.tileops.generated.shims via CallPythonOp_Generic. The 60 ops that
have a TileOps shim implementation are inlined in TILEOPS_OPS below.
`none` is recorded explicitly rather than by omission. That is the whole point
of the full-coverage shape: "absent from the file" and "known to be
unsupported" used to look identical, which made support impossible to count
and let sparse confs rot silently against a growing codegen. Omission is not
even a safe way to say "unsupported" -- GetBackendForOp() returns kFlagGemsCpp on a
table miss, so an unlisted op claims a FlagGems kernel by default.
What the file may claim is bounded by what the platform registers on
PrivateUse1. FlagGems coverage is measured on CUDA and is only a ceiling: an op
this platform does not register cannot reach any kernel, FlagGems included, so
it is written `none`. Registration sets are read from the generated
`*_register.inc` files that csrc/aten/register.cc includes -- the same list the
compiler sees -- which is why only platforms whose registration is a subset of
the op list get a generated conf. See VENDORS.
Usage:
python3 scripts/codegen/gen_vendor_confs.py # rewrite vendor confs
python3 scripts/codegen/gen_vendor_confs.py --check # exit 1 if any is stale
python3 scripts/codegen/gen_vendor_confs.py --stats # print a coverage table
"""
import argparse
import re
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
CONF_DIR = REPO_ROOT / "torch_fl/configs"
# tests/unit/test_gen_vendor_confs.py loads this file by path (scripts/ is not an
# importable package), so a bare `import backend_coverage` would not resolve.
sys.path.insert(0, str(Path(__file__).resolve().parent))
# The op list every vendor conf must cover: what the build can route at all, read
# from the generated registration list.
#
# This used to name backends_cuda.conf, which codegen_ops.py rewrites with one
# line per generated wrapper, so it agreed with registration by construction --
# but it made every platform's op universe a function of the *CUDA platform's*
# routing table. PPU reads a different conf precisely because the two disagree
# about routing, so deriving PPU's universe from CUDA's conf meant a CUDA-side
# edit that added or dropped an op line resized backends_ppu.conf without
# touching PPU, and PPU overriding a value in a file it also enumerates from is
# the round trip this script avoids everywhere else.
#
# generated/register.inc is the artifact the CUDA line and the PPU line both
# compile (the `#else` branch of csrc/aten/register.cc), which is the same reason
# the vendor kernel sets below are read from the generated C++ rather than from a
# conf.
GENERATED_REGISTER_INC = REPO_ROOT / "csrc/aten/generated/register.inc"
# Where the backend coverage sets come from. These used to be three shared conf
# files (backends_flaggems.conf, backends_flaggems_cpp.conf, backends_tileops.conf)
# that torch_fl/configs/ carried purely as generator input -- they routed nothing
# on their own and no platform selected them. They are now data in
# scripts/codegen/backend_coverage.py, so torch_fl/configs/ holds exactly one conf per
# platform and nothing else.
from backend_coverage import ( # noqa: E402
FLAGGEMS_CPP_OPS,
FLAGGEMS_PYTHON_OPS,
FLAGGEMS_HOLDS,
FLAGGEMS_PYTHON_OPS_FROZEN,
TILEOPS_OPS,
)
# Vendors that get a full-coverage conf.
#
# Only a platform whose PrivateUse1 registration is a *subset* of the op list
# belongs here, because that is what makes `none` honest. Claiming an op on
# PrivateUse1 with no kernel behind it does not fall back -- the call reaches the
# dispatcher and raises on the empty slot -- whereas leaving it unregistered lets
# it box to cpu_fallback. So `none` means cpu_fallback only where registration
# skips the op, and these three platforms are the ones that skip:
#
# platform registration branch in csrc/aten/register.cc m.impl count
# musa musa_register.inc + musa_flaggems_register.inc 517
# gcu gcu_register.inc + gcu_flaggems_register.inc 401
# ascend ascend_register.inc 372
#
# metax, tsingmicro and dcu fall through to the `#else` branch and register the
# full generated list (~2034), so a `none` entry there would be a guaranteed
# raise rather than a fallback. metax and dcu are handled as CUDA-boxing
# platforms below (see BOXING_PLATFORMS); backends_tsingmicro.conf stays
# hand-written.
#
# vendor -> (conf naming its native kernels, other .inc files it registers)
#
# The sets are read from the generated C++ rather than from the conf this script
# rewrites, so there is no round trip to survive: registration and routing are
# derived from the same artifact the compiler sees.
VENDORS = {
"musa": (
"backends/musa/generated/musa_register.inc",
("backends/musa/generated/musa_flaggems_register.inc",),
),
"gcu": (
"backends/gcu/generated/gcu_register.inc",
("backends/gcu/generated/gcu_flaggems_register.inc",),
),
"ascend": ("backends/ascend/generated/ascend_register.inc", ()),
# Kunlunxin has no FlagGems .inc to fold in yet: the vendor torch shim on
# this SDK hardcodes the FlagGems runtime device name to "cuda" while the
# tensors dispatch on PrivateUse1, so flag_gems' own gate always falls
# through and every FlagGems Python route dies at its first op. Until that
# is fixed (or the SDK ships a PrivateUse1-named runtime), kunlunxin has a
# single native .inc and no extra registration file.
"kunlunxin": ("backends/kunlunxin/generated/kunlunxin_register.inc", ()),
# Apple Silicon (Metal). Like kunlunxin this is a native backend with a
# single registration .inc and no FlagGems file to fold in: no Metal
# FlagTree/Triton line exists, so backends_mps.conf routes the ops
# backends/mps/generated/mps_register.inc claims to the `mps` kernel and
# everything else to `none` (cpu_fallback). After stage 2 that is the
# `unary`, `binary`, `binary_scalar` and `reduce` categories; see
# scripts/codegen/codegen_mps.py.
"mps": ("backends/mps/generated/mps_register.inc", ()),
# Iluvatar is still FlagGems-primary, but it now has a native (non-boxing)
# .inc for the first cohort of corex ixdnn/ixblas kernels, so the native slot
# is no longer empty. backends_iluvatar.conf routes an op to `flaggems` where
# FlagGems covers it, to `iluvatar` where only the vendor kernel does, and to
# `none` otherwise (cpu_fallback). The two .inc files are included into the
# same PrivateUse1 TORCH_LIBRARY_IMPL body, so they must be disjoint -- the
# generator (scripts/codegen/codegen_iluvatar.py) intersects its claims
# against the FlagGems .inc for exactly that reason.
"iluvatar": (
"backends/iluvatar/generated/iluvatar_register.inc",
("backends/iluvatar/generated/iluvatar_flaggems_register.inc",),
),
}
# Ops a vendor registers straight from register.cc instead of its .inc, so they
# are invisible to the .inc scan. Ascend claims aten::matmul (and the
# matmul_backward the derivative then binds to) on PrivateUse1 under
# `#if defined(USE_ASCEND)` so the call hits the fused aclnnMatmul rather than
# decomposing into mm/bmm/view; WrapperMatmul reads this conf to decide, so the
# entry has to be here or the fused kernel is unreachable.
#
# Iluvatar claims aten::_conj the same way, for a different reason: it is a view
# operator, so its `none` route cannot be served by cpu_fallback (ATen refuses a
# fallback for views -- measured on u180 as a raise from the ViewOp key that took
# down every path resolving a Conjugate bit). register.cc's USE_ILUVATAR arm
# publishes ATen's metadata kernel, unconditionally rather than in the
# FLAGOS_ILUVATAR_KERNEL-gated .inc, which is why it is listed here. It is not a
# kernel the conf can route to: FlagGems' `_conj` is eager and breaks the
# lazy-view contract (see the entry in NATIVE_TRITON_GAPS), so iluvatar also
# joins NATIVE_KERNEL_PREFERRED. That is what keeps the route string `iluvatar`
# -- the native slot is Backend::kIluvatar and Dispatcher::GetFn answers it for
# every backend, so it is the same metadata kernel `flaggems` would have reached,
# while the FlagGems slot would be subtracted as "also a native kernel" and the
# op would land on `none` again. Both entries are one decision and neither is
# correct alone.
#
# `view_as_real` is the same pair of reasons one step downstream: it is the view
# FlagGems' complex arithmetic decomposes into (`ops/add.py`), a `none` route
# refuses it at the ViewOp key like `_conj`, and FlagGems' own route for it is
# the kernel that is missing (see its entry in NATIVE_TRITON_GAPS). register.cc
# publishes ATen's metadata kernel for it under the same guard.
#
# The three in-place add/div overloads are a different pair of reasons again:
# they are not views, but FlagGems' complex path is wrong for `add` (complex128
# returns zeros) and raises for `div` (complex64), and ATen's CPU fallback is
# not an alternative -- the boxed fallback mutates a CPU copy, so
# `self.add_(other)` and `self.div_(other)` measured here returned the input
# unchanged. register.cc publishes add_/div_: add_ through ATen's own add_out,
# div_ through a host round trip (ATen's complex div writes its destination
# before it reads its sources, so it is wrong when the destination is the
# operand). `add.Tensor` and `add.Scalar` are in NATIVE_TRITON_GAPS but
# not here: they take the out-of-place -> in-place lowering to the same kernel,
# and an out= m.impl cannot be registered under the add.Tensor schema.
EXTRA_NATIVE = {
"ascend": {"matmul", "matmul_backward"},
"iluvatar": {
"_conj",
"view_as_real",
"add_.Tensor",
"add_.Scalar",
"div_.Tensor",
},
}
# Ops a hand-written wrapper claims for every platform, so they appear in no
# .inc at all. Same cause as EXTRA_NATIVE, different owner:
# csrc/aten/sdp_choice_stub.cc registers aten::scaled_dot_product_attention on
# PrivateUse1 for the CUDA-boxing builds, and that override reads this conf to
# choose between the FlagGems path and the boxing path -- so the entry has to be
# here or the choice is unreachable. It cannot be routed as its leaf ops like
# every other entry: scaled_dot_product_attention is a composite whose
# fused-backend selection runs *inside* the composite and then branches on
# query.device().type(), so on PrivateUse1 the leaves are never consulted.
EXTRA_ROUTED = {"scaled_dot_product_attention"}
# Native kernels that exist behind an op FlagGems currently *wins*, so they
# claim no m.impl of their own and the .inc scan cannot see them: the FlagGems
# .inc is included into the same PrivateUse1 TORCH_LIBRARY_IMPL body, and a
# second m.impl for one op is a duplicate registration. They are not routed --
# FlagGems stays the route. They are reachable through FLAGOS_FORCE_BACKEND=vendor
# and per-op FLAGOS_OP_*_=<vendor> overrides, which move an op only when it is
# annotated as a vendor candidate; the annotation is therefore load-bearing.
#
# They are NOT reachable through the dispatcher's runtime dtype escape, despite
# the shape of that mechanism: ResolveFn (dispatcher.h) substitutes the vendor
# slot only when FlagGemsRejectsArgs() says yes, and that predicate is compiled
# per vendor -- its `#else` arm (common.cc) returns false, and iluvatar has no
# arm. So for an iluvatar build the escape never fires and the force/override
# routes above are the only ones. (The escape's other prerequisite is present:
# WrapperMm & co. register into the vendor slot, so VendorSlot() resolves.)
# Adding a USE_ILUVATAR arm is a per-dtype decision that needs a BI-V150
# measurement of a FlagGems gap, which does not exist yet; until then this
# comment must not describe the kernels as escape-reachable.
#
# Listing them here is what keeps the annotation truthful. Without it an op like
# `mm` reads "= flaggems" with no marker -- the same line a platform with no GEMM
# kernel at all would emit -- and a kernel that only a force/override can reach
# would be indistinguishable from one that is unreachable. The generator's own
# header warns against exactly that documentation failure.
#
# Deliberately folded into NEITHER vendoring function. `vendor_native_ops()` is
# what the per-vendor generators subtract from their own registration set, so
# adding these there would make codegen_iluvatar_flaggems.py drop the two
# _native_batch_norm_legit* variants from the FlagGems `.inc` -- and because
# codegen_iluvatar.py omits exactly those ops from the native `.inc`
# (that is what keeps the two files disjoint), the ops would end up registered
# nowhere and every call would raise "backend not registered".
# `vendor_registered_ops()` is the registration closure the conf's `none` gate
# reads; these ops are registered by FlagGems, so widening that set would change
# no route. They are used only by render(), to annotate the route line.
# iluvatar's `mm` and `bmm` started here (measured, FlagGems-primary) and left
# on 2026-10-20: gapping them for issue #570 removes them from the FlagGems
# `.inc`, so codegen_iluvatar.py's read-back now claims them natively and the
# .inc scan sees their route. `addmm` left with the rest of the gemm family on
# 2026-10-21 for the same reason: codegen_iluvatar.py has carried an `addmm`
# kernel all along and the FlagGems claim was what kept its `m.impl` out of the
# native .inc. XLA/musa/gcu/ascend/kunlunxin have no escape-only kernels.
ESCAPE_NATIVE = {
"iluvatar": {
"_native_batch_norm_legit",
"_native_batch_norm_legit_no_training",
}
}
# CUDA-boxing platforms use the same four-key shape. Their fallback kernel is the
# CUDA boxing kernel, so the `<vendor>` slot is spelled "cuda" and it covers
# every op -- which is why a boxing conf has no `none` entries while a
# native-kernel vendor has many.
#
# platform -> (fallback backend key, conf naming that platform's triton gaps)
#
# The gap set is per-platform, not a FlagGems defect: the kernel works elsewhere,
# but this platform's triton backend cannot run it, so the op is forced back to
# boxing. MetaX's and DCU's are recovered here by diffing that platform's
# existing conf -- an op FlagGems covers whose conf line reads `cuda` is a gap
# that platform measured, so the set is read back from the artifact instead of
# restated, and the per-op diagnosis is in BOXING_GAP_NOTES below. That round
# trip only holds while this script is the confs' sole writer: codegen_ops.py
# wrote backends_metax.conf and backends_dcu.conf as well until #459, from
# fallback sets that had drifted away from the confs, so a documented full
# regeneration silently reverted the pins the diff had recovered. PPU's
# is a literal (BOXING_TRITON_GAPS) because it is vendor-first no longer, and the
# diff cannot tell a gap from a policy exception on a flaggems-first platform.
BOXING_PLATFORMS = {
"metax": "backends_metax.conf",
"dcu": "backends_dcu.conf",
# PPU routes FlagGems first; its triton gap set is the mm/bmm family. PPU's
# triton build rejects the `num_ldmatrixes` kwarg that FlagGems' _hygon
# mm/bmm kernel passes to triton's mm_kernel: mm raises KeyError at
# triton/runtime/jit.py:_pack_args, and bmm reaches the same kernel and
# stalls inside triton compilation. Measured on the PPU runner, routing mm to
# flaggems turned a 2.87s test_factory_ops.py into 1801s (28 minutes inside
# test_mm alone) and left the process exiting on SIGSEGV. PPU previously had
# no conf of its own and read backends_cuda.conf, so it silently inherited
# every CUDA FlagGems route; a separate conf is what lets the two platforms
# disagree about mm/bmm. Filed upstream as FlagGems issue #6225.
"ppu": "backends_ppu.conf",
}
BOXING_FALLBACK = "cuda"
# Generated conf notes for platform-specific FlagGems fallbacks. Keep these in
# the generator so regeneration preserves the measured diagnosis and upstream
# issue reference instead of erasing a hand-edited comment.
BOXING_GAP_NOTES = {
"dcu": (
"mm/bmm are pinned to cuda: FlagGems issue #6227 (first profiled",
"mm hangs until the 50-minute CI timeout on the DCU HCU backend).",
"mse_loss is pinned to cuda: FlagGems issue #6221 (mse_loss_backward",
"is not implemented on DCU).",
"transpose.int is pinned to cuda: FlagGems issue #6219 (transpose is",
"not implemented in the FlagGems module on DCU).",
"_conj is pinned to cuda, for a contract reason rather than a compile",
"one: ATen's conj is a lazy view -- it sets the Conjugate bit and leaves",
"the storage alone -- which tests/integration/test_math_bits_contract.py",
"pins, while flag_gems' _conj is a real kernel that materializes the",
"conjugated values, so registering it on PrivateUse1 replaces the view",
"with an eager copy and is_conj() comes back False. Boxed to the CUDA",
"kernel the view survives: measured on DCU, t._conj().is_conj() is True",
"and the result shares the input's storage (data_ptr equality).",
"relu/relu_ are pinned to cuda, for the same kind of contract reason:",
"with them on FlagGems the profiler-parity workload launches gems'",
"relu_forward_kernel_rank_1 instead of ATen's",
"at::native::vectorized_elementwise_kernel, and the demangling guard in",
"tests/integration/test_profiler_parity.py requires the trace to carry",
"an at::native template kernel -- with relu on FlagGems no kernel name",
"in that trace contains '::' and the guard fails as vacuous. Measured",
"on DCU by holding the build fixed and switching only the route: the",
"flaggems arm's kernel set has no '::' name, the cuda arm's has",
"at::native::vectorized_elementwise_kernel<4, ...launch_clamp_scalar>.",
"add/sub/div reached with a Python float are pinned to cuda: their",
"FlagGems pointwise kernels promote the element loop to f64, which the",
"DCU HCU backend cannot lower. ATen boxes a Python float as an f64",
"wrapped number, so `t + 2.0` on a bf16 tensor arrives at add.Tensor",
"rather than add.Scalar, and the kernel takes an f64 operand beside the",
"bf16 one. TritonHCUGPUToLLVM's TruncFOpConversion::createDestOps then",
'asserts `inElemTy.isF32() && "unsupported conversion"`',
"(third_party/hcu/lib/TritonHCUGPUToLLVM/ElementwiseOpHCUToLLVM.cpp:2358),",
"surfacing as RuntimeError: PassManager::run failed through",
"triton/backends/hcu/compiler_hcu.py make_llir. Same defect as FlagGems",
"issue #6212 (reported for MetaX); NATIVE_TRITON_GAPS['gcu'] carries the",
"same list. Measured on DCU, 2026-09-17, by calling every overload",
"through torch.ops.aten with a bf16 tensor -- the only way to reach the",
"sibling spellings, since `t + 2.0` lands on add.Tensor whatever the conf",
"says about add.Scalar -- and by replaying all eleven through",
"FLAGOS_OP_<op>=flaggems, dots doubled, so every verdict stays",
"re-runnable without the conf that no longer routes them there.",
"Python ints, f16/f32 tensors and tensor operands are unaffected:",
"add.Scalar, div.Scalar, div_.Scalar and the six clamp spellings stay on",
"flaggems, all measured passing, and mul was already on cuda. Pinned:",
"add.Tensor, add_.Tensor, sub.Tensor, sub_.Tensor, div.Tensor,",
"div.out, div_.Tensor, div.Tensor_mode, div_.Tensor_mode,",
"div.Scalar_mode, div_.Scalar_mode. The four *_mode entries have a",
"separate cause, and it needs a named rounding mode: 'trunc' routes",
"flag_gems to trunc_div_func/trunc_div_func_tensor_scalar, whose whole",
"body is `trunc(div_rn(x, y))`, and div_rn there is",
"triton.language.extra.hip.libdevice.div_rn -- the shim carries the",
"symbol, so use_tl_extra returns it and flag_gems' own x/y + tl.floor",
"fallback never runs -- while that symbol lowers to None on the HCU",
"backend. trunc(None) then raises 'cannot convert None of type",
"<class NoneType> to tensor', and the floor path dies the same way in",
"_float_floordiv with 'unsupported operand type(s) for -: NoneType and",
"int' on `q - 1`. Exactly the mode flag_gems' own triton_lang_helper.py",
"documents for asin on another fork: the symbol exists at the Python",
"level but lowers to None. rounding_mode=None routes to true_divide",
"instead and runs, which is why the overload survey measures all four",
"as passing -- the rounding_mode it synthesizes is None -- so they are",
"pinned on the named-mode measurement, not on the survey's.",
"Reached in Qwen-Image-2512: QwenImageRMS_norm in diffusers'",
"autoencoder_kl_qwenimage.py returns `normalized * self.scale *",
"self.gamma + self.bias`, and self.bias is the Python float 0.0",
"whenever the layer is built without a bias, which is how norm_out is",
"built; with the pipeline loaded as bfloat16 (sweep.py's torch_dtype)",
"that is add.Tensor(bf16_tensor, 0.0).",
"scatter_add_, argmin, mean and mean.dim are pinned to cuda for a",
"correctness reason, on FlagGems kernels that return a wrong answer or",
"raise rather than on anything the DCU backend cannot compile. Each was",
"measured on DCU with DAS boxing and FlagGems over the same tensor, both",
"arms in a fresh process and the build held fixed:",
"scatter_add_ with an index that is a valid PyTorch view(...).expand(...)",
"of stride (1, 0) -- logically (340, 12), physically 340 elements --",
"mismatches all 120 output elements, max |diff| 284.0, while boxing",
"matches all 120; the FlagGems 2-D path walks the index as if it were",
"contiguous and addresses through logical offset 4079, which is a silent",
"wrong answer here and an HSA VMFault on other allocations. Reported as",
"FlagGems issue #6448, with the same defect already filed as #6017.",
"argmin over a 20-column reduction returns 20, outside the valid [0, 19],",
"because the 32-lane kernel does not mask the tail; the survey cannot see",
"it, since its profiles reduce over the whole of a power-of-two row.",
"Reported as FlagGems issue #6447.",
"mean and mean.dim raise ZeroDivisionError on an empty reduction --",
"empty(0).mean() and empty(2, 0).mean(dim=1) -- where ATen returns NaN, so",
"a shape-agnostic call site turns a defined result into an exception.",
"Reported as FlagGems issue #6333.",
"The conf is overload-granular and cannot express a shape condition, so",
"the safe direction is the whole family on the boxing kernel --",
"scatter_add, scatter_add.out, argmin.out, mean.out and mean.dtype_out",
"were already there -- and the routes can be re-enabled per op with",
"FLAGOS_OP_<name>=flaggems once the FlagGems side is repaired.",
"native_batch_norm is pinned to cuda for an availability reason, one",
"level below the kernels: the route does not fail, it cannot be",
"entered. This op is routed through the FlagGems *Python* path, and",
"the generated kernel for it calls flag_gems.native_batch_norm",
"(csrc/aten/generated/flaggems_python_kernels.cc), a symbol the",
"FlagGems cohort pinned in this environment (5.3.4.post1.dev1) does",
"not export: it has batch_norm, batch_norm_backward and",
"_batch_norm_no_update, but no native_batch_norm. The Python call",
"resolves the attribute at run time, so the route dies with",
"AttributeError: module 'flag_gems.ops' has no attribute",
"'native_batch_norm' -- on every call, training or eval, and for any",
"shape. Measured on DCU, 2026-09-28, as nn.BatchNorm2d over a",
"(2, 4, 8, 8) fp32 input: the flaggems arm raises that AttributeError",
"for train=True and train=False alike, while the cuda arm returns a",
"result matching ATen (max |diff| 4.8e-07) and leaves the input",
"unmodified. Reported as Torch-FL issue #295, which mirrors FlagGems",
"issue #6332; on the FlagGems revision #295 measured (5.4.0rc2.post1)",
"the symbol exists, so what #295 saw was wrong output and a mutated",
"input rather than a missing entry point. Both revisions agree on the",
"conclusion -- the route is unusable for this op -- and the pin is the",
"same repair either way.",
"The rest of the family stays where it is, and deliberately: pinning",
"it wholesale would trade this failure for another. Measured on the",
"same build, native_batch_norm_backward matches ATen on both arms, and",
"_batch_norm_no_update is the inverse of this op -- it matches ATen on",
"flaggems and raises 'Expected tensor to have CPU Backend' on cuda, so",
"it must stay on flaggems. Only native_batch_norm moves.",
"_native_batch_norm_legit and its variants were already on cuda.",
"slice_backward and silu_backward are pinned to cuda for two hcu",
"backend gaps rather than for wrong answers: both gems kernels are",
"correct where they can run, and neither can run here. slice_backward",
"faults the hardware -- 'Invalid address access' -- and only once the",
"grad it produced is consumed by MIOpen's convolution_backward",
"(tests/integration/ops/test_conv1d_dispatch.py, C=6144 depthwise), so",
"it is an hcu codegen bug rather than a shape/stride mismatch on our",
"side: the kernel's own output metadata and values check out when",
"measured on its own. silu_backward calls tl.math.div_rn, whose",
"lowering is the same div_rn shim the div.*_mode entries above are",
"pinned for -- it returns None on the hcu backend, and here the None",
"reaches the builder, so compilation dies with AttributeError:",
"'NoneType' object has no attribute 'type'. MetaX pins slice_backward",
"too, for a cause that is not this one (an out-of-bounds Xnack fault",
"inside the gems kernel); the two entries overlap by coincidence.",
"The .out / .grad_input spellings are spelled cuda as well, but that",
"is not a gap: FlagGems does not cover those overloads.",
),
"metax": (
"MetaX is FlagGems-first like every generated conf, but the ops",
"triton-metax or flag_gems cannot run on the flagos device are pinned",
"to cuda and reach the boxing kernel (maca libtorch_cuda via mcblas).",
"The fallback has to be cuda and not a metax-native backend: the",
"hand-written mxcc backend is not registered in the MetaX boxing wheel",
"(FLAGOS_BUILD_VENDOR=OFF).",
"mm/bmm and their .out spellings are the FlagGems SPLIT_K kwarg",
"triton-metax rejects; mean.dim is FlagGems' non-inner-dim path, which",
"builds a CUDA context that fails on triton-metax.",
"add.Tensor, div.Scalar and div.Tensor are the scalar-arithmetic group:",
"MetaX Triton cannot lower it once FlagGems normalizes the scalar to a",
"tensor overload and promotes it to f64, because the compiler then",
"rejects f64 -> bf16. _conj is a contract pin rather than a compile",
"one: ATen's conj is a lazy view that sets the Conjugate bit and leaves",
"the storage alone, while FlagGems' kernel materializes the value.",
"sort and sort.stable, plus the matmul -> relu -> sum.dim_IntList",
"workload, are held for the profiler: the MetaX FlagGems C++ sort path",
"corrupts that workload, and the contract above is what MetaX activity",
"correlation is being stabilized against.",
"The rest are flag_gems kernels that guard on tensor.device.type",
'against flag_gems.device (== "cuda") and so fail on a flagos device',
"either by falling back to torch.<op>, which re-enters flagos_python",
"dispatch and recurses, or by raising ValueError('Inputs must be cuda",
"tensors ...'): embedding_dense_backward, i0, reflection_pad2d,",
"soft_margin_loss, special_i0e, special_i1, im2col, zero and the",
"mul_.Tensor group among them.",
"slice_backward is held for a fault rather than a wrong answer: gem's",
"slice_backward_kernel reads out of bounds on large tensors (a",
"grad_output [1, 6144, 35] scattered into [1, 6144, 32], from the",
"conv1d backward of a linear-attention model), and on MetaX that",
"Xnack/ATU fault (0x8) disables the whole process's mcruntime -- every",
"later op fails with mcErrorIllegalAddress -- so the bounds-safe boxing",
"kernel is the only way back.",
"slice.Tensor is held on a vestigial assertion: flag_gems/ops/slice.py",
"rejects complex64 and complex128, while its body is a pure as_strided",
"view that never consults a dtype, and diffusers'",
"QwenImageTransformer2DModel slices complex rotary frequencies.",
"Reported upstream as FlagGems issue #6356; the entry can come out when",
"that lands.",
"Most of the rest were measured by the 2026-09-15 differential survey",
"of the 166 ops MetaX gained at the FlagGems 5.4.0rc2.post1 ceiling,",
"which reached each op on both routes with one host-built operand pair.",
"The per-op verdicts, and the five flagged ops that fail on both routes",
"and are therefore not held, are pinned by the route each op takes in",
"this file; the differential survey that produced them is recorded in",
"docs/reference/operator-support.md, and the routing itself is now",
"asserted for every platform by tests/integration/flaggems/.",
"This set is not restated here: gen_vendor_confs.py reads it back from",
"backends_metax.conf on every run (see BOXING_PLATFORMS), so a pin is",
"recorded by the route it takes in this file, and the gap does not",
"drift away from the conf the way it did while codegen_ops.py wrote a",
"second copy from a stale literal (issue #459).",
),
"ppu": (
"49 of the 482 FlagGems-covered ops are pinned to cuda; the other 433",
"route to flaggems. mm/bmm are the first four (FlagGems issue #6225:",
"PPU rejects the _hygon kernel's num_ldmatrixes kwarg) and 33 are the",
"2026-09-15 overload survey's measured route-dependent failures. Four",
"more are reflection-padding routes that survey cannot reach: it derives",
"`padding` from the rank, so every profile fails ATen's arity check",
"before the device guard behind it can fire, and that guard is the",
"device-name mismatch the CUDA alignment fixes and leaves alone on every",
"other vendor. The last eight came from checks that survey cannot",
"see: the addmm family, whose autotune picks a BLOCK_SIZE_K < 16 config",
"on small-K shapes that the ppu triton rejects in tl.dot; _conj, which",
"FlagGems materializes instead of setting the Conjugate bit; _unsafe_view,",
"whose FlagGems reshape can silently return a copy; and slice.Tensor,",
"whose FlagGems route rejects complex dtypes. See",
"BOXING_TRITON_GAPS, which carries each op's failure, and",
"docs/reference/operator-support.md for the full survey.",
),
}
# Boxing platforms whose triton gap set is stated here instead of recovered by
# diffing the generated conf (see boxing_triton_gaps). Those two are the same
# thing only while the platform is vendor-first: the diff infers "this op is a
# gap" from "this op is spelled `cuda` even though FlagGems covers it", which
# stops being readable the moment a platform routes FlagGems first by policy --
# then the file's `cuda` entries are the exceptions and the diff would return
# just them, which is exactly right the first time and self-erasing the second.
#
# PPU: FlagGems-first is the intent (most of its 478 covered ops run the Python
# path), so its gaps are a measured exception list rather than a residue. The
# first four are the mm/bmm family from FlagGems issue #6225: PPU's triton
# rejects the `num_ldmatrixes` kwarg FlagGems' _hygon mm/bmm kernel passes to
# triton's mm_kernel -- mm raises KeyError at triton/runtime/jit.py:_pack_args
# and bmm reaches the same kernel and stalls inside triton compilation (measured:
# routing mm to flaggems turned a 2.87s test_factory_ops.py into 1801s, 28
# minutes inside test_mm alone, then SIGSEGV). `mm.out`/`bmm.out` are the out=
# spellings of the same kernel.
#
# Add an op here when a run reports it failing on the flaggems route, with the
# failure recorded next to it. The rest of the PPU set is the 2026-09-15 overload
# survey's verdict on the widened route table: tests/manual/flaggems_overload_survey.py
# reports 42 of the 478 routes with no correct case, and re-running those 42 with
# FLAGOS_OP_<op>=cuda splits them into 33 that pass on the boxing kernel and 9
# that fail on both. Only the 33 are route-dependent and appear here; the 9 stay
# on flaggems and their failures are recorded as pre-existing in
# docs/reference/operator-support.md. Each entry below carries the failure the
# survey measured, in the same profile the route is reached with.
BOXING_TRITON_GAPS = {
"ppu": {
# -- mm/bmm family, FlagGems issue #6225 (see above) --
"bmm",
"bmm.out",
"mm",
"mm.out",
# -- FlagGems' addmm autotune selects a BLOCK_SIZE_K < 16 config on
# small-K shapes, and FlagTree's ppu triton rejects that in tl.dot:
# min_dot_size[2] is 16, so triton/language/semantic.py:1555 raises
# "Input shapes should have M >= 1, N >= 1 and K >= 16" as a
# CompilationError inside the autotuner's do_bench_cudagraph replay.
# Measured on flagos:0 with flag_gems 5.4.0rc2.post1+gd45285ba6:
# M/N/K = 4/8/8, 128/128/8, 2/2/1 and 4/4/4 fail while 4/8/12, 8/8/15,
# 4/8/16 and 8/8/31 pass, so the trigger is which config the alignment
# strategy picks rather than K < 16 outright. nn.Linear(8, 8) over a
# (4, 8) input is the shape that reaches it from
# tests/integration/test_factory_ops.py::TestCopyTransfer::
# test_module_cpu_after_forward. baddbmm has the same dot structure and
# does not fail: measured OK at K = 8 and K = 16. --
"addmm",
"addmm.dtype",
"addmm.dtype_out",
"addmm.out",
"addmm_",
# -- `_conj` is a metadata operator in PyTorch: torch.conj() sets the
# Conjugate bit and leaves storage untouched. FlagGems' _conj
# (flag_gems/ops/_conj.py) computes the conjugate into a fresh tensor
# instead, so torch.conj(x).is_conj reads False on the flaggems route --
# right values, wrong contract. tests/integration/test_math_bits_
# contract.py asserts that contract for every backend and takes it as a
# precondition ("torch.conj must stay lazy for this contract to apply"),
# which fails 7 of its cases on the flaggems route and passes all 12 on
# the boxing kernel. The value-level overload survey cannot see this
# one: eager materialization is numerically indistinguishable. --
"_conj",
# -- ATen's `_unsafe_view` has the same aliasing/error contract as view:
# a viewable layout aliases the input, while an incompatible stride
# raises RuntimeError. FlagGems implements it as `reshape`, which may
# silently allocate a copy for that incompatible stride. Measured on
# PPU: a viewable input aliases on both routes, but a non-contiguous
# transpose returns a non-aliasing tensor on FlagGems where CUDA boxing
# raises "view size is not compatible". Keep the kernel generated for
# explicit A/B diagnosis, but preserve the default PyTorch contract. --
"_unsafe_view",
# -- Qwen-Image-2.1 slices a complex64 rotary-embedding cache from the
# second denoising step onward. FlagGems' slice wrapper asserts that
# complex64/complex128 are unsupported before reaching its as_strided
# implementation, while the CUDA boxing route preserves the complex
# dtype and view semantics. Measured on PPU in the PR #342 full 40-step
# case: the FlagGems route aborts at step 2 with that assertion; boxing
# completes the same slice contract. --
"slice.Tensor",
# -- FlagGems' reflection-padding wrappers reject a flagos operand before
# they reach a kernel, and the overload survey cannot see it: the harness
# derives `padding` from the tensor rank, so ATen's arity check fails
# every profile first ("padding size is expected to be 4, but got: 1"
# for 2d, "... 6, but got: 1" for 3d) and the device guard underneath it
# is never reached. All seven cases on each of the four routes are
# INVALID_CASE, which is why they appear in no rollback group even
# though they cannot run. Both modules compare the operand against
# flag_gems' own device name --
# ops/reflection_pad2d.py:106,136 and ops/reflection_pad3d.py:125,158
# if input.device.type != flag_gems.device:
# raise ValueError(f"input must be a {flag_gems.device} tensor")
# -- and on PPU that name is "cuda" while the operands these two ops are
# handed are flagos tensors -- torch.accelerator.current_accelerator()
# is `flagos`, and such a tensor reports device(type='flagos') with
# is_cuda False -- so the guard rejects them. Measured with the route
# forced:
# FLAGOS_OP_reflection_pad2d=flaggems
# torch.ops.aten.reflection_pad2d.default(<flagos tensor>, [1,1,1,1])
# -> ValueError: input must be a cuda tensor
# while the same call on a tensor made with device="cuda" returns the
# right answer: the boxing backend makes that spelling CUDA-typed, so
# the failure is the guard and not the kernel. See
# docs/reference/operator-support.md: the name comes from the `_thead`
# vendor descriptor (device_name="cuda"), and
# torch_fl.accelerator.cuda._cuda_compat.patch_flaggems_device_name,
# which realigns it on the NVIDIA path, returns immediately for any
# vendor but nvidia. reflection_pad1d and reflection_pad3d_backward
# carry no such guard and stay on flaggems. --
"reflection_pad2d",
"reflection_pad2d.out",
"reflection_pad3d",
"reflection_pad3d.out",
# -- FlagGems refuses the flagos device before running a kernel. The
# tensor is on the PrivateUse1 device, so `is_cuda` is False and each of
# these raises its own guard rather than a triton failure:
# i0 / i0.out "input tensor must be on cuda device"
# special_i0e/i1 "Tensors must be cuda tensors"
# bessel_k0(.out) "Tensors must be CUDA tensors"
# bessel_k1(.out) "input tensor must be on CUDA device"
# soft_margin_loss "input and target must be cuda tensors"
# upsample_bicubic2d "This Triton kernel requires CUDA tensors" --
"i0",
"i0.out",
"soft_margin_loss",
"special_i0e",
"special_i1",
"special_modified_bessel_k0",
"special_modified_bessel_k0.out",
"special_scaled_modified_bessel_k1",
"special_scaled_modified_bessel_k1.out",
"upsample_bicubic2d",
# -- triton CompilationError on the FlagTree ppu backend, no kernel
# produced: randint at 20:15, randint_like at 25:13,
# norm.ScalarOpt_dim at 14:16 --
"norm.ScalarOpt_dim",
"randint",
"randint_like",
# -- `out=` is not written through, or the alias does not accept the
# schema's arguments: cosh.out "cosh_out() missing 1 required positional
# argument: 'out'"; sum.out returns the input's shape (32, 32) instead of
# the reduction (); mul_.Tensor dispatches to aten::mul.out, which has no
# CPU fallback --
"cosh.out",
"mul_.Tensor",
"sum.out",
# -- numerically wrong, every profile that ran: measured max_diff on
# float32 unless noted --
"_pdist_backward", # nan
"elu", # 0.85
"elu_", # 0.76
"elu_backward", # 0.93
"histc", # 1024
"range", # returns float64 for a float32 range
"special_chebyshev_polynomial_v", # nan
"special_chebyshev_polynomial_w", # 1.0
"special_shifted_chebyshev_polynomial_u", # 798.6
"special_shifted_chebyshev_polynomial_w", # 2.1e11
# -- raises on every profile that ran --
"_unique2", # "return arity 3 != 1"
"dequantize.self", # int_repr has no CPU kernel
"norm.Scalar", # "look up dimensions by name, got: name = None"
"randperm", # bare AssertionError
"special_chebyshev_polynomial_u", # n must be in [0, 5]
"special_hermite_polynomial_h", # n must be in [0, 9]
"unique_consecutive", # RecursionError
},
}
# Where the registration .inc files live, relative to the repo root.
CSRC_DIR = REPO_ROOT / "csrc/aten"
# Which generated confs may route to `flaggems_cpp` at all.
#
# That slot is Backend::kFlagGemsCpp, registered in csrc/aten/flaggems_cpp_kernels.cc
# behind `#ifdef FLAGOS_FLAGGEMS_CPP`, which csrc/CMakeLists.txt defines only for
# a FLAGOS_BUILD_FLAGGEMS_CPP=ON build. That switch defaults ON for cuda and
# tsingmicro and OFF everywhere else, and CMakeLists.txt pins it OFF for dcu,
# musa and bpu, because the path needs FlagGems' liboperators.so built for that
# vendor's toolkit.
#
# One platform now means one conf, so a conf can no longer be reserved for the
# opt-in build that compiles the slot: backends_metax.conf is what every MetaX
# build reads, with or without FLAGOS_BUILD_FLAGGEMS_CPP=ON. The key stays legal there
# because Dispatcher::GetFn (csrc/aten/dispatcher.h) degrades kFlagGemsCpp to the
# boxing kernel when the slot is empty, instead of raising "backend not
# registered". So the 17 measured C++ routes are used when a MACA-built FlagGems
# is present and silently box when it is not -- one file, both builds.
#
# The native-kernel vendors (musa/gcu/ascend) still withhold the key. Not for
# safety, but because it would be noise: FlagGems' C++ runtime is not built for
# any of them, so every entry would degrade, and `flaggems` records the same
# routing decision honestly. Every op in the C++ set is also in the Python set
# (test_flaggems_cpp_set_is_a_subset_of_the_python_set pins that), so they reach
# the same kernel through python_op_caller; only the GIL-free entry point
# differs. (backends_flaggems_cpp.conf, the generic CUDA one, is codegen output
# and not written here.)
FLAGGEMS_CPP_PLATFORMS = {"metax"}
# Which platforms' confs may route the FlagGems Python path. Ascend is in the
# set: backends_ascend.conf is FlagGems-first, so those routes must stay
# resolvable -- keep the generated conf honest by never routing a platform
# through a dispatcher slot its wheel does not compile. Boxing platforms
# compile the Python caller and may use the measured FlagGems coverage --
# including PPU, whose FlagTree install (triton with the `ppu` backend) is
# what set_env_ppu.sh provisions.
#
# Kunlunxin is deliberately absent, and a native vendor rather than a boxing
# one: libxpuapi.so (baidu::xpu::api) has the operator surface, and there is no
# FlagGems liboperators.so for the XPU SDK, so no FlagGems C++ wrapper can be
# built either. The Python path is blocked by the SDK itself -- its torch shim
# reports the FlagGems runtime device name as "cuda" while the tensors dispatch
# on PrivateUse1, so flag_gems' device gate never matches and every route falls
# through. backends_kunlunxin.conf therefore routes only the ops the native
# kunlunxin kernels back, and everything else stays at `none` (cpu_fallback).
# Add kunlunxin here only together with a measured FlagGems cohort, so the gate
# and the conf stay in step.
#
# Iluvatar is in the set from the start: it has no boxing path at all, so the
# FlagGems Python route is the only route its conf can emit, and everything it
# does not claim falls to `none` (cpu_fallback). The allowlist it registers is
# the FLAGGEMS_PYTHON_OPS ceiling; per-op pruning follows the survey measurement
# the way kunlunxin's entry above is waiting on, not a guess.
FLAGGEMS_PYTHON_PLATFORMS = {"ascend", "metax", "dcu", "gcu", "musa", "ppu", "iluvatar"}
# Ops a native-kernel vendor must keep on its own kernel because that platform's
# triton backend cannot compile the FlagGems kernel. The boxing platforms express
# this by pinning the op to `cuda` and recovering it with boxing_triton_gaps();
# a native vendor's conf spells the fallback as the vendor name, which is also
# what an op with no FlagGems coverage looks like, so the two cases cannot be
# told apart by reading the file back. Stated here instead.
#
# ascend: pow/rsqrt used to be excluded here. They crashed bishengir-compile with
# "LLVM ERROR: unsupported datatype for arith::ExtFOp to hfusion" on triton-ascend
# 3.2.2 / CANN 9.0.0 / Ascend910_9382 (CI run 34792677968, filed upstream as
# FlagGems issue #6226). That stack is no longer what Ascend runs -- FlagGems now
# goes through FlagTree 0.6.2a1+ascend3.5 (Triton 3.5) -- so the exclusion was
# re-measured rather than inherited. On Ascend910 with CANN 9.0.0, FlagTree
# 0.6.2a1+ascend3.5 and FlagGems d45285ba, all five ops -- pow.Scalar,
# pow.Tensor_Scalar, pow.Tensor_Tensor, rsqrt and rsqrt_ -- compile, run on the
# FlagGems route and match the CPU reference for shapes (1,), (7,), (128, 256)
# and (3, 5, 17) in fp32/fp16/bf16, three seeds each, plus the backward through
# the FlagGems kernels. They are no longer excluded.
#
# ascend: mul_.Tensor is here for a defect inside FlagGems, not in the Triton
# backend. flag_gems/ops/mul.py is the only operator module in FlagGems that gates
# its Triton path on the runtime device *name* -- `_DEVICE_NAME =
# runtime_device.name` (line 34) -- and when the operand's device type differs it
# falls back with `torch.ops.aten.mul.out.redispatch(_FALLBACK_KEYSET, a, b,
# out=out)` (line 589). On this backend the runtime name is "npu" while the
# tensor's device type is "flagos", so the fallback always fires, and it hands the
# boxed `mul.out` schema the caller's raw operand: `t.mul_(b)` dies with
# "Unable to cast ... to Tensor" against aten::mul.out(Tensor self, Tensor other,
# ...), and the `.Scalar` spelling with "Expected a value of type 'Tensor' for
# argument 'other' but instead found type 'float'". ATen boxes mul_.Scalar onto
# mul_.Tensor, so one entry covers both spellings. No other elementwise op is
# affected because no other module carries that gate -- measured: add_.Tensor,
# div_.Tensor, sub_.Tensor and add_.Scalar all pass on the FlagGems route while
# mul_.Tensor and mul_.Scalar both fail. Ascend has a native mul kernel, so the
# route back is free. mul.Tensor is outside the FlagGems coverage set already, so
# only the in-place form needed an entry. Measured on Ascend910 with FlagGems
# d45285ba + FlagTree 0.6.2a1+ascend3.5. Not yet filed upstream.
#
# ascend: sort/sort.stable fail inside the kernel's own compilation:
# flag_gems/ops/sort.py:111 is rejected by BiShengHIR with "error: ub overflow,
# requires 4620288 bits while 1572864 bits available! (possible reason: tiling
# basic block is too large ...)", and the op never launches (measured both through
# torch.sort and torch.sort(stable=True) on Ascend910 with FlagTree
# 0.6.2a1+ascend3.5). This is the same kernel the MUSA entry below routes around
# for a different reason -- there the failure is mudnn's missing uint32 cast, here
# it is the Ascend compiler's unified-buffer budget. Ascend's own sort emits both
# the values and the indices, and argsort/msort are composites over sort, so one
# entry covers all four.
#
# ascend: the last three generator-consuming RNG ops, which fail *after* the
# philox bridge (torch_fl/accelerator/ascend/_ascend_compat.py) fixes the state
# contract, plus randperm, which was never a generator problem at all. Each dies
# inside BiShengHIR's compilation of the FlagGems kernel, so the op never
# launches. Measured on Ascend910 with FlagGems 6d31db9aa + FlagTree
# 0.6.2a1+ascend3.5:
#
# rand, rand_like flag_gems/ops/rand.py:35 is rejected with "ub overflow,
# requires 2294016 bits while 1572864 bits available!",
# the same unified-buffer budget that rejects sort below.
# The .out overloads are already native and unaffected.
# exponential_ flag_gems/ops/exponential_.py:152 rejects the kernel's own
# compare: "'arith.cmpi' op attribute 'predicate' is not a
# valid ...". FlagGems' Ascend vendor list already carries this
# op in CUSTOMIZED_UNUSED_OPS, so upstream agrees it is unused.
# randperm wrong before it is slow. randperm(50) returns all zeros --
# silently, and identically for two different seeds, which is
# what the RNG dispatch test reads as "ignores the seed" --
# while randperm(2000) fails to compile with "ub overflow,
# requires 10092544 bits" through topk.py:266 -> topk.py:193.
# The failure is shape-dependent, so a small-N check proves
# nothing about the route.
# native_dropout, flag_gems/ops/dropout.py:33 is rejected the same way, and
# native_dropout_ again only for large tensors: the 100_000-element dropout in
# backward tests/integration/ops/test_rng_dispatch.py needs 5112576
# bits of unified buffer, while the 256-element one the same
# file uses elsewhere fits. Both overloads have an aclnn
# kernel, so the route back is free; the ATen composite the
# test describes as unreachable-by-seed is not what `ascend`
# resolves to.
#
# Ascend's own kernels implement all six, so the route back is free.
#
# ascend: every comparison overload FlagGems routes here, because the kernels
# evaluate the comparison in float32 -- flag_gems/ops/{ge,gt,le,lt}.py apply
# `x.to(tl.float32)` to the operand and eq/ne cast both sides. That is exact for
# the float dtypes these kernels were written for and silently wrong for integer
# operands wider than float32's 24-bit mantissa. Two measured defects, both on
# Ascend910 with FlagGems 6d31db9aa + FlagTree 0.6.2a1+ascend3.5:
#
# 1. the cast itself. `ge` over [2**53+1, 2**53] against the same operands
# shifted by one returns [True, True] where ATen returns [False, False],
# and `eq` over four distinct int64 values reports all four equal.
# 2. the scalar operand. A Python int outside int32 range collapses to 0
# before the comparison, which is what `torch.all(values >= -(1 << 63))` in
# tests/integration/ops/test_rng_dispatch.py trips over. A one-line Triton
# kernel with no FlagGems involved reproduces it:
# `tl.load(x).to(tl.float32) >= y` over [-(1<<63), -1, 0, 1] returns
# [F, F, T, T] for y = -(1<<63) and [F, T, T, T] for y = 1<<40, where ATen
# returns all-False for both. The same kernel with a float y, or with a
# 0-dim int64 tensor operand, is correct -- so it is the int-to-float32
# scalar path, not the width of the value.
#
# (1) is a FlagGems defect and (2) is a backend one; neither has a routing-free
# workaround, and both are silent. Ascend implements all twelve overloads
# through aclnn, so the route back is free. Not yet filed upstream.
#
# ascend: mm and mm.out used to be pinned here for a FlagGems tuner defect, and
# are no longer. The pin recorded that the Ascend tune config tunes a kernel
# that cannot accept one of its own keys:
# flag_gems/runtime/backend/_ascend/tune_configs.yaml declares
# BLOCK_M/BLOCK_N/BLOCK_K/SPLIT_K for `mm:`, while the kernel that key is fetched
# for -- flag_gems/ops/mm.py:mm_kernel_general, reached from `mm` / `mm_out`
# through the general path -- takes only BLOCK_M/BLOCK_N/BLOCK_K/GROUP_M/
# IS_FP64. The first call would die inside the autotuner's own benchmark run,
# before any kernel was compiled, with "KeyError: 'Keyword argument SPLIT_K was
# specified but unrecognised'" from triton/spec/ascend/runtime/jit.py:_pack_args.
# That was measured on Ascend910 / CANN 9.0.0 / FlagTree 0.6.2a1+ascend3.5
# against FlagGems d45285ba and 6d31db9aa.
#
# It does not reproduce on the current cohort. Measured on Ascend910C / CANN
# 9.0.0 / FlagTree 0.7.0+ascend3.5 / FlagGems 5.4.0: the yaml still declares
# SPLIT_K for `mm:` (lines 98/103/108) and runtime.get_tuned_config("mm") still
# returns the three Config objects carrying it, but the first call succeeds. The
# key never reaches _pack_args because FlagGems' own LibEntry.run no longer
# forwards **kwargs to the kernel at all -- it collects the arguments from the
# wrapped kernel's signature (self.jit_function.params, then
# self.signature.parameters) and launches positionally as
# `kernel[grid[0:3]](*all_args)` (flag_gems/utils/libentry.py:1472-1622), so a
# config key the kernel does not declare is dropped instead of passed to the
# argument packer. Confirmed with a cold cache -- a fresh HOME, which leaves
# both the triton compile cache and ~/.flaggems/config_cache empty -- so a
# cached tuned config is not the reason, and in a fresh process whose
# `[flagos dispatch]` line reads `mm -> flaggems`.
#
# Routing them here also fixes numerics the runtime-conformance suite reads. At
# 64x64 with a fixed seed, FlagGems matches an fp32 CPU reference to 8.6e-6
# max-abs; the aclnn route returns 4.7e-3, which misses a rtol=atol=1e-3
# comparison at the near-zero entries. The aclnn cube ops run fp32 operands
# through `get_cube_math_type(true)`, i.e. cubeMathType=1, which downcasts them
# to HF32; ALLOW_HF32=0 does not change the result, so the downcast is the mode
# the kernel asks for and not a CANN default to tune away.
# The rest of the family is unaffected: bmm, bmm.out and addmm already run on
# FlagGems and match a float64 CPU reference to 1.6e-7 relative, three orders
# tighter than the aclnn path's 1.7e-4. matmul is not in the FlagGems coverage
# this conf is generated from and still routes to `ascend`. The FlagGems tuner
# defect itself is still unfiled upstream.
#
# ascend: topk returns all zeros -- both the values and the indices -- and does it
# silently, for every shape measured. On Ascend910 with CANN 9.0.0, FlagTree
# 0.6.2a1+ascend3.5 and FlagGems 6d31db9aa, `torch.topk(randn(128, device=...), 5)`
# answers [0.0, 0.0, 0.0, 0.0, 0.0] / [0, 0, 0, 0, 0] against a CPU reference of
# [3.4105, 2.5672, 2.3025, 2.3022, 1.9218] / [59, 69, 89, 45, 122]. The FlagGems
# body is reached -- its `GEMS TOPK` debug line fires -- and calling
# flag_gems.ops.topk directly on the same operand returns the same zeros in 0.02s,
# so the shared entry point is what is wrong, not this conf's plumbing.
#
# The indices are what identifies the failure. topk_single_stage_kernel stores
# `sorted_idx`, which is built from `tl.where(mask, cols, mask_index_val)` -- for
# this shape every element of it is a distinct value in 0..127, and the pad is
# INT32_MIN. Five zeros cannot be sorted output from that. The values buffer is
# built the same way, from `x_val` padded with float("-inf"). Neither buffer holds
# anything the kernel could have put there, so the store never lands: this is an
# empty output, not a mis-sorted one. (N=128 with k=5 has HAS_TLE False and
# x.is_cuda False on this backend, so topk.py's radix-TLE fast path is skipped and
# the single-stage bitonic kernel above is the one that runs.) The compile is also
# pathological rather than merely wrong at the margin -- bishengir-compile spends
# minutes on a single (shape, k) pair, and the large-N shape in the same family is
# already recorded as blowing the unified-buffer budget (`randperm(2000)` through
# topk.py, "ub overflow, requires 10092544 bits").
#
# Ascend implements topk through aclnnTopk (csrc/aten/backends/ascend/topk.cc), so
# the route back is free -- and it is exact: with FLAGOS_FORCE_BACKEND=vendor the
# same call returns the CPU reference values and indices, 0 zeros. This is what
# un-blocks tests/integration/test_compute_device_index.py::test_tuple_returning_op,
# which is itself blocked from CI by issue #391. Not yet filed upstream.
#
# ascend: sum.dim_IntList, because the FlagGems kernel reduces a bool operand in
# i1 -- which turns the reduction into an "any" and makes the count wrong. Two
# defects sit behind it, both in the non-fp16 branch of `sum_dim_kernel`
# (flag_gems/ops/sum.py:229):
#
# * `cdtype = inp.dtype.element_ty` (:242) leaves a bool input accumulating in
# i1, where `_sum += a` (:257) is a bitwise OR and `tl.sum` (:258) over i1 is
# an "any". Every row holding at least one True reduces to `1` and every row
# holding none to `0`, in the correct int64 dtype, so nothing downstream can
# tell the count is wrong. Measured on Ascend with the FlagGems route forced
# back: a `(1, 52)` bool tensor with 52 Trues returns `[1]`, with 40 Trues
# `[1]`, with 1 True `[1]`, with no Trues `[0]`; a `(3, 52)` tensor whose rows
# hold 52/0/52 Trues returns `[1, 0, 1]` along dim=1 and `[1, 1, 1, 1]` along
# dim=0, where the CPU returns `[1, 0, 1]` and `[2, 2, 2, 2]`.
# * the same i1 accumulate is rejected outright by BiShengIR on the
# multi-dimension path, so `mask.sum(dim=(0, 1))` is a hard RuntimeError
# rather than a wrong answer:
# loc("_sum"(.../flag_gems/ops/sum.py:257:16)): error: 'hivm.hir.vadd' op
# failed to verify that operand at idx 0 and 1 should have element type
# 16-bit signless integer or 32-bit signless integer or 16-bit float or
# 32-bit float or 64-bit signless integer
#
# The float, fp16 and int64 paths are numerically correct, so this is a dtype
# gap that a per-op conf entry spends more than it needs to. It is written that
# way anyway because the mechanism this repo has for a dtype gap
# (`FlagGemsRejectsOpDtype` in csrc/aten/common.cc) exists to keep an op on
# FlagGems for the dtypes that work, and the only thing keeping `sum.dim_IntList`
# there for float would be an unmeasured speed claim -- aclnnReduceSum is exact
# for every dtype. The survey's 2d-f32, 4d-f32, 1d-f32, 2d-f16, 2d-i64 and
# 2d-f32-strided profiles all PASS; the 2d-bool profile is the one that is WRONG.
#
# `mask = row_mask and col_mask` (:254) is not part of this. It reads like a
# Python `and` that would return the second operand and drop the row check, and
# Triton warns about it on every launch ("Logical operators 'and' and 'or' are
# deprecated for non-scalar tensors"), but the front end lowers a BoolOp between
# block tensors to `logical_and` (`visit_BoolOp`, folding its operands through
# `_apply_binary_method`; triton/compiler/code_generator.py:1575-1587), so the
# mask is correct and only the spelling is deprecated.
#
# The full reduction is unaffected: `mask.sum()` routes through `sum`, not
# `sum.dim_IntList`, and returns `tensor([52], dtype=torch.int64)` correctly.
#
# This is what blocks the Qwen-Image text encoder on Ascend. diffusers'
# `QwenImagePipeline._extract_masked_hidden` does `bool_mask.sum(dim=1)` and
# feeds the result to `torch.split`, so the wrong [1] becomes
# `RuntimeError: split_with_sizes: split sizes sum to 1 but tensor has 52
# elements along dim 0` -- a crash three frames away from its cause.
#
# Ascend implements sum.dim_IntList through aclnnReduceSum
# (SumDimIntlistKernelAscend in csrc/aten/backends/ascend/generated/), so the
# route back is free. That kernel needed a fix of its own before the move was
# correct -- it did not apply torch's no-dtype promotion and returned the input
# dtype, so a bool sum came back `tensor([True])` rather than int64 counts; the
# promotion now lives in codegen_ascend.py's `_reduce_dtype_prologue`. With both
# in place `mask.sum(dim=1)` returns `[52]` and `mask.sum(dim=(0,1))` returns
# `[52]`, bit-identical to the CPU. Not yet filed upstream.
#
# ascend: gelu and gelu_backward, both `approximate` spellings and every dtype,
# because the failure is in the kernels rather than at a dispatch boundary.
# Three of flag_gems/ops/gelu.py's four kernels evaluate `pow(x_fp32, 2)` --
# `gelu_tanh` (:43), `gelu_backward_none` (:54) and `gelu_backward_tanh` (:67,
# :69) -- and `pow` there is `tl_extra_shim.pow`. The Python int literal `2`
# reaches triton-ascend's libdevice binding as an int32 operand, and the table