Repository navigation
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
7709829 to
5cc7afe
Compare
There was a problem hiding this comment.
🟡 Changes recommended
CPU eligibility regressions and the missing advertised permutation-dispatch update remain unresolved.
3 open issues
What changed in this PR
Generalizes DeepSpeed’s Triton guards and device contexts to support non-CUDA accelerators with an active Triton backend.
Changes:
- Adds shared accelerator-tensor checks and current-device switching.
- Enables NPU Triton detection and broadens runtime eligibility.
- Adapts kernel and AutoEP tests for multiple accelerator types.
| File | Description |
|---|---|
| tests/unit/v1/runtime/test_chunked_cross_entropy.py | Broadens backend coverage and adapts rejection checks. |
| tests/unit/v1/ops/triton_ops/test_swiglu_triton.py | Generalizes availability and device-context tests. |
| tests/unit/v1/ops/triton_ops/test_group_gemm_triton.py | Uses accelerator capability checks. |
| tests/unit/v1/ops/triton_ops/test_fused_rotary_pos_emb.py | Generalizes device tests and layout comparisons. |
| tests/unit/v1/ops/triton_ops/test_fused_rms_norm.py | Updates availability, errors, and layout checks. |
| tests/unit/v1/ops/triton_ops/test_autoep_fused_token_ops.py | Broadens eligibility and updates error assertions. |
| tests/unit/v1/moe/test_autoep_unit.py | Generalizes device cases and protocol doubles. |
| tests/unit/v1/moe/test_autoep_permute_rows.py | Enables non-CUDA accelerator cases. |
| tests/unit/v1/moe/test_autoep_grad_parity.py | Broadens async split-plan test eligibility. |
| deepspeed/runtime/chunked_cross_entropy.py | Uses accelerator-aware backend selection. |
| deepspeed/ops/triton_ops/swiglu_triton.py | Uses shared current-device contexts. |
| deepspeed/ops/triton_ops/fused_rotary_pos_emb.py | Generalizes tensor guards and launch contexts. |
| deepspeed/ops/triton_ops/fused_rms_norm.py | Uses the shared tensor guard. |
| deepspeed/ops/triton_ops/autoep_fused_token_ops.py | Generalizes guards and device errors. |
| deepspeed/ops/triton_ops/_triton.py | Adds tensor eligibility and device-context helpers. |
| deepspeed/module_inject/auto_ep_layer.py | Broadens async split-plan tensor eligibility. |
| accelerator/npu_accelerator.py | Delegates Triton support to the shared probe. |
Review effort: Balanced
Signed-off-by: Zizhao Tang <1601677496@qq.com>
5cc7afe to
9488bd6
Compare
|
Very significant! |
Signed-off-by: Zizhao Tang <1601677496@qq.com>
Extend is_triton_supported with a per-kernel probe on CUDA and NPU and route every Triton call site (swiglu, packed swiglu, grouped GEMM, fused RMSNorm, fused RoPE, fused weighted restore and row weighting, chunked cross entropy, fp8 GEMM, liger vocab-parallel cross entropy, permute rows) through it, so each platform validates each kernel on first use and caches the answer; probes live in one library (deepspeed/ops/triton_ops/_probes.py). The single-element packed SwiGLU backward (upstream triton-ascend unified-buffer compile crash) now falls back to the separate kernel. Signed-off-by: Zizhao Tang <1601677496@qq.com>
Probe failures the backend only reports asynchronously now fail the probe via a device synchronize after the run; the per-kernel gate pre-checks the accelerator signature instead of a broad TypeError catch and fails closed when the probe cannot even be built; probes pin the device and fork the RNG so they neither run on the wrong device nor advance the caller's generator; the fp8 gate key carries the weight dtype and group size; the probe cache is thread-safe, warns once on failure, and honors DS_TRITON_KERNEL_GATE=0 as an escape hatch back to the backend-level gate. Signed-off-by: Zizhao Tang <1601677496@qq.com>
Pin the probe mechanism's contract on any accelerator with an active Triton backend: the probe runs once per process per key and later calls answer from the cache, a failing probe closes the gate with a cached False, probes leave the caller's RNG sequence unchanged, and DS_TRITON_KERNEL_GATE=0 bypasses probing. Signed-off-by: Zizhao Tang <1601677496@qq.com>
Hi, @jinyouzhi .Sorry I hadn't noticed issue #8789 earlier — it's a great discussion and raises some really good points. I was wondering if we could improve is_triton_supported() by having generic probes check Triton compatibility on the running hardware at runtime, so each platform doesn't have to keep its own blacklist. I've tried this out in the current PR and also left a note over in #8789. Feel free to take a look when you get a chance — happy to rework it if this approach doesn't fit. |
The probe's tensors consume the accelerator's device generator, so the RNG-isolation test now seeds and samples that generator (via the accelerator abstraction) instead of only the CPU one. Signed-off-by: Zizhao Tang <1601677496@qq.com>
Thank you for your reply. I think this idea and practice very useful and potential to be genetic for all backends. Could you also share your opinion and results into the issue? |
Of course, very happy to discuss this further! I've already posted the details in #8789 |
Probe tensors are now deterministic constants instead of random draws: on NPU, torch_npu's set_rng_state is a no-op and device Generators are not independent, so no fork-based RNG isolation can be sound there. With no draws the caller's generator is never touched by construction and the probe is reproducible run over run. - Synchronize the current stream instead of the whole device after the probe run so unrelated streams are neither waited on nor faulted here - Defer the probe while the current stream is being captured (CUDA graphs) - Guard index-less devices in the fp8 probe's device switch - Drop the precision-loss promise from the probe docstring; no probe passes a reference comparison - Rework the RNG-isolation test into the control comparison: the first draw after seeding, with the probe in between, must equal the same draw without the probe (red against the previous fork-based probes, green with the deterministic inputs, verified on Ascend 910B4) Signed-off-by: Zizhao Tang <1601677496@qq.com>
The indexed-gather path is probe-unsafe, so it is declared statically: the per-kernel gate gains a probe-less form (probe_fn=None) where the accelerator answers for the kernel name alone, and the NPU accelerator returns False for permute_rows_gather until the upstream fix lands. Removes the is_triton_gather_supported hook from the abstract accelerator, keeping every platform's knowledge in its own accelerator file. Signed-off-by: Zizhao Tang <1601677496@qq.com>
Follows the op_builder layout for Triton kernel variants: the accelerator answers its triton_ops subpackage (triton_ops_dir, None for portable), and the shared resolve_triton_ops_impl loads the platform module with a portable fallback, cached per feature. First variant: the Ascend packed SwiGLU backward lowers its block size to 1024, the triton-ascend workaround for the single-element unified-buffer compile failure (reported upstream). The portable entry point resolves the packed class through the dispatch and the kernel probes resolve the same class, so the gate tests exactly the implementation the production path runs. Platforms without a variant see no change. Drop the single-element packed-SwiGLU special case: with the Ascend variant the shape compiles, so the shared code keeps no shape-specific gate. Signed-off-by: Zizhao Tang <1601677496@qq.com>
The per-hardware dispatch becomes an annotation on each public function: dispatch(feature, portable_module=None) runs the platform's same-named function when its triton_ops subpackage defines one and the portable original otherwise (a re-exported decorated function counts as "no override", recognized by identity, so a variant can never recurse). Every DeepSpeed-owned Triton kernel entry on the training path carries the annotation - swiglu / swiglu_packed, grouped GEMM, fused RMSNorm / RoPE, weighted-restore / row-weighting, vocab-parallel softmax, chunked cross entropy, fp8 GEMM, and the MoE permute / gather kernels - so a platform variant plugs in without touching any call site; kernels outside triton_ops pass their portable module and keep their variants under the platform's triton_ops subpackage. The Ascend variant stays the single fork: it overrides swiglu_packed by name and only lowers the packed backward's block size to 1024, sharing the portable module's validation, runtime gate, and eager fallback. Signed-off-by: Zizhao Tang <1601677496@qq.com>
The Ascend triton_ops subpackage now ships variants for every training-path Triton kernel flash-linear-attention implements on triton-ascend, following its backends' Ascend patterns: - triton_ops/npu/_ub.py: a unified-buffer manager ported from FLA's ascend_ub_manager (capacity detection via ASCEND_UB_CAPACITY_BITS or the tbe SOC spec with a 64 KiB fallback, and the power-of-two tile helpers), so the variants budget their tiles instead of fixed constants. - swiglu / swiglu_packed: flat UB-budgeted tiles (the backward gets the larger multiplier and therefore the smaller tile); the packed backward keeps the verified 1024 workaround for the upstream single-element unified-buffer compile failure. - fused_rms_norm: FLA's BT-row tiling with the weight resident per program on a grid-stride loop, keeping the portable cast-before-gamma roundings; the dweight partial kernels are reused verbatim (their reduction order is part of the eager contract). - fused_apply_rotary_pos_emb: an FLA-budgeted sequence tile, reusing the portable kernel unchanged. - chunked_cross_entropy: FLA's split-vocabulary [rows, splits] grid with its split reduction (each tile stores the negative target logit, the final log-sum-exp is added once host-side, ignored rows are masked). The variants keep the portable kernels' eager-mirroring arithmetic, so the numerical contract the unit tests assert is unchanged; the FLA-derived structure changes launch shapes only. The four kernel probes whose features gained variants now resolve the class through the same dispatch as the production path, so the gate keeps testing exactly what production runs. Features FLA does not implement (grouped GEMM, vocab-parallel softmax, weighted-restore / row-weighting, fp8 GEMM, MoE permute / gather) keep the portable kernels. Verified on Ascend 910B4 with triton-ascend 3.6.0: triton_ops 302 pass / 9 fail / 7 skip with the 9 failures exactly the baseline set (6 packed-vs- separate SwiGLU bitwise, 2 row-weighting, 1 environmental OOM); AutoEP and chunked cross entropy suites pass; the variant kernels stay within 1 ULP of the portable kernels on device. Signed-off-by: Zizhao Tang <1601677496@qq.com>


Background
DeepSpeed has already embraced Triton for a growing set of fused kernels — the recently developed AutoEP (Automatic Expert Parallel) features, for example, rely on Triton kernels for token permutation and row weighting, alongside the grouped GEMM (General Matrix Multiply), SwiGLU, fused RMSNorm, and fused RoPE paths. As with Triton itself, that integration has so far been CUDA-first: the guards and tests naturally reference CUDA, the reference platform. In the meantime, the Triton ecosystem has expanded well beyond CUDA — several hardware platforms now ship their own Triton backends, for example the triton-ascend backend for Ascend NPUs — and a backend that reports itself through the standard driver probe can run the same kernels. This PR carries DeepSpeed's Triton integration toward accelerator-agnosticism: the device guards, launch contexts, and tests stop naming a specific device, and the Triton gate follows the shared driver probe, so every platform can benefit wherever its backend reports support.
A backend-level gate is, however, too coarse once a backend is open: a kernel-level backend bug would still crash at runtime. The PR therefore adds a second, per-kernel gate: each Triton call site validates its own kernel on first use through a runtime probe — a minimal forward+backward run on the live device, cached per process — so the platform answers for itself kernel by kernel, no blacklist to maintain, and a backend fix flips the gate automatically. Finally, where the fix is not a gate answer but an implementation difference, the PR adds the third piece, per-hardware kernel variants through an annotation on the public function: every DeepSpeed-owned Triton kernel entry on the training path carries a dispatch decorator and is itself the generic fallback; an accelerator names its own
triton_opssubpackage (triton_ops_dir, op_builder-style) and a platform module overrides the function by name — the first variant, shipped in this PR, lowers the Ascend packed SwiGLU backward toBLOCK_SIZE=1024(the workaround for the triton-ascend single-element unified-buffer compile failure). CUDA remains the primary, fully supported platform, and every default path behaves exactly as before.Summary
Layer 1 — device-agnostic guards and the shared driver probe. Removes the CUDA hard-coding from the
triton_opsdevice guards, launch contexts, and tests, and routes the Triton gate through the shared Triton driver probe so the fused kernels can run on any accelerator that reports a working Triton backend (CUDA first, others — e.g. NPU via triton-ascend — where their backend is installed). With no Triton backend installed,is_triton_available()isFalseand every gate stays closed — no behavior changes on any platform; this is a guard refactor, not a kernel change.Layer 2 — per-kernel runtime probes. The Triton gate becomes per-kernel and self-reporting:
is_triton_supported(kernel=None, probe=None)gains optional per-kernel parameters on CUDA and NPU. Without arguments it keeps today's backend-level meaning; with aprobe, the backend actually compiles and runs the kernel's minimal risky usage on the live device once per process and caches the answer, keyed bykernel:device:dtype.swiglu,swiglu_packed,group_gemm, fusedRMSNorm, fusedRoPE, fusedweighted_restore/row_weighting, chunked cross entropy, fp8 GEMM, liger vocab-parallel CE, andpermute_rows— routes through this gate with its probe defined in a single library (deepspeed/ops/triton_ops/_probes.py), so probes never scatter across call sites.DS_TRITON_KERNEL_GATE=0is an emergency escape hatch back to the backend-level gate.permute_rowsgather path remains a narrow, probe-unsafe static declaration in the per-kernel gate (the NPU accelerator answers False for the kernel name; multi-card device fault, upstream issue in the comment).Layer 3 — per-hardware kernel variants, dispatched by annotation. Where the fix is an implementation difference rather than a gate answer, the platform ships its own variant:
@dispatch("<feature>")and is itself the generic fallback; a platform overrides it by defining a same-named function in itstriton_opssubpackage (re-exporting the decorated function counts as "no override", so a variant can never recurse). Every DeepSpeed-owned Triton kernel entry on the training path carries the annotation —swiglu/swiglu_packed, grouped GEMM, fused RMSNorm / RoPE, fused weighted-restore / row-weighting, vocab-parallel softmax, chunked cross entropy, fp8 GEMM, and the MoE permute / gather kernels — so a platform variant plugs in without touching any call site. (The liger vocab-parallel CE wrapper is excluded: it wraps a third-party kernel, and the gate already covers it.) Kernels outsidetriton_opspass their portable module to the decorator; platform variants still live under the accelerator'striton_opssubpackage.abstract_accelerator.triton_ops_dir()answers the accelerator'striton_opssubpackage (None= portable, the default); the NPU accelerator points atdeepspeed.ops.triton_ops.npu. The resolver loads the platform module with a portable fallback and caches per feature; a subpackage may hold a single kernel.Problem
Hard-coded CUDA assumptions in
deepspeed/ops/triton_opsfused_rms_norm.py,fused_rotary_pos_emb.py, andautoep_fused_token_ops.pyreject any tensor withdevice.type != "cuda"("runs CUDA kernels"), even though the kernels are written in Triton and the runtime device is whatever the accelerator reports.torch.cuda.device.swiglu_triton.py(packed forward/backward) andfused_rotary_pos_emb.pyswitch devices withtorch.cuda.device(x.device), which breaks on CPU-built torch and on non-CUDA accelerators.NPU_Accelerator.is_triton_supported()returnsFalseunconditionally, so all Triton paths fall back on NPU even when a working Triton backend (triton-ascend) is installed.device_name() == "cuda"; bfloat16 capability is queried viatorch.cuda.get_device_capability()(crashes on CPU-built torch); guard-message assertions expect the "CUDA kernels" wording; two-device stream regressions usetorch.cuda.devicecontexts; the sm-capability auto-selection test has no device guard at all.A backend-level gate cannot catch kernel-level backend bugs
MLIRCompilationError, upstream unified-buffer overflow), several fp32/fp16 kernels differ from eager by 1 ulp, and thepermute_rowsgather faults the device in multi-card runs. A backend-level gate either leaves the whole platform closed (the oldreturn False) or opens every kernel including the broken ones.fp8_gemm, liger vocab-parallel CE, the inference attention flag), so they flip open on NPU without any per-kernel validation.Fix
Layer 1 — device-agnostic guards
npu_accelerator.py:is_triton_supported()delegates tois_triton_available()— the same shared probe as the other accelerators, which reportsTrueonly when a Triton backend's driver is active. Without triton-ascend the gate stays closed, preserving today's fallbacks._triton.py: addsis_accelerator_tensor(), delegating to the accelerator'son_accelerator(). This is the single place in the package that lazily importsget_accelerator, keeping the module import-light.fused_rms_norm.py/fused_rotary_pos_emb.py/autoep_fused_token_ops.py: device guards use the helper; error messages say "runs Triton kernels"; the "same CUDA device" message becomes "same device".swiglu_triton.py/fused_rotary_pos_emb.py:torch.cuda.device(...)launch contexts become device-agnostictorch.device(...)contexts.ep_kernels.py:permute_rows_supportedaccepts any accelerator tensor; docstrings say "accelerator tensor" instead of "CUDA tensor".tests/unit/v1/ops/triton_ops/): module-level gates useis_triton_supported(); bfloat16 checks useis_bf16_supported(); guard-message assertions expect the Triton wording; two-device contexts usetorch.device(accelerator.device_name(...)); the sm-capability auto-selection test is marked CUDA-only.Layer 2 — per-kernel runtime probes
accelerator/abstract_accelerator.py:is_triton_supported()keeps its contract.cuda_accelerator.py/npu_accelerator.py:is_triton_supported(kernel=None, probe=None)— the backend-level answer without arguments; with a probe, the sharedprobe_triton_kernelhelper compiles, synchronizes, and caches.npu_accelerator.pydeclares the gather exception for thepermute_rows_gatherkernel name (return False, upstream issue in the comment)._triton.py:probe_triton_kernel(key, run, reference=None, tolerance=None)— one run per process; a current-stream synchronization after the run (not a device-wide one) so asynchronous faults fail the probe here, not on the first real workload, without waiting on unrelated streams or surfacing their errors; optional bitwise / allclose comparison against a reference; thread-safe cache; fail-closed with a one-time warning.is_triton_kernel_supported(kernel, dtype, device, probe_fn, *args)builds the probe only on a cache miss, so the steady-state hot path is one dict lookup, defers the probe while the current stream is being captured (CUDA graphs), and supports a probe-less static declaration (probe_fn=None) where the accelerator answers for the kernel name alone — the probe-unsafe paths._probes.py(new): one probe per kernel — a minimal forward+backward on the caller's device and dtype, wrapped in the device context. Probe inputs are deterministic constants, not random draws, so a probe never touches the caller's RNG state by construction and is reproducible run over run. (Necessary: on NPU, torch_npu 2.10.0.post7'sset_rng_stateis a no-op and deviceGeneratorobjects share the default generator's state, so no fork/save-restore-based isolation can be sound there.)swiglu/swiglu_packedfall back to eager;group_gemm_tritonfalls back totorch._grouped_mm; fused RMSNorm / RoPE / weighted-restore / row-weighting raise their existing "unsupported" errors, which their dispatchers already turn into the eager path; chunked CE resolvesautototorchwhen the probe fails;fp8_gemmand the liger gate take the same per-kernel probe.permute_rows_supportedroutes through the per-kernel gate as a probe-less static declaration (is_triton_kernel_supported(..., None)) instead ofdevice.type == "cuda".tests/unit/v1/ops/triton_ops/test_triton_probe.pypins the gate contract (run-once cache, fail-closed cached answer, RNG isolation, escape hatch) on any accelerator with an active Triton backend.Layer 3 — per-hardware kernel variants, dispatched by annotation
accelerator/abstract_accelerator.py:triton_ops_dir()returnsNoneby default — backends opt in only when they ship a variant, so the 11 existing platforms need no change.accelerator/npu_accelerator.py:triton_ops_dir()returnsdeepspeed.ops.triton_ops.npu._triton.py:resolve_triton_ops_impl(feature, portable_module=None)— asks the accelerator, imports the platform module, falls back to the portable one on a miss, and caches per feature — and thedispatch(feature, portable_module=None)decorator: the decorated portable function runs the platform's same-named function when it exists, the portable original otherwise (steady state: one dict lookup + one getattr per call).triton_ops/npu/swiglu_triton.py(new): definesswiglu_packed— validation, the runtime gate, and the eager fallback are the portable module's shared helpers, and only the packed backward'sBLOCK_SIZEis lowered to 1024 (portable kernel source reused unchanged); the module stays importable without a device or a Triton install, so resolution never depends on hardware.swiglu_triton.py:swigluandswiglu_packedcarry@dispatch("swiglu_triton");_validate_packed_input/_swiglu_packed_eager/_swiglu_packed_supportedare factored out for the variant; the_swiglu_packed_1elem_supportedspecial case and its probe are deleted (the shared code keeps no shape-specific gate).fused_rms_norm.py/fused_rotary_pos_emb.py/autoep_fused_token_ops.py/group_gemm_triton.py/vocab_parallel_softmax.py: every public kernel entry carries@dispatch("<module>").deepspeed/runtime/chunked_cross_entropy.py,deepspeed/ops/fp_quantizer/fp8_gemm_triton.py,deepspeed/moe/ep_kernels.py: entries carry@dispatch("<feature>", portable_module=<own module>); the resolver's portable path takes the module name, so out-of-package kernels dispatch the same way with their variants under the platform'striton_opssubpackage._probes.py: the packed probe resolves the class through the same dispatch (it must not call the public function itself — that would re-enter the gate), so the gate tests the platform's variant.tests/unit/v1/ops/triton_ops/test_triton_dispatch.py(new) pins the resolver contract with a duck-typed accelerator double — no subpackage resolves portable, a subpackage resolves the platform module, an unoverridden feature falls back to portable, a missing feature raises, an out-of-package feature resolves its explicit portable module — plus three decorator contract tests — the platform override runs, no override falls back to the original, a re-exported wrapper counts as "no override" — on any host, no device required.Runtime-detection mechanics
kernel:device:dtypeDS_TRITON_KERNEL_GATE=0disables probing globallyKnown limitations (deliberate, documented)
MLIRCompilationErrorare Python exceptions). Hard native crashes (segfault/abort) are outside thetry/exceptmodel; none has been observed, and subprocess isolation is a possible follow-up if one appears.combine_impl="fused_weighted_sum",row_weighting_impl="fused") fail fast with a clear error, while drop-in APIs (swiglu,group_gemm, chunked CE, fp8) fall back silently — each matches its caller's contract.permute_rowsgather is the single static per-kernel declaration: probing a device fault is unsafe, so the NPU accelerator'sis_triton_supportedanswers False for the kernel name until the upstream fix lands.Follow-ups (explicitly out of scope)
DS_TRITON_KERNEL_GATEcould gain a force-enable mode; transient-failure retries are not evaluated.set_rng_stateno-op, non-independent deviceGenerators) are worth filing upstream with torch_npu.Compliance with the repo AI-contribution policy (AGENTS.md)
is_triton_supported+ NPU andtriton+ascend— no existing PR or issue covers opening the NPU Triton gates or device-agnosticizing thetriton_opsguards. The nearest PR, AutoEP: generate large NPU permutation indices on device #8822 (AutoEP NPU permutation indices), is closed and unmerged and touches a different path.Test plan
The affected suites run on both platforms against branch
npu-triton-support(CUDA: A100; NPU: Ascend 910B4, triton-ascend 3.6.0).CUDA (A100) — the primary platform
triton_ops(305): all pass — identical to master.test_chunked_cross_entropy.py(50): all pass — including the device-agnostic Triton-backend change.Environment: 2 × NVIDIA A100-SXM4-80GB (80 GB each), driver 580.126.16, CUDA 12.4, NCCL 2.21.5, PyTorch 2.14.1+cu126, Triton 3.8.0, transformers 5.20.2, Python 3.10.22.
NPU (Ascend 910B4, triton-ascend 3.6.0)
triton_ops(318): 302 pass / 9 fail / 7 skip — 8 of the 9 failures are the triton-ascend backend issues reported upstream and being fixed (6 packed-SwiGLU 1-ulp bitwise-vs-separate, the exact baseline set, + 2 row-weighting 1-ulp eager-parity with the bit-identical2.29e-05/3.05e-05deltas); the 9th is an environmentaltorch.OutOfMemoryErrorintest_expert_offset_exceeds_int32— the test allocates ~4.3 GiB, the device currently has ~1.3 GiB free (94% HBM occupied by colocated workloads), and the test is untouched by this PR. The 7 skips are 4 two-device cases (single-card run), 2 CUDA-only cases, and 1 memory-guarded parity case. The 13 new in-repo gate/dispatch contract tests (5 probe + 8 dispatch) are included and pass (the 4 tests over the previous run are the new dispatch decorator contract tests). The single-element packed-SwiGLU shapes now run the packed path through the Ascend variant (BLOCK_SIZE=1024) instead of any fallback.test_chunked_cross_entropy.py(50): 47 pass / 3 skip (the skips are CUDA-only memory-peak, fork-safety, and device-assert subprocess tests). The Triton backend is now exercised on the accelerator instead of only CUDA.The
torch.device(...)launch-context replacement is exercised by the NPU run of the packed SwiGLU and fused RoPE paths.Hardware: Ascend 910B4 (single device for the suites; the async split-plan parity test additionally ran multi-card), torch 2.10.0+cpu, torch_npu 2.10.0.post7, CANN 8.5.1, triton-ascend 3.6.0.
Probe layer (NPU, Ascend 910B4, triton-ascend 3.6.0)
Before/after the probe layer on the affected four files:
MLIRCompilationError)Full training-path sweep (10 files, 583 tests): 565 pass / 18 fail, where the 18 are exactly
6 swiglu + 2 row-weighting upstream-numerics failures (the row-weighting deltas
2.29e-05/3.05e-05are bit-identical at the pre-probe baseline commit) plus 10 artifacts of editing filesmid-run; rerunning the affected files cleanly gives 8 pre-existing failures, 0 introduced.
Probe cost (NPU; each measurement in a fresh subprocess with an empty Triton compile cache;
the benchmark script is kept out of the repo):
swiglu32×16 bf16Two probe catches verified end-to-end: the packed SwiGLU 1-element probe absorbs the
MLIRCompilationErrorand the three tests go green via the fallback; the fp8 probe closesthe gate on NPU (no
float8_e4m3fnsupport) where the open gate previously crashed at runtime.Both still hold after the probes switched to deterministic inputs.
Per-hardware dispatch (NPU, Ascend 910B4, triton-ascend 3.6.0)
resolve_triton_ops_impl("swiglu_triton")resolvesdeepspeed.ops.triton_ops.npu.swiglu_tritonon NPU, and the variant overridesswiglu_packedby name — the decorated portable function dispatches to the variant's own function and keeps the portable implementation as its fallback, with_PACKED_BWD_BLOCK_SIZE == 1024(pinned on-device by the verification script below).allclose; packed-vs-eager is not bitwise on Ascend, the upstream numerics issue).test_triton_dispatch.py(8): all pass, no device required — also verified in a Triton-less process, which additionally proves every decorated module imports cleanly without a Triton install and the dispatched chains answer through their eager fallbacks.test_triton_probe.py(5): all pass (re-run after the dispatch change).swiglu_triton, the portable module for the other eight.Verification script (kept out of the repo):
test_npu/deepspeed/roadmap2026q4/triton_ascend/verify_dispatch_npu.py—ALL CHECKS PASSEDon Ascend 910B4.Probe-gate unit tests (in-repo, platform-agnostic)
tests/unit/v1/ops/triton_ops/test_triton_probe.pypins the gate's contract — thewall-clock costs are the benchmark numbers above, not CI assertions:
test_probe_runs_once_and_later_calls_answer_from_the_cache— the probe factory runs exactly once; later calls answer from the cache without running or constructing the probe.test_failing_probe_closes_the_gate_with_a_cached_false— a raising probe closes the gate with a cachedFalse, and later calls keep the fallback without re-running any probe.test_probe_does_not_advance_the_caller_rng_sequence— control comparison: the first draw after seeding, with the probe run in between, must equal the same draw without the probe. On NPU this test is red against the previous fork-based probes and green with the deterministic inputs (verified on Ascend 910B4).test_env_escape_hatch_bypasses_probing—DS_TRITON_KERNEL_GATE=0answers at the backend level and never constructs a probe.test_static_declaration_answers_from_the_backend_gate_without_a_probe— a probe-less static declaration (probe_fn=None) runs nothing and answers with the backend-level gate for a kernel name no backend declares.Result: 5 pass / 0 fail on Ascend 910B4 (triton-ascend 3.6.0), 5.6 s.
Pre-commit
pre-commit run --files <the changed files>— all checks passed (yapf, flake8, check-torchdist, check-torchcuda, check-license, codespell).