Initialize large-head FMHA shared memory per kernel variant#29140
Conversation
Signed-off-by: Kevin-Li-2025 <2242139@qq.com>
|
@microsoft-github-policy-service agree |
There was a problem hiding this comment.
Pull request overview
This PR updates the CUDA dispatch logic for the ONNX-domain Attention op to avoid launching CUTLASS Memory Efficient Attention (MEA) in a known-crashing configuration (external-cache nonpad_kv_seqlen + head_size > 256). Instead, it forces that specific case to fall through to the unified unfused attention implementation, which supports large head sizes safely.
Changes:
- Add an MEA eligibility guard: when
nonpad_kv_seqlen != nullptrandhead_size > 256, skip MEA and use the unified unfused fallback. - Add a Python parity test that exercises
head_size=512withnonpad_kv_seqlenon CUDA to ensure the large-head external-cache scenario works (via fallback).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
onnxruntime/core/providers/cuda/llm/attention.cc |
Adds a targeted MEA dispatch guard for the nonpad (custom right-padding) path when head_size > 256, forcing fallback to unified unfused attention. |
onnxruntime/test/python/transformers/test_onnx_attention/test_gqa.py |
Adds a CUDA test covering head_size=512 + nonpad_kv_seqlen to validate the new fallback behavior doesn’t crash and remains numerically correct. |
Review summaryThe fallback itself is semantically safe and non-regressing — the unified unfused path genuinely handles Major1. The 2. The guard checks 3. Likely true root cause the PR doesn't address: the smem opt-in is registered for only one of two kernel variants. 4. The test doesn't prove the fallback occurred. Out-of-scope follow-up
Minor / Nit
Open questions (need pre-Ampere/SM75 hardware to settle)
Reviewed by a multi-model agent team (readability / correctness / adversarial / spec-depth / integration). Structural claims above were verified against the cited source; smem/crash claims that require SM75 hardware are flagged as open questions. |
tianleiwu
left a comment
There was a problem hiding this comment.
Summary
This fix correctly addresses the root cause of #28388 in fmha_launch_template.h. The previous code opted a single kernel_fn into the large dynamic-shared-memory limit via one static bool once guard. Because the default kernel (attention_kernel_batched_impl) and the right-padding/nonpad kernel (attention_kernel_batched_impl_right_padding) are distinct device functions that both need the cudaFuncAttributeMaxDynamicSharedMemorySize opt-in when smem_bytes > 0xc000 (e.g. head_size=512), once only configured whichever variant ran first. The second-seen variant then launched without the opt-in and crashed (too much shared memory). Splitting into per-variant statics (right_padding_once / default_once) and launching the matching kernel directly is the right fix. Upgrading the ignored cudaFuncSetAttribute return to CUDA_CALL_THROW is a nice diagnosability improvement.
Verified: queries_per_block is a compile-time template parameter of LaunchCutlassFmha, so both statics and the direct launches are well-formed per instantiation; function-local statics keep thread-safe init; CUDA_CALL_THROW is available transitively via memory_efficient_attention.h -> cuda_common.h -> cuda_call.h.
High priority: PR description/title no longer match the implementation
The PR description (and the auto-generated reviewer overview) describe a different approach: adding an MEA eligibility guard in core/providers/cuda/llm/attention.cc so that nonpad_kv_seqlen != nullptr && head_size > 256 falls through to the unified unfused path. The current diff touches no such file and instead keeps MEA enabled for the large-head nonpad path while fixing the shared-memory opt-in. The Tests section also references a *_falls_back_from_mea_* test that is not present in the diff. Please update the title, description, and Tests section to match the shipped kernel-launch fix so reviewers and the #28388 resolution rationale are not misled.
The code change itself looks correct and well-scoped.
Signed-off-by: Kevin-Li-2025 <2242139@qq.com>
tianleiwu
left a comment
There was a problem hiding this comment.
The latest head addresses my previous feedback. The PR title/body now match the actual per-kernel shared-memory opt-in fix, and the regression test documents why the default-then-nonpad ordering is intentional.
I re-reviewed the current diff and the CUDA launch change still looks correct: the default and custom right-padding kernels are distinct device functions, each now gets its own cudaFuncAttributeMaxDynamicSharedMemorySize initialization, and the CUDA API result is checked with CUDA_CALL_THROW. The author response on the optional reverse-order test is reasonable because that coverage would require process isolation once the first sequence has initialized both variants.
No remaining findings from me.
Description
Fixes #28388.
Large-head Memory Efficient Attention kernels require an explicit
cudaFuncAttributeMaxDynamicSharedMemorySizeopt-in when their dynamic shared-memory requirement exceeds0xc000. The default batched kernel and the custom right-padding kernel are distinct device functions, but the previous function-localonceconfigured only whichever function pointer was selected on the first invocation. If the other variant ran later in the same process, it launched without the required opt-in and failed with excessive shared-memory usage.This change:
cudaFuncSetAttributeto the exact kernel that will launch;CUDA_CALL_THROW; andTests
TestONNXAttentionGQALargeHeadNonpadMEA, which runs the default large-head MEA kernel first and then the right-padding/nonpad variant in the same process. This ordering reproduces the original process-global initialization bug and verifies that both distinct kernels opt in independently.python3 -m py_compile onnxruntime/test/python/transformers/test_onnx_attention/test_gqa.pygit diff --checkThe targeted CUDA test requires an ONNX Runtime CUDA build and a compatible GPU, so execution is delegated to CI.