Repository navigation
[CUDA] Optimize Qwen3.8-Flash-Next DSA, Indexer, HC, and QMoE kernels for SM12x and 256K context - #33802
Merged
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical issues can discard valid top-k candidates, admit stale workspace data, and break builds targeting pre-SM70 GPUs and tests on SM90.
5 open issues
Update GEMV launch-configuration expectations and test table · New Initialize the full suffix for noncompact top-k mode · New Round up active chunk boundary to prevent sentinel/live-key overlap · New Guard __match_any_sync for targets below SM70 · New Use runtime tensor capacity when validating reuse · New
What changed in this PR
This PR builds on #33786 to optimize ONNX Runtime’s CUDA kernels for Qwen3.8-Flash-Next, targeting SM12x GPUs and contexts up to 256K tokens.
Changes:
- Enables INT8 tensor-core sparse attention by default and adds a lower-shared-memory pipeline.
- Optimizes sparse-indexer scoring and top-k selection, while allowing runtime-sized caches.
- Tunes HyperConnection and quantized MoE kernel dispatch, buffering, and prefetching.
| File | Description |
|---|---|
onnxruntime/contrib_ops/cuda/sparse/packed_sparse_attention_indexer.cc |
Allows runtime cache capacities in QSA and CSA refresh paths. |
onnxruntime/contrib_ops/cuda/sparse/packed_sparse_attention_indexer_impl.cu |
Tiles query scoring and optimizes top-k initialization, histograms, and merging. |
onnxruntime/contrib_ops/cuda/moe/qmoe_kernels.cu |
Makes scalar fallback launch-grid conversions explicit. |
onnxruntime/contrib_ops/cuda/moe/qmoe_int3_tc.cuh |
Enables FC2 remainder prefetch and adjusts kernel configurations. |
onnxruntime/contrib_ops/cuda/llm/moe_gemm/moe_gemv_tc.cuh |
Specializes small-route discovery and widens eligible INT2 FC2 tiles. |
onnxruntime/contrib_ops/cuda/llm/fpA_intB_gemv/int8_tc_gemv.cuh |
Simplifies load addressing and retunes INT8 GEMV dispatch. |
onnxruntime/contrib_ops/cuda/bert/hyper_connection_low_rank_mix_impl.cu |
Reduces shared-memory weight slots on selected architectures. |
onnxruntime/contrib_ops/cuda/bert/dynamic_sparse_attention.cc |
Enables INT8 tensor-core prefill and decode by default. |
onnxruntime/contrib_ops/cuda/bert/dynamic_sparse_attention_prefill_int8.cuh |
Adds a two-buffer shared-memory attention pipeline. |
onnxruntime/contrib_ops/cuda/bert/dynamic_sparse_attention_impl.cu |
Selects and launches attention variants based on shared-memory capacity. |
onnxruntime/contrib_ops/cpu/bert/dynamic_sparse_attention_helper.h |
Clamps total sequence length to the bound past-cache capacity. |
Review effort: Balanced
…g and top-k - Tile 8 query tokens per CTA in QsaRaggedPrefillScoreKernel<T, TState, true> (4 in registers + 4 in shared memory) with a bit-exact 19-shuffle 16-head butterfly reduction, cutting present_key_state DRAM traffic by 2x and speeding up 128K prefill scoring by 1.96x (11.67s -> 5.94s on RTX Spark SM121). - Avoid zero-filling inactive 2048-key chunks in QsaScoreTileTopKKernel beyond element-0 sentinels, eliminating the 256K state_capacity padding overhead on short/medium chunks. - Use warp-level __match_any_sync aggregation for radix histogram updates in QsaPartialTopKKernel and extend single-launch QsaFinalMergeAndEmitKernel up to 64 chunks (256K context). - Allow runtime past_key_state / past_key capacities configured via dynamic_batching / runtime_profiles to differ from static node attributes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tianlei Wu (tianleiwu)
force-pushed
the
tlwu/qfn_rtx_spark_opt
branch
from
November 2, 2026 23:08
39b1a55 to
6985510
Compare
- Put the non-WarpHeads token loop in the else branch of QsaRaggedPrefillScoreKernel (fixes "loop is not reachable" nvcc error that broke the CUDA CI builds). - Round up the first inactive 2048-key chunk so sentinels never overwrite live keys. - Initialize the whole key suffix when ORT_PACKED_SPARSE_INDEXER_COMPACT_TOPK=0. - Guard __match_any_sync for SM70+ and fall back to per-thread atomicAdd. - Use the runtime past_key_state capacity in ComputeReuse. - Update INT8 tensor-core GEMV launch-configuration tests and comments for the new dispatch branches; drop the always-false waves(7) < waves(8) check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Akshay Sonawane (apsonawane)
approved these changes
Nov 3, 2026
Tianlei Wu (tianleiwu)
enabled stacked auto-merge (squash)
November 3, 2026 20:46
Tianlei Wu (tianleiwu)
added a commit
that referenced
this pull request
Nov 4, 2026
… a large-value-scale accumulator (#33847) ### Description Follow-up to #33802 addressing [this review comment](#33802 (comment)): the INT8 tensor-core DynamicSparseAttention kernel could silently lose the contribution of earlier tiles when a later tile raises the running maximum, now that the INT8 TC prefill/decode kernels are enabled by default. The kernel rescales the PV accumulator with `alpha = FastExp2(old_max - new_max)`, which uses `ex2.approx.ftz.f32`. When the maximum rises by more than ~87 nats, `alpha` flushes to zero and erases the earlier accumulator. A large `value_scale` (up to the finite range) can make that earlier contribution significant. The existing large-value-scale FP32 fallback only tracked how far the *current tile's* scores sit below the new maximum (`lowest_depth`), so it did not fire in this case. ### Changes | File | Change | |---|---| | `contrib_ops/cuda/bert/dynamic_sparse_attention_prefill_int8.cuh` | When `check_tail_depth` is armed (some `abs(value_scale) > 2^16`), the rescale `run_max - new_max` of a non-empty accumulator now also feeds `lowest_depth`. A rise of more than `kInt8TcTailDepth` (20 nats) makes the CTA take the existing `AttendRowFp32` fallback. Updated the comments accordingly. | | `test/contrib_ops/dynamic_sparse_attention_op_test.cc` | Added `Int8TensorCoreIncreasingMaximumLargeValueScale_CUDA`: candidate 0 scores 0 (value code 1), candidate 1 scores 88 in a later tile, `value_scale = 2^120`; expected output `2^120 * e^-88 / (1 + e^-88) ~= 8.0e-3` (the buggy path returned 0). Covered for prefill and for decode with 1/2/3 tiles per split. | | `docs/contrib_ops/cuda/dynamic_sparse_attention.md` | Documented the additional fallback condition. | The fallback is only armed for `abs(value_scale) > 2^16`, so models with ordinary value scales (e.g. Qwen3.8-Flash-Next, max 0.285) see no change in dispatch or performance. ### Testing - Built `onnxruntime_provider_test` (CUDA, SM80) and ran `DynamicSparseAttentionTest.*` on A100: 74 tests pass. - The new test fails without the kernel change and passes with it. - `lintrunner` (CLANGFORMAT) clean. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tianlei Wu (tianleiwu)
added a commit
to microsoft/onnxruntime-genai
that referenced
this pull request
Nov 4, 2026
…d preserve DFlash2 graphs at chunk=4096 (#2840) ## Summary Stacked on #2825 (`tlwu/20261030/qwen38-plane-bundling`). Companion ORT PR: microsoft/onnxruntime#33802 This PR enables **256K (262,144) context length** via `engine.dynamic_batching` / `runtime_profiles` in `qwen38_flash_graph`, and optimizes PLE paging and `chunk_size = 4096` DFlash2 graph retention on **NVIDIA RTX Spark N1X (SM 12.1)**: 1. **256K `dynamic_batching` & `runtime_profiles` Support in `qwen38_flash_graph` (`src/models/qwen38_graph.cpp`, `src/models/qwen38_replay_geometry.cpp`)**: - Map `engine.dynamic_batching` (`block_size`, `num_blocks`, `max_batch_size`, `max_scheduled_tokens`, `dflash2_max_snapshots`) and `search.chunk_size` (including `runtime_profiles` overlays) to `qwen38_graph.kv_capacity = target_tokens + kQwen38GraphIdMaxRows`, `model.context_length = target_tokens`, `search.max_length = target_tokens`, and `qwen38_graph.prefill.chunk`, and propagate the batching-derived context tier to target and auxiliary graph selection. - Materialize symbolic target and head cache-length dimensions (`past_position_ids`, `[head.]past_key_values.{l}.{key,value,index_key}`) from `capacity_` via `ResolveQwen38CacheSlotShape` while preserving declared static dimensions, allow symbolic KV/Indexer dimensions in `ValidateQwen38CacheShapes`, and exempt `ple_context` in `RequireQwen38ReplayCoverage` for RC4/RC5 `SpeculativePrefixCommit` models. - Derive reserved DFlash2 blocks from actual drafter pool/embedding bytes and target bytes per configured `block_size` via `ResolveQwen38DflashReservedBlocks`: with `block_size = 256` and `dflash2_max_snapshots = 1` (13 reserved DFlash2 blocks), `num_blocks = 1037` provides 1,024 target blocks = **262,144 (256K) max request length** (3.38 GiB GPU KV + Indexer cache). 2. **Preserve Captured DFlash2 Decode CUDA Graphs When `chunk_size = 4096` (`src/models/qwen38_graph.cpp`)**: - Pre-size `Dflash2Drafter`'s `StepTensor` during `Warmup()` to `std::min(fast_->chunk, sliding_window + block_size)` (2056 rows when `chunk_size = 4096`, 2048 rows when `chunk_size = 2048`) so that `BeginDecode` after a 4096-token prefill chunk (`ingest_count = 2056`) never reallocates `StepTensor` or calls `ReleaseCapturedGraphs()`. 3. **Batched PLE `PrefetchVirtualMemory`, Configurable Host PLE Cache & WDDM Standby-List Relief (`src/qwen38_plane/qwen38_plane.cu`, `src/models/qwen38_graph_plane.cpp`)**: - Add `host_gather_ple_rows_batch` to batch all S=8 decode-step PLE page misses into a single `PrefetchVirtualMemory` syscall. - Make `StagedPleWorkspace::cache_slots` configurable up to 65,536 rows (192 MiB `ngram_cache_data` + 1.56 MiB metadata per workspace when `cudaMemGetInfo` reports >= 12 GiB free memory, sizing down to 16,384 rows / 48 MiB when < 12 GiB, or overridden to a power-of-two in `[1024, 65536]` via `ORT_QWEN38_PLE_CACHE_SLOTS`), and scale the prefill n-gram cache insertion tail in `PleGatherPool::run` to `cache_slots / 4`. - Remove `FILE_FLAG_RANDOM_ACCESS` when opening 39.17 GiB PLE shards on Windows so the OS cache manager does not pin stale 256 KB read-ahead views at the expense of WDDM GPU shared memory (**3.77x faster 32K TTFT** and **4.65x faster 32K decode** on `all_int4`). ## Validation - `engine_unit_tests --gtest_filter="*Qwen38*"` (431/431 passed) and `lintrunner -a`. - End-to-end `smoke.py` and MMLU-Pro smoke tests. - 8K / 128K / 255K prompt + 1K decode (262,144 total tokens) benchmarks on RTX Spark N1X (SM 12.1): - `2b375` (`chunk=4096`): 8K TTFT 4.26s (1,923 tok/s, decode 93.38 tok/s / 52.17 ms/cyc), 128K TTFT 75.06s (1,746 tok/s, decode 97.13 tok/s / 55.72 ms/cyc), 255K + 1K = 256K TTFT 164.07s (1,592 tok/s, -41.66s vs baseline). - `top16` (`chunk=2048` clean mem / `chunk=4096`): 8K TTFT 7.13s / 4.77s, decode 91.61 / 92.09 tok/s (62.38 / 62.06 ms/cyc), 128K TTFT 96.89s / 86.50s, decode 80.73 tok/s (66.69 ms/cyc), 255K + 1K = 256K TTFT 212.49s / 197.38s (-21.82s / -36.94s vs baseline), decode 57.67 tok/s (75.80 ms/cyc). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 task done
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.


Summary
Stacked on #33786 ( lwu/20261031/qmoe-cuda-int2-tc).
This PR optimizes CUDA prefill and decode kernels for Qwen3.8-Flash-Next (RC5) on Blackwell SM 12.x (100 KB shared memory/SM, RTX Spark N1X) from short/medium contexts (2K–32K) up to 256K (262,144) context length:
untime_profiles.
untime_profiles.
Validation