Repository navigation
[Fix] fla_npu >= 26.10 compatibility for the GDN AscendC stack (chunk loader + causal conv backends) - #2240
Open
longboat2010 wants to merge 4 commits into
Open
[Fix] fla_npu >= 26.10 compatibility for the GDN AscendC stack (chunk loader + causal conv backends)#2240longboat2010 wants to merge 4 commits into
longboat2010 wants to merge 4 commits into
Conversation
… / fused gated norm) Production NPU route for the GatedDeltaNet ops: causal conv1d with three-level dispatch (fused fla_npu AscendC -> unmasked correction -> four-tap torch fallback), the chunked gated delta rule kernel wired to 8 fla_npu AscendC ops + 3 mindspeed triton helpers with a device-side chunk-indices cache, and a fused gated RMSNorm (one npu_rms_norm pass + bf16 silu/mul replacing the ~13-pass fp32 decomposition). Dispatch is gated on device==npu plus XTUNER_NPU_* env knobs (all default on); the non-NPU path is unchanged. Requires the fla_npu package (https://github.com/flashserve/flash-linear-attention-npu, v26.6.0, compiled from source) on Ascend devices only.
…tack fla_npu's 26.10 line moves the stable raw ops to fla_npu.ops.ascendc.*, gates the legacy torch.ops.npu.* registrations behind an explicit load_legacy_torch_ops() call, and ships wheel variants without the embedded legacy extension. Adapt both affected GDN modules; behavior on 26.6 is unchanged. - chunk_gated_delta_rule_npu: best-effort load_legacy_torch_ops() in load_ascendc_ops() -- FileNotFoundError on 26.10 wheels without the legacy .so is caught and logged once, loading continues via the stable API the chunk route actually uses; dedup the backend-selection log lines (one line per process instead of per layer per rank). - causal_conv1d_npu: resolve the conv backend for both API generations (stable raw npu_causal_conv1d_fn/_bwd on 26.10+ with a fallback to the deprecated raw npu_causal_conv1d entry; legacy torch.ops.npu.* on 26.6), with per-backend fwd/bwd calling conventions (kwargs + host-list query_start_loc + NTD backward on stable; positional calls with per-document backward split on 26.6); upgrade the startup probe from isfinite-only to a numeric F.conv1d autograd check so finite-but-wrong kernels are rejected at startup instead of surfacing as NaN loss mid-training.
longboat2010
force-pushed
the
fix/fla-npu-2610-compat
branch
from
October 30, 2026 07:05
238e14d to
a59ded3
Compare
longboat2010
force-pushed
the
fix/fla-npu-2610-compat
branch
from
October 31, 2026 08:14
f4c2c99 to
ca8a10a
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Based on #2120 (
feat/npu-gdn-ascendc-ops), which implements the GDN part of the proposal in #2118. fla_npu's 26.10 line splits its API surface: the stable raw ops move tofla_npu.ops.ascendc.*, the legacytorch.ops.npu.*dispatcher registrations are no longer performed on import (they need an explicitfla_npu.load_legacy_torch_ops()call), and some 26.10 wheel variants ship no embedded legacy extension at all. The GDN stack as written in #2120 breaks or silently degrades on such wheels. This PR adapts the two affected modules; behavior on 26.6 is unchanged.Since #2120 is not merged yet, this PR is stacked on its branch. The delta relative to #2120 is now three commits: the 26.10 compat fixes (two files), the env-switched composite GDN forward for the chunk route (touches
chunk_gated_delta_rule_npu.pyagain), and the runtime-length l2norm kernels.What (3 files)
xtuner/v1/ops/gated_deltanet/chunk_gated_delta_rule_npu.pyload_ascendc_ops()best-effort callsfla_npu.load_legacy_torch_ops()when present. On 26.10 wheels without the embedded legacy extension this raisesFileNotFoundError— caught and logged once; loading continues via the stable API (fla_npu.ops.ascendc.*), which is what the chunk route actually uses. Legacy-API callers still fail with their own specifichasattr/assert carrying the real cause, instead of this generic loader error._log_once): a 16-layer × N-rank job previously printed the same resolution line once per layer per rank.npu_chunk_gated_delta_rule_fwd— one AscendC call covering the chunk-local cumsum, the WY/KKT scores, the tri-inverse, the state pass and the output projection that the decomposed forward runs as three Triton helpers + three AscendC ops plus six transposes. The composite is layout-native BSND, so the module's T-major q/k/v/g/beta flow through un-transposed andocomes back T-major. The composite backward (npu_chunk_gated_delta_rule_bwd) is Ascend950-only — aclnn rejects it with "ChunkGatedDeltaRuleBwd is supported on Ascend950 only" on the 910 parts this repo trains on (measured) — so the arm pairs the fused forward with the unchanged decomposed backward: the composite'sg_cumsum(fp32, in-kernel cumsum) andA([B, HV, T, chunk] bf16, the solve_tril layout) replace exactly what the backward's save-for-backward used to hold, so backward stays copy-free. Kernel-facing flags match the module contract (use_qk_l2norm_in_kernel=False,use_beta_sigmoid_in_kernel=False,use_exp2=False,disable_recompute=True).xtuner/v1/ops/gated_deltanet/causal_conv1d_npu.py(the bulk of the compat delta)_ensure_conv_ops()rewritten as a dual-backend resolver, decided exactly once per process: on the decoupled runtime (detected via the presence offla_npu.load_legacy_torch_ops) it imports the stable rawfla_npu.ops.ascendc.npu_causal_conv1d_fn/npu_causal_conv1d_bwd, with a second fallback to the deprecated rawnpu_causal_conv1dentry whose host-metadata path differs (tracked by ahost_listflag); on 26.6 it registers and uses the legacytorch.ops.npu.npu_causal_conv1d{,_bwd}names as before.XTUNER_NPU_GDN_APIno longer selects the conv backend (a note is printed when it is set) — it only governs the chunk GDN op resolution, as its name always suggested.query_start_loc, NTD-layout backward returningout[0]/out[1]; legacy = positionaltorch.opscalls with a per-document backward split (26.6 dw accuracy).F.conv1dautograd reference (relative-error threshold 0.15 for fwd/bwd). This catches "finite but wrong" kernels: on the 26.10 dev wheel the stable conv adapter returned all-zeros for all-ones input and NaN-adjacent garbage otherwise — the old probe passed it and the corruption surfaced as NaN loss mid-training. With the numeric probe the fused path is rejected at startup and the unmasked-correction fallback engages cleanly.xtuner/v1/ops/gated_deltanet/l2norm_npu.py(new; resolved by_fused_l2norm()inchunk_gated_delta_rule_npu.py)D <= 512path that serves head_dim 128) declare the row countTastl.constexpr. Triton inlines it into the binary, so every new row count forces a fresh seconds-long compiler-subprocess run on the calling rank's main thread. With variable-length packing, a megabatch whose padded token count differs by a few tokens makes a handful of ranks hit a never-before-seen row count mid-run and stall the whole job at the next collective — observed as step-locked periodic throughput spikes, reproducible across independent runs.Tpassed as a runtime argument anddo_not_specialize=["T"]. MakingTruntime is not sufficient on its own: triton still specializes runtime int arguments by divisibility class, and the deadNB = cdiv(rows, 2048)parameter flipped its class between row-count families and compiled a second binary — so the unused parameter is dropped entirely (the kernel bodies never read it). The kernel bodies are otherwise unchanged: theif block_start < Tguard and the block-pointerboundary_checkalready handle arbitraryT, andD/BD/BTstay constexpr (bound to the model's head_dim, constant in training).D > 512keeps delegating to fla_npu's per-row kernel, which takes no row-count argument at all._fused_l2norm()resolvesXTUNER_GDN_L2NORM:rt(default, the runtime-length kernels) /fla(the previous route) /inline; the startup[l2norm]log line reports which one is live.Usage
The composite forward arm is env-switched, resolved per
chunk_gated_delta_rulecall site (slab-split and single-graph paths dispatch identically):XTUNER_NPU_GDN_FUSED_FWDunset or1(default):FusedFwdChunkGatedDeltaRule— composite forward + decomposed backward, whenever the wheel ships the composite. On wheels without it (26.6 and earlier) the arm falls back to the decomposed chain with a single log line, so a wheel downgrade degrades instead of breaking; underXTUNER_NPU_STRICT_KERNELS=1only an explicit=1with a missing composite fail-fasts at wiring time (the silent default-on fallback never raises).XTUNER_NPU_GDN_FUSED_FWD=0:ChunkGatedDeltaRule, the decomposed chain — byte-identical to the pre-arm code path.[gdn_fused_fwd] fla_npu composite GDN forward active (layout=BSND, bwd decomposed)per rank.Validation
.so): theload_legacy_torch_opsskip is clean and the GDN chunk route resolves via the stable API; the broken stable conv adapter is rejected by the numeric probe at startup and the run degrades to the unmasked-correction conv (error band fwd 0.094 / dx 0.125 / dw 1.0), exit 0.oand all five autograd gradients compare with 0 violations at 2e-2/3e-2 tolerance; max |Δo| ≤ 4.9e-4, max |Δgrad| ≤ 7.8e-3 (bf16 rounding band); the composite'sg_cumsumis bit-identical to the Mindspeedchunk_local_cumsum(max |Δ| = 0.0), so the backward's saved mirrors are the same tensors the decomposed chain would have saved.profile_step=9, identical config and seed/data — the only delta is the knob): decomposed baseline 30.44 s steady per-step vs 30.10 s and 29.75 s for the fused arm across two independent runs (−1.1% / −2.3%, mean −1.7%; same-arm run-to-run spread 1.2% bounds the noise band, so the effect is direction-consistent and worth ~0.3–0.7 s/step at this shape). The composite backward (the heavier half) remains unavailable off-Ascend950.XTUNER_FAIL_ON_NONFINITE=1clean for all 50 steps on both fused runs.[gdn_ascendc] GDN kernel: fla_npu AscendC+[l2norm] fla_npu triton, 10/10 steps, 0 NaN, benchmark success; steady 18–21 s/step at 16384 tokens/rank/step (~772 tok/s per rank instantaneous on the final step), losses consistent across all 256 ranks.XTUNER_NPU_STRICT_KERNELS=1) still fail-fasts when a requested kernel route cannot resolve — no silent degradation in A/B bookkeeping.Notes
XTUNER_GDN_L2NORM=flarestores the previous fla_npu l2norm route if a pin is ever needed.