Repository navigation
[CUDA] Add default-off op-owned MoE source fusion - #34086
Ti-Tai Wang (titaiwangms) wants to merge 16 commits into
Conversation
Reuse existing PatternRewriter and capability callbacks with batched-root discovery, lazy op-owned factories and an actual SM80+ eligibility gate. Add host and CUDA coverage while preserving existing pilot scope and kernels. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The reported H200 run has 307 failures with TF32 enabled even when MoE fusion is disabled, leaving the new provider test failing on supported hardware until baseline-specific tolerances are validated and the full matrix is rerun.
1 open issue
What changed in this PR
Adds default-off CUDA MoE source fusion to the op-owned declaration framework introduced in #34022, reusing the existing matcher, capability callbacks, and MoE kernel.
Changes:
- Recognizes bounded FP32 dense SwiGLU graphs with conservative shape checks, exact target probing, and an SM80+ hardware gate.
- Adds lazy declaration factories and batched-root matching while preserving learned weight banks.
- Adds host and CUDA tests, documents supported boundaries, and clarifies numerical-semantics guidance.
| File | Description |
|---|---|
onnxruntime/test/providers/cuda/cuda_runtime_fusion_test.cc |
Adds numerical, placement, and pre-SM80 rejection tests. |
onnxruntime/test/optimizer/moe_sources_test.cc |
Tests matching, admission, ownership, stale offers, and pattern limits. |
onnxruntime/test/optimizer/contrib_source_replacement_policy_test.cc |
Validates MoE declaration scope and numerical metadata. |
onnxruntime/core/providers/cuda/cuda_execution_provider.cc |
Adds MoE selection with an actual-device SM80+ gate. |
onnxruntime/core/optimizer/pattern_rewriter.h |
Declares configurable pattern limits and batched matching. |
onnxruntime/core/optimizer/pattern_rewriter.cc |
Implements bounded larger patterns and scan-local preparation. |
onnxruntime/core/optimizer/cuda_runtime_fusion.h |
Defines MoE configuration and rule identifiers. |
onnxruntime/core/optimizer/cuda_runtime_fusion.cc |
Collects factories and materializes declarations after admission. |
onnxruntime/core/optimizer/cuda_runtime_fusion_internal.h |
Exposes internal factory-based selection. |
onnxruntime/core/graph/contrib_ops/source_replacement.h |
Adds the mathematical-semantics numerical profile category. |
onnxruntime/core/graph/contrib_ops/source_replacement_policy.cc |
Accepts the new numerical profile category. |
onnxruntime/contrib_ops/cuda/fusion/scaled_silu_sources.cc |
Adds a lightweight ScaledSiLU factory. |
onnxruntime/contrib_ops/cuda/fusion/moe_sources.h |
Declares the fixed-configuration structural test helper. |
onnxruntime/contrib_ops/cuda/fusion/moe_sources.cc |
Implements MoE patterns, guards, snapshots, probing, and replacement. |
onnxruntime/contrib_ops/cuda/fusion/cuda_source_replacements.h |
Declares MoE sources and shared factory metadata. |
onnxruntime/contrib_ops/cuda/fusion/causal_conv_with_state_sources.cc |
Adds a lightweight causal-convolution factory. |
docs/design/Extensible_Runtime_Fusion.md |
Extends the declaration design with bounded MoE support. |
docs/Cuda_Runtime_Fusion.md |
Documents MoE configuration, eligibility, and validation boundaries. |
cmake/onnxruntime_optimizer.cmake |
Includes MoE host companions in the optimizer build. |
AGENTS.md |
Adds operator and fusion numerical-semantics guidance. |
Review effort: Balanced
Use identical-input source and fused CUDA graphs as the equivalence comparison, retain a separate tighter fused/direct target check, and remove the independent double-reference accuracy gate. Preserve matrix coverage, placement assertions and production kernels. Document the graph-replacement validation boundary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve both the MoE numerical-contract regression and new opset-domain regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The MoE rewrite can admit shapes whose replacement exceeds CUDA’s grid-Y limit and fails at execution.
6 open issues
Set a validated TF32 error budget for the H200 provider test Document optimizer settings needed to preserve MoE topology · New Cap admitted N to avoid exceeding CUDA grid-Y limit · New Filter candidate roots before full pattern matching · New Document factory registration and lazy rule materialization · New Update pattern reference for enlarged limits and batched scans · New
Review effort: Max
Add metadata-only routing-grid boundary regressions, skip matcher preparation for unrelated Add roots using a necessary producer check, and refresh application configuration, lazy catalog and PatternRewriter limit documentation. Production kernels and numerical tolerances are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep TF32-off source equivalence and fused/direct checks in both precision modes. Retain all TF32-on source execution, placement, shape and finite-output checks without gating on a cross-kernel rounding comparison. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Continue requiring the seven replacement attributes and absent swiglu_limit. Permit only the additional schema-defaulted integer swiglu_limit_after_activation=0; reject other extra attributes and nondefault activation-limit behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Accepted large MoE dimensions can underallocate the FC1 buffer by 448 bytes due to float-based workspace sizing, allowing gated activation to corrupt adjacent workspace and routing scales.
3 open issues
6 resolved since last review
Set a validated TF32 error budget for the H200 provider test Filter candidate roots before full pattern matching Cap admitted N to avoid exceeding CUDA grid-Y limit Document optimizer settings needed to preserve MoE topology Update pattern reference for enlarged limits and batched scans Document factory registration and lazy rule materialization
Review effort: Max
Reject downward-rounded FP32 workspace sizes, append metadata-only host configuration validation, and split the unchanged CUDA matrix into 96 independent cases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep all distinct CUDA configurations, consolidate near-miss coverage on production discovery, and retain fixed-candidate route-K rejection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>



Description
Add default-off, op-owned CUDA MoE source fusion on top of #34022.
ep.cuda.runtime_fusion.moeindependently and require actual SM80+ hardware before offering FP32 MoE.No production kernel changes, expanded existing-pilot matching scope, default enablement or initializer transformations are included.
Earlier review follow-ups: all five findings in the earlier assessed Copilot review are addressed:
65535 * 1024for the existing CUDA routing fallback's grid-Y limit. Metadata-only tests cover the limit and one past it, including no target lookup or graph mutation on rejection.ConstantFolding;TransposeOptimizerdisabling where needed to preserve the admitted immutable-bank topology. The MoE flag does not change ordinary optimizer behavior.Numerical integration scope: remove only the TF32-on source/fused blocking assertion, rather than increasing its tolerance or changing an existing kernel. Source MatMul can use TF32, whereas the existing FP32 MoE dispatch uses SIMT. This is a cross-kernel floating-point-path comparison, not a replacement-wiring accuracy contract.
The original TF32-off source/fused bound remains atol=1e-5, rtol=2e-4. Fused/direct consistency retains exactly that bound with TF32 both disabled and enabled. All 88 distinct fixture combinations and their 264 source/fused/direct sessions remain after deduplicating repeated E=2/k=2 inputs, including the original source execution in TF32-on mode. No independent high-precision kernel-accuracy gate is added. The numerical-profile description and CUDA fusion documentation now state this scope consistently.
For provenance, the earlier head
2453a226a4completed the original CUDA matrix with one TF32-on source/fused assertion failure at E=64, k=1, N=7, scaled inputs, element 144: difference 0.0020274445414543152, former bound 0.0019924522042274475. The latest passing matrix uses the explicitly revised integration scope; it is not evidence that this cross-kernel difference or kernel accuracy has been fixed.Latest review fixes: addressed all three inline items in review 5851521402.
Motivation and Context
Extend the op-owned source-declaration path to MoE without a second EP-wide rewrite registry. Keep the existing ScaledSiLU and CausalConvWithState pilots unchanged while testing a larger family of alternatives.
This PR depends on #34022 and remains in native stack #34023 with ordered history #34021 (merged), #34022, #34086. Updates preserve published history; no force-push or default enablement.
Current test-only cleanup (2026-11-11)
Parent:
04f2a6e58cb216fc182b8955b48018d448eed66f; combined stack:b776d13387af86c78036e48262ca8b7cdd05f1f9. Normal commits/merge and fast-forward publication only. The final committed test contents match the validated source overlay byte-for-byte; production code, kernels, matching scope, default-off behavior and numerical gates are unchanged.Remove owner-policy English-reason coupling and isolate malformed/duplicate registered-schema and legacy-key failures. Complete the other declarations so missing-policy rejection cannot mask schema-key validation.
Deduplicate the CUDA matrix from 96 to 88 distinct cases / 264 source-fused-direct sessions: compiled and executed the actual old/new parameter generators using ORT containers, proving identical distinct configurations and unique parameter names. Remove the diagnostic-only k-choice field. Consolidate the nine near-miss mutations at E=8/16/64 on production discovery, retaining no-lookup/no-mutation assertions and the separate fixed-candidate wrong-k regression. Remove one duplicate host E=2/k=2 fixture, unasserted timing output and the exact 63-plan implementation assertion; single-index preparation and matching-result checks remain.
Validation: passed on the combined source overlay: fresh CPU rebuild and 45/45 targeted host tests, zero failures/errors/skips; actual CUDA test translation-unit compilation with the configured CUDA-build C++ flags; canonical CLANGFORMAT and whitespace checks. The compiled old/new generator comparison passed (96 generated cases versus 88 unique cases, equal distinct configurations). Temporary generator source/executable were removed afterward.
Exact commands and results:
Not rerun for this test-only cleanup: GPU numerical execution, full/cross-platform suites, and standalone schema consumers. The older 386-host/98-GPU results below are historical, not reruns on these heads. The 45-test run used the combined stack, not a separate parent-only build. No new-head CI success is claimed.
Previous CI policy validation after syncing main
0b22d27a85(before test-only cleanup)Parent:
5362926ba8374fa250943346647c9269212db011; combined stack:e63f8268ea5d7c4113c861ef4d9a1418694ce12a. Normal merges only; no history rewrite.Upstream #33901 added
MatMulNBitsQLoRAMlp-1andMatMulNBitsQLoRAQkv-1. Both now authorPendingTodobeside their schema definitions; neither is added to the frozen legacy baseline or advertised as an executable source. The existing regression covers both exact keys, unqualified variants, CPU/CUDA/WebGPU, rejection of version 2, and rejection after removing either policy. This follow-up does not edit kernels, matching scope or default-off behavior.Passed: 386/386 CPU-build host tests on the exact clean combined commit, including full inventory and the extended owner-policy regression; 2/2 standalone schema consumers; all 3 disabled-contrib syntax probes (
bert_defs.cc,contrib_defs.cc,training_op_defs.cc, with both training macros); canonical CLANGFORMAT and diff whitespace checks. The host/schema builds used the newly pinned Eigen archive (SHA118eca78e961ac5e9123ccdff5035ab340dcc5e11), isolated from the earlier Eigen 3.4 cache. At most four compilation jobs. Source-tree identity and artifact hashes were checked before publication.Exact build/test commands (all passed; tests run from their respective build output directories):
Exclusions for that previous main-sync follow-up: no GPU workload or CUDA kernel rebuild was performed for this metadata-only follow-up. The 98-case CUDA result below belongs to the earlier pre-main-sync tree and is not qualification of the new merged head. Windows/macOS/Android/Web/plugin execution and full upstream integration coverage remain CI responsibilities; new-head CI is not claimed green. The bounded two-file authored diff was reviewed directly; the upstream commit set was not independently re-reviewed.
Previous review-fix validation before the main sync
Previous review-fix validation:
9dcc6903bba388e5d7a5d2506db1a88e4a50fb52Parent-only overlay: 358/358 host, 2/2 real CUDA EP, 2/2 standalone schema consumers passed. That run preceded only extraction of an unchanged test DSL literal into a constant.
The final combined stack passed 386/386 host, 98/98 real CUDA EP tests (96 MoE cases / 288 sessions plus the two existing pilots), 2/2 standalone schema consumers, and disabled-contrib syntax probes for
bert_defs.cc,contrib_defs.cc, andtraining_op_defs.ccwith both training macros. CUDA execution used idle physical GPU 3, H200/sm90, under the fail-closed guard; zero gtest failures/errors/skips. The newly built task-owned CUDA provider was loader-verified and artifact hashes stayed unchanged during execution. Four build jobs, NVCC threads=1.Commits were created after validation; their merged tree was verified byte-for-byte against the compiled final source overlay before normal atomic publication. These are source-tree-qualified runs, not a claim that embedded build revision strings were regenerated after committing. The validation tree is clean at the published merge head.
Commands below ran from the existing build-output directories with the existing CUDA/cuDNN loader paths. Each completed successfully:
Canonical
lintrunner --take CLANGFORMAT <changed C++ paths>andgit diff --checkpassed in both working trees. Lintrunner used the existing virtual environment plusPYTHONPATH=/usr/lib/python3.12/site-packages; the earlier adapter blockage is resolved without installing dependencies. The task-created driver cache was removed.Previous exclusions: actual pre-SM80 execution is not qualified on H200; host tests cover metadata validation without materialization/lookup and the actual-device regression was extended for a future pre-SM80 run. Actual Windows/MSVC, full training/plugin suites, other GPU architectures, authentic fixtures and whole-model accuracy/performance were not run locally. No new high-precision kernel-accuracy gate is added. New-head CI is reported separately, not claimed green. The earlier Android comparison below is historical, not a new-head size measurement. No new full-team/model-diversity review coverage is claimed.
Historical validation and CI follow-ups (earlier heads only)
Earlier CUDA validation baseline:
c4d2e5d38870b8890f9be75facc2f52c0184b355git diff --check. Canonical lint could not complete becauselintrunner_adaptersis missing; no dependencies were installed.Commands ran with the existing virtual environment activated, from
/mnt/data/titaiwang/runtime-fusion/moe-fusion-integration-20261109/cuda/Release, using task-owned CUDA/cuDNN loader paths. The actual CUDA matrix used physical GPU 4 under the fail-closed resource guard, with 30-second per-query telemetry timeouts and a 30-minute workload budget. Sampled telemetry is not a GPU lease.Earlier review-fix baseline, not a fresh current-head rerun:
d8595ee30dpassed 382/382 host regressions and 95/95 provider regressions, explicitly excluding the then-failing MoE numerical matrix. The c4d2e5d baseline above covers that complete matrix under the approved scope. Parent #34022 independently passed its CUDA build, 356 host tests, 95 provider tests, and standalone schema consumer.Earlier CI compatibility follow-up:
2a14039f224f48e3677f9724ba1ee22d45592187Normally merge #34022's schema-macro portability fix. Update only the test assertions: preserve strict checks of the seven replacement attributes and absent
swiglu_limit, while permitting only the compatible upstreamswiglu_limit_after_activationINT default 0. Its presence does not enable clipping; wrong types, nonzero values, or other extra attributes still fail. No kernel, matcher, admission, default-off, or numerical-gate changes.08839905fd90ef959b55bd930e57a23aa771f2d6before the final schema-only follow-up: 96 fixture combinations / 288 source/fused/direct sessions, one gtest, 648.04 seconds, physical GPU 4, zero failures/errors/skips. Loader logs verify the task-owned CUDA provider and artifact hashes stayed unchanged during execution.Exact-head commands, from the existing build-output cwd and loader environment:
That follow-up's final-head host validation with only the upstream schema-default overlay:
Complete matrix on runtime-identical precursor
08839905fd90ef959b55bd930e57a23aa771f2d6with that overlay:/mnt/data/titaiwang/runtime-fusion/moe-fusion-integration-20261109/cuda/Release/onnxruntime_provider_test --gtest_filter=CudaRuntimeFusionTest.MoESourceFusedDirectTF32OffAndOn --gtest_output=xml:/home/titaiwang/.copilot/session-state/07b6ecde-d659-4b29-aab6-78ba683dbbb3/files/fusion-ci-20261110T221446/validation-overlay/cuda.xml # Passed.Changed C++ ranges match clang-format 18 and pass
git diff --check. Canonical lintrunner was attempted for the changed files but was then blocked by missinglintrunner_adapters; no dependencies were installed.Known upstream Android baseline failure: the exact upstream main commit
d7ab0d271b0399d889c6b9540cacf04a84b74333fails the same gate: section total 1,611,530 bytes versus threshold 1,610,778, exceeding it by 752 bytes. #34022's captured earlier CI build reports the same total, on-disk size, and all 27 section sizes. This measured size failure is already present upstream; these fixes do not raise the threshold or claim the gate is green.Exclusions recorded for that earlier follow-up: new-head full/cross-platform CI, including actual Windows/MSVC, is pending. The diagnosed 8-versus-7 local assertions are corrected and covered under both schema variants, but CI is not claimed all-green. Other GPU architectures, actual pre-SM80 execution, optional authentic fixtures, whole-model accuracy/performance, and earlier fixed-model review gaps remain unqualified. TF32-on cross-kernel accuracy remains outside this integration gate. This follow-up received direct bounded review with no actionable findings, not another whole-team review.