Skip to content

[CUDA] Optimize Qwen3.8-Flash-Next DSA, Indexer, HC, and QMoE kernels for SM12x and 256K context - #33802

Merged
Tianlei Wu (tianleiwu) merged 6 commits into
mainfrom
tlwu/qfn_rtx_spark_opt
Nov 3, 2026
Merged

Tianlei Wu (tianleiwu) merged 6 commits into
mainfrom
tlwu/qfn_rtx_spark_opt

Conversation

@tianleiwu

@tianleiwu Tianlei Wu (tianleiwu) commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

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:

  1. DynamicSparseAttention (dynamic_sparse_attention.cc, dynamic_sparse_attention_impl.cu, dynamic_sparse_attention_prefill_int8.cuh, dynamic_sparse_attention_helper.h):
    • Enable DynamicSparseAttentionInt8TcKernel prefill & split-K decode by default on SM >= 8.0.
    • Add a 2-stage shared-memory pipeline (kTwoBufferSmem, 38.1 KB / 40.4 KB smem) on 100 KB/SM architectures (SM 8.6/8.9/12.0/12.1) to double occupancy from 1 to 2 CTAs/SM (2.83x faster DSA prefill, 7.5x faster DSA decode).
    • Clamp otal_sequence_length to past_cache_capacity when a smaller runtime past_key cache is bound via
      untime_profiles.
  2. PackedSparseAttentionIndexer (packed_sparse_attention_indexer.cc, packed_sparse_attention_indexer_impl.cu):
    • Tile 8 query tokens per CTA (4 in registers + 4 in shared memory) in QsaRaggedPrefillScoreKernel<T, TState, true> and replace shared-memory reduction barriers with a bit-exact 19-shuffle 16-head butterfly reduction, cutting present_key_state DRAM reads by 2x and speeding up 128K prefill scoring by 1.96x (11.67 s -> 5.94 s).
    • Avoid zero-filling inactive 2048-key chunks in QsaScoreTileTopKKernel beyond element-0 sentinels, eliminating the 256K state_capacity padding tax on short/medium prompts.
    • Aggregate radix histogram updates in QsaPartialTopKKernel with warp-level __match_any_sync and extend single-launch QsaFinalMergeAndEmitKernel up to 64 chunks (256K context).
    • Allow runtime past_key_state capacity to be sized dynamically from dynamic_batching /
      untime_profiles.
  3. HyperConnectionLowRankMix (hyper_connection_low_rank_mix_impl.cu):
    • Tune kWeightSlots to 5 on CUDA_ARCH >= 1000 (30.8 KB in HcDownKernel, 23.6 KB in HcUpPreKernel), increasing 100 KB/SM occupancy from 2 to 3–4 CTAs/SM (-34% to -43% kernel time).
  4. QMoE Decode & Prefill (moe_gemv_tc.cuh, qmoe_int3_tc.cuh, qmoe_kernels.cu, int8_tc_gemv.cuh):
    • Increase kRouteChunks to 3 (38.7 KB smem -> 2 CTAs/SM) for otal_routes <= 96 and enable kM = 2 2-route FC2 tile sharing in MoeGemvTcInt2Kernel.
    • Enable kRemMode = 1 FC2 remainder prefetch in Int3TcCoalescedKernel.

Validation

  • End-to-end smoke.py and 10-item MMLU-Pro smoke evaluation across all 3 Qwen3.8-Flash-Next RC5 variants (2b375, 3b3_16, all_int4).
  • 2K / 8K / 32K and 8K / 128K / 255K + 1K (262,144 total tokens) TTFT & decode benchmarks on RTX Spark N1X (SM 12.1):
    • 2b375: 32K TTFT 24.85s -> 19.63s (+26.6%), 128K TTFT 85.96s -> 75.06s (-10.91s), 255K TTFT 205.73s -> 164.07s (-41.66s, +25.4%), 128K decode 97.13 tok/s.
    • 3b3_16: 32K TTFT 29.96s -> 24.47s (+22.4%), 128K TTFT 101.07s -> 86.50s (-14.57s), 255K TTFT 234.31s -> 197.38s (-36.94s, +18.7%).

Copilot AI 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.

🟡 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
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

Base automatically changed from tlwu/20261031/qmoe-cuda-int2-tc to main November 2, 2026 23:08
…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>
- 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>
@tianleiwu
Tianlei Wu (tianleiwu) enabled stacked auto-merge (squash) November 3, 2026 20:46
@tianleiwu
Tianlei Wu (tianleiwu) merged commit f7e70ad into main Nov 3, 2026
@tianleiwu
Tianlei Wu (tianleiwu) deleted the tlwu/qfn_rtx_spark_opt branch November 3, 2026 20:47
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants