Skip to content

feat(kda): SM90 FlashKDA prefill, intracard CP, and backend dispatch#106

Open
cherhh wants to merge 59 commits into
inclusionAI:mainfrom
cherhh:kda-backends
Open

feat(kda): SM90 FlashKDA prefill, intracard CP, and backend dispatch#106
cherhh wants to merge 59 commits into
inclusionAI:mainfrom
cherhh:kda-backends

Conversation

@cherhh

@cherhh cherhh commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

📌 Description

This PR adds the SM90 (Hopper) KDA prefill backend to cuLA, ported from MoonshotAI's FlashKDA CUTLASS C++ kernels to CuTeDSL, and extends it with an intracard context-parallel (CP) mode that is not present upstream.

Huge thanks to the MoonshotAI FlashKDA authors — the kernel design and its numerics are theirs; this port follows the original kernels closely.

Also on top of #105:

  • FlashKDA prefill, with a multi-backend kda_prefill (FlashKDA + fused CUTLASS C++, default FlashKDA). Falls back to fused C++ when FlashKDA can’t run (HV > H, fp32 g, no CuTeDSL, CULA_BACKEND_*=0, …). Direct entry points (flashkda_prefill, kda_prefill_hopper*, …) stay.
  • Intracard CP for FlashKDA: use_intracard_cp="auto" | True | False.
  • Matching tests / benches / docs.

Related ideas also show up in FlashQLA; we keep an exact M and skip their gate-decay warmup.

🔍 Related Issues

🚀 Pull Request Checklist

Thank you for contributing to cuLA! Before we review your pull request, please make sure the following items are complete.

✅ Pre-commit Checks

  • I have installed pre-commit by running pip install pre-commit (or used your preferred method).
  • I have installed the hooks with pre-commit install.
  • I have run the hooks manually with pre-commit run --all-files and fixed any reported issues.

🧪 Tests

  • Tests have been added or updated as needed.
  • All tests are passing.
CUDA_VISIBLE_DEVICES=0 PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest \
  tests/test_kda_sm90_prefill_vs_fla.py \
  tests/test_kda_sm90_intracard_cp.py \
  -m kda_full -q

⚡ Performance

H100 80GB HBM3. run: bench_kda_sm90_prefill.py --mode both, bench_kda_sm90_cp.py.

FlashKDA (serial; default use_intracard_cp=None) vs FLA (H=64, D=128, bf16, safe_gate=True, no init state, warmup 25 / iters 100, iqr mean):

[Fixed]
  B       T  │  rel_rmse   rel_max  mean_diff  │   FLA(ms)  cuLA(ms)  Speedup
  1     512  │  0.004811  0.010582   0.000013  │    0.8675    0.1358    6.39x
  1    1024  │  0.004587  0.007772   0.000011  │    0.8651    0.1900    4.55x
  1    4096  │  0.004827  0.011236   0.000013  │    1.1736    0.6787    1.73x
  1    8192  │  0.004624  0.010000   0.000011  │    2.2894    1.3259    1.73x
  1   16384  │  0.004731  0.010050   0.000012  │    4.5402    2.6260    1.73x
  2     512  │  0.004569  0.007772   0.000011  │    0.8700    0.1414    6.15x
  2    1024  │  0.004805  0.010811   0.000013  │    0.8736    0.2528    3.46x
  2    4096  │  0.004623  0.010000   0.000011  │    2.2812    0.9266    2.46x
  2    8192  │  0.004730  0.010050   0.000012  │    4.4960    1.8157    2.48x
  2   16384  │  0.004923  0.009756   0.000013  │    8.9855    3.6398    2.47x

[Varlen]
  Config                                          │  rel_rmse   │   FLA(ms)  cuLA(ms)  Speedup
  uniform 10seqs T=4096  [409..415]  avg=409      │  0.004764   │    1.2668    0.4974    2.55x
  random  10seqs T=4096  [24..1201]  avg=409      │  0.004769   │    1.2650    0.5602    2.26x
  skewed  10seqs T=4096  [227..2053] avg=409      │  0.004766   │    1.2626    0.6761    1.87x
  uniform 20seqs T=4096  [204..220]  avg=204      │  0.004703   │    1.3642    0.4888    2.79x
  random  20seqs T=4096  [5..787]    avg=204      │  0.004735   │    1.3383    0.5556    2.41x
  skewed  20seqs T=4096  [107..2063] avg=204      │  0.004715   │    1.3063    0.6929    1.89x
  uniform 10seqs T=8192  [819..821]  avg=819      │  0.004597   │    2.3585    0.9345    2.52x
  random  10seqs T=8192  [48..2401]  avg=819      │  0.004599   │    2.3786    1.0581    2.25x
  skewed  10seqs T=8192  [455..4097] avg=819      │  0.004600   │    2.4002    1.2839    1.87x
  uniform 20seqs T=8192  [409..421]  avg=409      │  0.004580   │    2.4630    0.8980    2.74x
  random  20seqs T=8192  [9..1574]   avg=409      │  0.004572   │    2.4571    1.0214    2.41x
  skewed  20seqs T=8192  [215..4107] avg=409      │  0.004581   │    2.4602    1.3106    1.88x
  uniform 10seqs T=16384 [1638..1642] avg=1638    │  0.004722   │    4.6365    1.8113    2.56x
  random  10seqs T=16384 [95..4802]  avg=1638     │  0.004720   │    4.6397    2.0693    2.24x
  skewed  10seqs T=16384 [910..8194] avg=1638     │  0.004720   │    4.6481    2.4976    1.86x
  uniform 20seqs T=16384 [819..823]  avg=819      │  0.004706   │    4.6820    1.7328    2.70x
  random  20seqs T=16384 [19..3147]  avg=819      │  0.004705   │    4.6985    1.9834    2.37x
  skewed  20seqs T=16384 [431..8195] avg=819      │  0.004710   │    4.6760    2.5166    1.86x

Same FlashKDA binary, CP "auto" vs off (warmup 10 / iters 100; cp marks whether auto actually engaged CP — the irregular-length configs 1023/1025/65537 are included per review feedback):

                                             H=4                              H=8
config                T      cp  │  off(ms)  auto(ms)  speedup  │  off(ms)  auto(ms)  speedup
T=1023             1023      no  │   0.1369    0.1488    0.92x  │   0.1362    0.1499    0.91x
T=1025             1025      no  │   0.1341    0.1485    0.90x  │   0.1365    0.1483    0.92x
T=4K               4096     yes  │   0.4508    0.2175    2.07x  │   0.4718    0.2313    2.04x
T=8K               8192     yes  │   0.8890    0.2732    3.25x  │   0.9164    0.3043    3.01x
T=16K             16384     yes  │   1.7506    0.3751    4.67x  │   1.8041    0.4972    3.63x
T=32K             32768     yes  │   3.4780    0.5674    6.13x  │   3.5821    0.8718    4.11x
T=64K             65536     yes  │   6.9248    0.9366    7.39x  │   7.1335    1.6234    4.39x
T=64K+1           65537     yes  │   7.0665    0.9678    7.30x  │   7.3087    1.6867    4.33x
2x16K             32768     yes  │   1.8072    0.6046    2.99x  │   1.9176    0.8354    2.30x
32K+4K            36864     yes  │   3.4873    0.7141    4.88x  │   3.6136    1.1966    3.02x
32K+1K            33792     yes  │   3.4768    0.5674    6.13x  │   3.5965    1.1274    3.19x
32K+1023+1025     34816     yes  │   3.5551    0.7125    4.99x  │   3.6815    1.2040    3.06x
64K+1K            66560     yes  │   6.9311    1.1843    5.85x  │   7.1537    2.1218    3.37x
64K+2x1K          67584     yes  │   6.9392    1.1993    5.79x  │   7.1692    2.1349    3.36x
64K+5x1K          70656     yes  │   6.9361    1.2404    5.59x  │   7.1950    2.2624    3.18x
64K+1+1023+1025   67585     yes  │   7.0793    1.2242    5.78x  │   7.3222    2.1873    3.35x

28/32 engaged; engaged-only geo-mean 4.00x; best 7.39x; worst 2.04x

Non-CHUNK-aligned lengths keep the full win (65537: 7.30x/4.33x vs 7.39x/4.39x for 64K). The four non-engaged rows (T=1023/1025) run serial either way; the ~0.9x there is the auto planner's fixed cost on a ~0.14 ms call.

@cherhh
cherhh requested review from fkuner and icavan July 19, 2026 19:14
@cherhh cherhh changed the title Kda backends feat(kda): SM90 FlashKDA prefill, intracard CP, and backend dispatch Jul 19, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new SM90 (Hopper) FlashKDA prefill backend written in CuTeDSL, featuring a two-kernel pipeline and support for intra-card context parallel (CP) execution. It also restructures the repository layout to support multiple backends with a generic registry and dispatch mechanism. The review feedback highlights a few critical issues: a bug in the fully-fused backend where explicitly disabling context parallel incorrectly rejects the call, a potential error in flashkda.py when q.device.index is None, and potential CPU device errors in utils.py when calling torch.cuda.current_stream directly.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread cula/kda/backends/fully_fused.py Outdated
Comment thread cula/kda/flashkda.py Outdated
Comment thread cula/utils.py
Comment thread cula/utils.py
cheheng.ch added 26 commits July 20, 2026 04:20
K1 prepares decayed q/k, beta and the WY inverse; K2 runs the
inter-chunk recurrence. CHUNK=16, D=128, forward only.
Split long sequences into segments and run the recurrence per segment:
K1 once -> pre_scan -> merge -> segment-K2. Bit-identical to serial.
The shape binds wrong whenever only one of the two states is present.
27us -> 3us per launch; torch tensors pass straight through.
cheheng.ch and others added 24 commits July 20, 2026 04:20
plan_prefill returns the plan the executor runs; a trivial plan means
serial. SM100 keeps its own decision under sm100/policy.py.
csrc/kda/sm90 and hopper_fused_fwd stay exactly as on main (incl. the
inclusionAI#86 intra-card CP). Names: kda_prefill_hopper (+_opt/_auto) = C++ fused,
kda_prefill_hopper_cutedsl = this CuTeDSL backend.
allocate_workspace/clear_workspace_cache lost their last caller when the
arena landed; banners and restating comments go per the new comment rules.
FlashKDA remains first choice; unsupported calls fall through to the fully-fused CUDA path with per-backend rejection reasons.
Validate CP inputs before planning, key reusable buffers by CUDA stream, and bound dynamic caches at 32/64 entries.
Co-authored-by: Cursor <cursoragent@cursor.com>
("32K+1K", [32768, 1024]),
("64K+1K", [65536, 1024]),
("64K+2x1K", [65536, 1024, 1024]),
("64K+5x1K", [65536] + [1024] * 5),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shall add more irregular varlen settings like 65537, 1023, 1025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added those irregular lengths (1023, 1025, 64K+1, and a couple ragged mixes). Numbers are in the #106 (comment).

Comment thread benchmarks/bench_kda_sm90_cp.py Outdated
]


def time_kernel(fn, warmup=None, n_iters=None):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try reuse benchmark_cuda_mode_fn

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — time_kernel now delegates to benchmark_cuda_mode_fn.

Comment thread cula/kda/backends/__init__.py Outdated
``CULA_BACKEND_*`` variable makes dispatch try the remaining implementation.
"""
device_ctx = torch.cuda.device(q.device) if q.device.type == "cuda" else contextlib.nullcontext()
with device_ctx:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with torch.cuda.device_of(q): is more concise.

Why do we need to specify the device context here? Is it a common pattern in kernel library? When do we have to add ctx guard explicitly?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to device_of. Needed so launches/allocs run on the same GPU as the input tensors (current device can differ in multi-GPU). FLA's input_guard does the same.

Comment thread cula/kda/chunk.py

if cp_context is not None:
if use_intracard_cp is True:
if use_intracard_cp is CPMode.FORCE:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the intracard cp not compatible with Kimi CP now?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kimi CP gives each rank a sequence shard; intracard CP currently assumes a full sequence. Nesting should work in theory, but isn't implemented/tested yet, so we reject for now.

Comment thread cula/kda/flashkda.py Outdated
out: torch.Tensor | None = None,
final_state: torch.Tensor | None = None,
):
q, k, v, g, beta = _contig(q), _contig(k), _contig(v), _contig(g), _contig(beta)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a concern about hiding implicitly conversions here. I'd rather return an error to let the caller know they should pass a contiguous tensor or bf16 tensor here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, done.

Comment thread cula/ops/kda/cp_mode.py Outdated
@classmethod
def parse(cls, use_intracard_cp, use_cp=None) -> CPMode | None:
"""Public use_intracard_cp value ("auto"/True/False; use_cp is a
deprecated alias) -> mode. None stays None (backend default)."""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not drop use_cp if it is a deprecated option

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed now. Only use_intracard_cp remains.

Comment thread cula/kda/hopper_fused_fwd.py Outdated
cu_seqlens,
chunk_indices,
)
with torch.cuda.device(q.device):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question: when do we have to add the device hint?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

cherhh added 4 commits July 20, 2026 17:49
Review feedback on inclusionAI#106. Also drops the use_cp plumbing here ahead of
removing the alias everywhere.
never shipped in a released tag; use_intracard_cp is the only spelling
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants