Skip to content

[RFC] AscendC backend for FLA: Scope, roadmap, and performance comparison #1531

Open

Description

@chenjingxiang96

Proposal

This RFC proposes adding a wheel-backed AscendC backend to the project (initially demonstrated in PR #1528) and outlines the necessary performance evaluation to validate this architectural change.

1. Adding the AscendC Backend

The proposed backend integrates through the existing registration and dispatch mechanism. It provides an opt-in path to use prebuilt operators (e.g., fla_npu) for specific workloads on Ascend hardware. (Refer to PR #1528 for the initial KDA integration).

2. Scope & Performance Evaluation Plan

Before committing fully to this backend, we must evaluate its performance benefits over the existing Triton-Ascend kernels. We are preparing a comprehensive benchmark suite covering:

Next Steps

We will post the benchmark scripts and comparison results (AscendC vs. Triton-Ascend) in this issue once ready. We aim to use this data to determine the value of integrating AscendC, and to define the future scope and roadmap for this backend.

Rationale

Why this RFC is necessary

Introducing and maintaining a dual-backend approach (AscendC + Triton) is a fairly large undertaking. It increases the CI burden and long-term maintenance overhead. To make an informed decision about the roadmap, we need concrete performance data rather than assumptions.

Key Questions to Answer

  1. Does the AscendC backend provide a sufficient performance gain over Triton-Ascend to justify the maintenance cost?
  2. If integration is necessary, what is the preferred approach: wheel-based (prebuilt) integration or source-code integration?

This issue will serve as the central tracking point for these discussions.

Related PR: #1528

Activity

  1. zheliuyu commented on Oct 20, 2026

    @zheliuyu
    Collaborator

    Looking forward to the AscendC backend delivering better kernel performance. I can’t wait to see the benchmark data.

  2. chenjingxiang96 commented on Oct 20, 2026

    @chenjingxiang96
    Author

    Data collected on: 2026-10-15
    Raw results: results_a2/, results_a5/ (*.jsonl, one RESULT JSON per line)

    1. Baseline & Environment

    Item Value
    fla_npu (Ascend C) Source archive git archive origin/main = f623f2b02ddd7a06b115d8061cc805275761a8b6 (2026-10-15 09:52). On A5 it is built into a wheel and installed; on A2 it runs from the same source tree with a manually embedded OPP (reason: see §6). Both sides use identical source code
    fla-org (triton-ascend) github.com/fla-org/flash-linear-attention main = 3995fd79a49210e1f1cb0b90b3bf3df9fef63707 (2026-10-15 11:16), using its built-in fla/ops/kda/backends/triton_ascend and fla/ops/gated_delta_rule/backends/triton_ascend backends
    A2 Atlas A2 / Ascend910B3 (8 cards), CANN 9.1.0-beta.1, Python 3.11, torch 2.7.1 + torch_npu 2.7.1.post5, triton-ascend 3.2.1
    A5 Ascend950PR (7 cards), CANN 9.1.0, Python 3.10, torch 2.12.0+cpu + torch_npu 2.12.0, triton-ascend 3.2.1

    fla-org side does NOT use triton_ascend_kernels (a third-party package, not installed on either machine), i.e. it runs exactly along the "upstream fla-org + triton-ascend" path.

    2. Benchmark Scenarios & Methodology

    TP splitting is modeled as heads-per-rank = total heads / TP (B and T remain unchanged per rank):

    Scenario Full-model config Heads per rank at TP=1/2/4/8
    Kimi-K3 KDA layer B=1, T=8192, K=V=128, chunk=64, H=96 96 / 48 / 24 / 12
    GLM-5.3-Flash linear (KDA) layer B=1, T=8192, K=V=128, chunk=64, H=64 64 / 32 / 16 / 8
    GDN (Qwen3.5 family) B=1, T=8192, K=V=128, chunk=64, H=16, HV=32 (GVA=2) HV=32 / 16 / 8 / 4 (H=HV/2)
    Decode step B=1, T=1, others same as above Kimi H=96 / GDN HV=32

    Methodology:

    • Wall clock (host-synchronized): measured with torch.npu.synchronize(), median of 5 runs after 2 warmups, unit: ms.
    • Device kernel time: msopprof --aic-metrics=BasicInfo --replay-mode=application, sum of Task Duration(us) from OpBasicInfo*.csv; the profile covers 3 invocations (2 warmup + 1 timed) and values are normalized to a single invocation.
    • Layouts follow each implementation's native path: fla_npu uses BNSD (the actual head-major input used by models), fla-org uses its only supported [B,T,H,D].
    • g/beta semantics aligned on both sides: KDA passes raw gate + raw beta logits (use_gate_in_kernel=True, use_beta_sigmoid_in_kernel=True, safe_gate=True, lower_bound=-5, use_qk_l2norm_in_kernel=True, disable_recompute=True); GDN passes the already-activated log-space decay (fla_npu's fused entry does not support in-kernel gate).

    3. Performance Results

    Wall-clock median (ms, single op / single layer, B=1, T=8192, chunk=64, K=V=128). Bold = faster side for that row.

    Scenario Op A5 fla_npu A5 fla-org A5 speedup A2 fla_npu A2 fla-org A2 speedup
    Kimi-K3 KDA TP1 fwd 5.081 25.901 5.10× 25.349 31.690 1.25×
    Kimi-K3 KDA TP2 fwd 2.623 13.233 5.05× 13.074 16.663 1.27×
    Kimi-K3 KDA TP4 fwd 1.452 6.485 4.47× 7.538 9.374 1.24×
    Kimi-K3 KDA TP8 fwd 1.030 4.194 4.07× 4.536 6.072 1.34×
    GLM-5.3 KDA TP1 fwd 2.860 17.498 6.12× 16.580 22.242 1.34×
    GLM-5.3 KDA TP2 fwd 1.615 9.664 5.98× 9.314 11.461 1.23×
    GLM-5.3 KDA TP4 fwd 1.181 5.696 4.82× 5.686 6.932 1.22×
    GLM-5.3 KDA TP8 fwd 0.874 4.223 4.83× 3.645 5.225 1.43×
    GDN TP1 fwd 1.337 11.156 8.35× N/A (1) 11.178 —
    GDN TP2 fwd 0.899 5.471 6.09× N/A (1) 6.540 —
    GDN TP4 fwd 0.761 3.695 4.85× N/A (1) 4.548 —
    GDN TP8 fwd 0.631 3.487 5.52× N/A (1) 4.061 —
    Kimi-K3 KDA TP1 fwd_bwd 15.067 Compile failed (2) — 56.229 (3) Compile failed (4) —
    GLM-5.3 KDA TP1 fwd_bwd 10.343 Compile failed (2) — 36.846 (3) Compile failed (4) —
    GLM-5.3 KDA TP2 fwd_bwd 5.392 Compile failed (2) — 19.832 (3) Not tested —
    GDN TP1 fwd_bwd 4.017 Compile failed (2) — N/A (1) 28.857 —
    Kimi-K3 KDA TP1 decode T=1 0.075 0.172 2.28× 0.184 0.457 2.48×
    GLM-5.3 KDA TP1 decode T=1 0.076 Not tested — 0.196 Not tested —
    GDN TP1 decode T=1 0.064 0.104 1.63× 0.163 0.429 2.63×
    GDN TP2 decode T=1 0.064 Not tested — 0.157 Not tested —

    Notes:

    • (1) On A2, fla_npu GDN fused forward is unavailable ("The prepare path is supported on Ascend950 only").
    • (2) On A5, fla-org backward fails to compile (bishengir SyncCommon.cpp:222 crash).
    • (3) On A2, fla_npu runs the legacy fused backward.
    • (4) On A2, fla-org backward fails to compile (UB overflow).
  3. zhiyuan1i commented on Oct 21, 2026

    @zhiyuan1i
    Collaborator

    Thanks for the RFC and benchmark results. I would prefer a source-based JIT integration with the NPU kernel repository as a git submodule, rather than a prebuilt kernel wheel.

    Why this approach:

    • Inspectable numerics: KDA has subtle contracts around gate/cumsum, FP32 accumulation, safe_gate/lower_bound, dtype conversions, and backward recomputation. Reviewers and coding agents need to inspect the actual implementation, not just a binary interface.
    • Exact source versions: FLA can pin an exact upstream commit through the submodule. Reviewers can check out that revision and link issues or fixes to the corresponding upstream maintainers.
    • Independent maintenance: The NPU team retains ownership of kernel development. FLA updates the submodule revision after an upstream fix and reruns the relevant tests, without waiting for another binary release.

    Distribution requirements:

    • flash-linear-attention[ascendc] should include the complete source snapshot for the pinned revision, including license and notice files.
    • The installed package should record the upstream repository and commit SHA.
    • JIT caches should be keyed by the source revision, compiler/CANN version, and target architecture.
    • CANN, torch_npu, and compiler compatibility still need explicit validation.

    This provides a better balance than either a binary-only dependency or copying the entire implementation into FLA. A minimal KDA JIT proof of concept covering packaging, source-version reporting, and numerical tests would help validate the approach.

    cc @zheliuyu

  4. zheliuyu commented on Oct 22, 2026

    @zheliuyu
    Collaborator

    Findings

    Looking at KDA alone: the shapes in this benchmark barely overlap with what flash-linear-attention already tests.

    After collapsing the repeated backward rows, the KDA table lists 10 (B, T, H, D) shapes. On current main (cd61447d), tests/ops/test_kda.py collects to 213 cases (pytest --collect-only) and 48 distinct (B, T, H, D) shapes (varlen cases counted as (1, total T, H, D), vLLM decode cases as T=1). The two sets share 1 shape, and that shape is hit by 2 cases.

    issue shapes covered by the KDA tests    1/10
    KDA test cases that land on an issue shape    2/213
    
    # B T H D Listed as flash-linear-attention tests
    1 1 8192 96 128 Kimi prefill 2 cases
    2 1 8192 48 128 Kimi prefill none
    3 1 8192 24 128 Kimi prefill none
    4 1 8192 12 128 Kimi prefill none
    5 1 8192 64 128 GLM prefill none
    6 1 8192 32 128 GLM prefill none
    7 1 8192 16 128 GLM prefill none
    8 1 8192 8 128 GLM prefill none
    9 1 1 96 128 Kimi decode none
    10 1 1 64 128 GLM decode none

    The single shared shape is (1, 8192, 96, 128), in test_chunk_use_beta_sigmoid_in_kernel, once with allow_neg_eigval false and once true. The benchmark reports 6 switches: use_gate_in_kernel, use_beta_sigmoid_in_kernel, safe_gate, lower_bound=-5, use_qk_l2norm_in_kernel, and disable_recompute=True; these 2 cases exercise only use_beta_sigmoid_in_kernel (their q/k l2norm is applied outside the kernel, not via use_qk_l2norm_in_kernel). So the one shape that is covered is still not tested on the code path this benchmark runs.

    To be clear: flash-linear-attention-npu does show a solid performance edge over flash-linear-attention's triton-ascend path on some of these shapes. But the near-zero test overlap says three things:

    • The two test matrices aim at different targets: flash-linear-attention's 48 shapes are mostly small, generic correctness cases, while the 10 benchmark shapes come from production Kimi/GLM workloads.
    • The two repos have evolved independently, and flash-linear-attention-npu's numerical correctness is currently not verifiable inside flash-linear-attention's test suite — the shared test backend imported by its main KDA test file is not in the public repo either.
    • So the speedups alone cannot justify integration: on these shapes and switch combinations, flash-linear-attention-npu's correctness is unverified as far as flash-linear-attention is concerned, let alone protected against regressions.

    On the data posted so far, flash-linear-attention is therefore not in a position to integrate flash-linear-attention-npu (the AscendC backend proposed in this issue).

    Where to go from here

    If the two repos are judged to be moving in the same direction

    flash-linear-attention-npu should first align with flash-linear-attention's existing KDA tests — at minimum the full test_chunk / test_fused_recurrent paths (including their varlen variants) and production-adjacent large shapes such as (1, 8192, 96, 128) in test_chunk_use_beta_sigmoid_in_kernel. In parallel, the 10 production shapes above, run with the benchmark's 6-switch combination, should be added as numerical correctness tests (flash-linear-attention has none today, and none are publicly visible on the flash-linear-attention-npu side). On top of that, flash-linear-attention-npu needs to demonstrate a stable, reproducible performance advantage over triton-ascend on A2/A3 and 950 — i.e. become a drop-in replacement for triton-ascend on these high-priority cases. That is the right time to integrate.

    If not

    Shelve the integration for now. In the meantime, we suggest flash-linear-attention-npu keep evolving as a standalone wheel and publish its own numerical test results so correctness can be checked; contributing the 10 shapes with the 6-switch combination as a test PR to flash-linear-attention would also be the most direct way to give the two test suites their first real intersection. The integration discussion can be reopened any time once that exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ascend-npuAscend NPU (triton_ascend) relatedenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions