Repository navigation
[Feat] QSA: Add cache sharding for Qwen3.8 hybrid models - #43527
shiyang814-cpu wants to merge 6 commits into
Conversation
Expand deferred block indices, including the pending tail, before the owner fallback maps logical token IDs to KV slots. Validate cache-sharding groups against the model's KV-head replica width using attention TP size, and reject unsupported QSA plus DCP composition. Keep configurator results in physical rows and convert once through the runner and allocator. Pass logical capacity unchanged to worker request limits, scheduler admission, load reporting, and pool invariant checks. Add regressions to the existing QSA and scheduler test files. Normalize formatting in the touched files and fix the unresolved Optional annotation. Validation: - Targeted suite: 96 passed, 1 skipped, 555 subtests passed. - Related suite: 244 passed, 17 skipped, 161 subtests passed. - Final focused rerun: 24 QSA tests and 15 capacity/admission tests passed. - Four-GPU owner attention agrees with FP32 reference in eager, static, and CUDA Graph execution (maximum absolute error 0.005144). - H20 TP8/sharding4 completes 512K and 1M prefill smoke requests. - TP4/sharding4 with 24Q/2KV is rejected before weight loading. - Ruff selected lint rules, isort, formatting, and git diff --check pass. Validation limit: synthetic random-token generation is not token-exact against the unsharded baseline. These regressions and kernel checks do not establish full-model deterministic equivalence. Refs: sgl-project#43527
1e06478 to
0c69e62
Compare
a588d40 to
d8a5294
Compare
…ls (TP-sharded QSA states, distributed top-k, owner attention, FP32 LSE merge, graph comm buffers) Squashed net diff of shiyang814-cpu#43527 (base 72730d6) applied with git apply -3; 27 conflict hunks hand-reconciled so stack semantics survive: - _qsa_write_plan: 5-tuple(valid) x 7-tuple(cross-prefix) -> 8-tuple; ring snapshot (_snapshot_cross_prefix_members read-before-store, host-prefix gate) threaded through; _overwrite_cross_prefix_groups gains owner_rows so straddling groups honor cache-shard ownership (slot-0 inert when unsharded, owner-masked skip when sharded). - metadata.get_prefill_mqa_inputs: PR sharded local-page-table branch (prefill_page_table set only when sharding active) + our fused qsa_pack_prefill_compressed_keys path kept for the unsharded default. - sparse_attn: KV_IS_FP8 descale (merge-4 d6b9eb1) + PR RETURN_LSE in union; (total_q,device_index) geometry sig + batch-invariance gate intact. - graph_metadata: 10-tuple launcher (num_groups + world/rank union). - capacity: scheduler/tp_worker drop * kv_shard_widening (runner reports logical incl page-interleave); configurator reserves graph comm pools. Gate: --qsa-cache-sharding-size (alias --state-cp-size) default 1 = OFF; validation short-circuits when 1 (prod TP4 QSA behavior byte-unchanged). Our CPU write-plan tests adapted to the 8-tuple; test_qsa ours+theirs unions.
… stubs, CUDA/ops gates, guard-safe helper extraction - test_qsa.py: our write-plan CPU tests unpack the new 8-tuple (+valid); PR's unguarded page_sharded topk tests get the file's CUDA skipif. - test_qsa_fused_kv_prepare.py: __new__ stub pools must publish the same construction contract the PR's backend __init__ demands from real pools (cache_sharding_runtime size=1); eligibility + GPU-window stubs fixed. - test_qsa_fp8_kv.py: trtllm-descale stub gained qsa_profile/_trtllm_counters and the pre-existing device-cuda failure is now an honest CUDA skip. - test_custom_all_reduce_vmm_guard.py (pre-existing, unrelated): positive path needs the custom-AR ops extension -> skipif when not loaded. - backend/metadata: prefill host-length tolist and sharded index_select pack moved into dedicated helpers so the stack's source-scoped hot-path guards (no host materialization / no eager gather chain) keep protecting the unsharded hot path exactly as before. - srt qsa_indexer: drop now-unused get_is_capture_mode import (our sync-free RoPE capacity contract intentionally replaced the PR's guarded item() path); restore typing.Tuple for the snapshot signatures.
…ps: whitelist validate_replayssm_spec_algorithm and validate_linear_lossless_verify at their pipeline positions; list --enable-linear-lossless-verify and --qsa-cache-sharding-size in the undocumented-flag allow list Co-Authored-By: opencode <noreply@opencode.ai>
STACK=qwennext-stack-204-2026-10-15, code tip c8792c2. Entries: 200 sgl-project#43527 QSA cache sharding (default OFF), 201 sgl-project#39716 tokenizer-clone preprocessing (DEFAULT-ON), 202 sgl-project#38650 reconcile (upstream adopted, entry-156 helper dropped), 203 sgl-project#42388 linear lossless-verify (default OFF, replayssm-exclusive A/B arm), 204 no-GPU test maintenance trio. Header/CAVEATS: FI 0.7.1 venv bump marked resolved (entries 162-164 PCIe-IPC ACTIVE next restart; 10-09/10-14 INERT notes annotated HISTORY), sgl-project#44115 re-skipped at 0.7.1 (uniform_q_len still absent from wrapper plan()), former DEFER/DEDICATED-WINDOW block replaced with resolution pointers, new BEHAVIOR WATCH bullet for the restart. Co-Authored-By: opencode <noreply@opencode.ai>
The PR sgl-project#43527 port added the LSE stride slots and RETURN_LSE to the paged chunk-prefill launch, but _sparse_gqa_chunk_prefill_paged has no LSE output. The two extra positionals shifted onto NUM_KV_HEADS, so every paged chunk prefill (SGLANG_QSA_CHUNK_PREFILL_KV_SOURCE=paged, or the dense-pack OOM fallback) died with "dynamic_func() got multiple values for argument 'NUM_KV_HEADS'". The same port's owner LSE launch predated this branch's K/V descale slots and KV_IS_FP8; pass unit descales and the FP8 flag so it launches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
0fb8a3a to
4f0a18f
Compare
…entries 122,198,200,211,215,220,267,268,305 Re-plants the scheduler.py fork-hunks (replant/...managers__scheduler.py.fork-hunks.patch) onto the post-sgl-project#46977 admission structure. tp_worker marker consumed (15/23 B4 markers still on the tree; per-file counts, damage-state from entry 310's map). PER-HUNK DECISIONS (all VERIFIED against the merged tree, not taken on faith): - entry 122 (sgl-project#40458): grammar-sync overlap disable RE-PLANTED for mixed mode (is_decode() OR is_mixed()) + the fork's trimmed comments; upstream's host-draft-only arm was the pre-sgl-project#40458 form (origin/main verified: absent). - entry 220 (sgl-project#39765, OPEN port): _has_pending_mamba_cache_update + the early pop_and_process/short-circuited disable-overlap RE-PLANTED into event_loop_overlap; the require_mlp_sync/disable/extra-buffer gate verbatim. - entry 198 (sgl-project#37612): THE CATCHER — B3-5 landed the raiser; the try/except MambaCowAllocError around init_next_round_input (res=NO_TOKEN, request stays queued; the revert block + unconditional alloc_group_end were already in the merged tree = the no-leak halves VERIFIED PRESENT) RE-PLANTED with the import. COW component test re-anchored to the #46970x kv_storage seam the B3-5 replant moved _state() behind (stub was the pre-split mamba_allocator shape). - entry 268 (sgl-project#46643, OPEN port -- VERIFIED NEVER MERGED: git log 12dc623..2a6c204 --grep 46643 = ZERO; only sgl-project#45491/sgl-project#45435/sgl-project#45472/sgl-project#45670 of the briefed merged-list arrived in-range): the UNCONDITIONAL batch_is_full clear RE-PLANTED on the scheduler side, subsuming the entry-198 hybrid-SSM arm and the priority-preemption/is_empty arms (policy side lands in B4-3). - entry 267 (sgl-project#46496): retract_decode new_token_ratio FLOOR clamp RE-PLANTED. - entry 211: triton_load_watch.prewarm_device_loads(self.model_worker) line RE-PLANTED between install() and mark_serving_started (the utils-side function survived the merge; only the call site was dropped). - entry 200 (sgl-project#43527) CAPACITY PAIR RE-PLANTED: model_runner .logical_max_total_num_tokens passes allocator= again (the configurator's PageInterleavePoolAllocator -> allocator.size branch SURVIVED the merge byte-identical, so the merged tree had the fork predicate with upstream's consumer-side multiplications = DOUBLE-COUNT); the three scheduler * kv_shard_widening multipliers back to the fork form (pool-stats shape, load_inquirer, and the init_req one-page-per-shard subtraction); tp_worker drops page_interleave_shard_size x + import. Upstream's invariant-checker hunk: OBSOLETE (the constructor no longer takes max_total_num_tokens -- absorbed by refactor). - entry 215 (sgl-project#44351/sgl-project#44401/sgl-project#44420): VERIFIED ALREADY PRESENT at the merge (filter_batch-every-step + reset ride, num_held_rows, budget_state FIRST statement, PD-gate deletion all byte-compare to the fork form) -- DEDUPED, no edit. Entry 107's local early-revision of sgl-project#40940 stays superseded (entry 215 rule), and its re-plant here would double-count. - entry 220's/305's pause-window busy accounting + write_fence_stream + is_empty arm: VERIFIED PRESENT (entry 305 landed patch-identical via sgl-project#46127; the fence assignment is the B3-2 controller half) -- DEDUPED. TEST FIXTURE RE-ANCHORS (not B2's: no markers, pure new-upstream-contract drift, per the entry-198 fixture-adaptation precedent): the two hybrid-ssm admission stubs gain disagg_prefill_bootstrap_queue=None (upstream's routing-key bootstrap_queue arg in calc_priority arrived in-range). PRUNE vs BREAKAGE (managers −523sub audit): the three managers test files vanished in-range (test_schedule_policy_dfs_weight, test_scheduler_batch_is _full_latch, test_uno_token_accounting) were ALL deleted by upstream's OWN sgl-project#45672 prune (3438ed6). Two are byte-identical upstream copies = zero fork delta. The third carried a 3-line fork comment/stub delta whose SEMANTICS the re-planted unconditional clear pins: its case was the is_empty-arm mirror of test_scheduler_hybrid_ssm_admission_latch (present, green, hybrid_swa/non-hybrid arms included) -- NOT re-homed, recorded. ACTUALS (damage -> after, CVD=9, prod venv, PYTHONPATH=python, per-file): test_scheduler_hybrid_ssm_admission_retry 4F -> ** 4P ** test_scheduler_hybrid_ssm_admission_latch 3F -> ** 4P ** test_mamba_prefill_cache_boundary 2F/12subF -> ** 5P/12sub EXACT (220 pin) ** test_scheduler_retract_ratio 1F -> ** 2P EXACT (267 pin) ** test_scheduler_init_req_max_new_tokens 1F -> ** 10P/6sub ** test_scheduler_grammar_mixed_tail_sync 1F -> ** 4P ** adjacent scheduler-stub files (held_row, running_batch_finished, pause_generation, disagg_idle, dp_converted, hicache_events) 56P/64sub UNMOVED -- no regression from the clear/catcher/capacity edits. Lint: ruff format + F821/E9 clean on all five files; imports OK (no GPU execution, no push). Co-Authored-By: OpenCode <noreply@opencode.ai>
…ge damage — entries 61,75,133,137,140,162,176,178,201,247,300,110 LAST B4 COMMIT: 14 markers consumed (correction to B4-3's body: the scheduler/schedule_policy pair was NOT deleted there -- consumed here, so the true session census is 23/23, B4=0 on the tree, foreign B1 15/B2 2/B5 23 INTACT (grep census).) IO_STRUCT 1F VERDICT (the session-1 NEEDS-ATTENTION flag): NOT a fork-delta loss, NOT env -- UPSTREAM'S OWN guard extension vs stale fork stub: _validate_joint_schema_request grew a pointer_head arm in-range (origin/main tokenizer_manager.py:1455, ModelConfig.pointer_head), while our test's SimpleNamespace stub predated it. Fixed test-side pointer_head=None, matching the pattern the two sibling stubs (test_hidden_state_server_mode, test_sampling_mask_validation) already adopted. 1F -> file GREEN. RUNNER-ZONE MERGE DAMAGE (attributed AT ae6ee52 in a scratch worktree: identical 7F there, NOT my regressions; tsv flashinfer_autotune/decode rows were B4-classified without a tree marker): - decode_cuda_graph_runner: _init_model_hooks DEFINITION lost at the merge while its __init__ CALL and the replay _replay_prepare_hook CONSUMER survived = every real DecodeCudaGraphRunner build dies AttributeError (boot-integrity class, same shape as B3-8's pool-side half). Re-planted VERBATIM from the entry-61 version (validate_model_support + the needs_replay_prepare-gated prepare_cuda_graph_replay hook; qwen4_exp still defines both). - test_flashinfer_autotune_gate 5F: upstream grew TWO post-suite arms (kv-sharding get_parallel read + the block-FP8-as-MXFP8 model walk); pinned both in _should_run per the file's OWN per_tensor_fp8 precedent. - test_graph_routing_context 2F: brand-new upstream file driving the fork-instrumented replay(); added the entry-61 hook off-pin to the __new__-built stub (entry-198 fixture-adaptation class). PER-MARKER DECISIONS: - prefill_cuda_graph_runner (300): RE-PLANT the ONE dropped line, the validate_model_support() call before capture (base_runner's method + model_context/capture/_capture/replay-context arms VERIFIED PRESENT -- entry-300 union landed at merge). - model_runner (300,187/sgl-project#43527): sgl-project#43527 half done IN B4-1 (allocator= kwarg); entry-300 half VERIFIED PRESENT (get_model().override(shared_experts=...) at split_prefill + forward ctx). - eager_runner (61,176): fold VERIFIED LANDED (model_context execute, shared_read_event import retarget, forward_context(get_forward_context()) decode wrap, resolve_prefill_shared_read_ends rename, cp_extend pair). - runner_utils/__init__ (176): SUPERSEDED BY DESIGN -- upstream made the package initializer import-free (its own docstring says so) and ALL consumers import defining submodules (grep-audited, incl. the cp_extend pair used by eager/base runners). - custom_all_reduce_v2 (75): SUPERSEDED with evidence -- upstream's own de26bb1 (sgl-project#46853) REMOVED the multi-node VMM allocator gate in-range (absent on origin/main); entry 75's LEGACY guard + shared probe live in custom_all_reduce.py:106 + cuda_vmm_utils:102; unit/distributed 156P/1S/ 35sub unmoved (the entry 308/309 guards stay intact). - logits_processor (162): RE-PLANT the one dropped arm, pcie_ipc=True on the vocab _logits_gatherer (the MultimemAllGatherer pcie_ipc API + whole transport stack survived the merge -- only this opt-in call kwarg was lost); the out=/fp32-buffer/cast_to_float32 arms VERIFIED PRESENT. pcie selection/ar tests green. - loader (178): RE-PLANT the sgl-project#42419 kv-scales-drop block (entries 178's recorded net-diff half) between the mxfp8 rename and the new-in-range monokernel wrapper; shared_experts resolve/override/attr arms VERIFIED PRESENT. unit/model_loader 161P/19S/91sub green. - serving_responses (110,201): RE-PLANT both _preprocess offloads (the PreprocessExecutor machinery landed complete via upstream's sgl-project#39716 absorption -- serving_base._preprocess + tokenizer_manager wiring + executor tests green); ADAPTED the _process_messages site with functools.partial to carry upstream's new-in-range output_constrained kw-only arg through the positional-only run() surface. - qwen3_coder_detector (20,21,89,108-109,157-160): fold VERIFIED LANDED (math/jsonschema/referencing imports, tuple-return _get_arguments_config, _parse_finite_float, anyOf/oneOf union arm, parameters threading at both sites) -- merged with our streaming-string region (561 discards parameters DELIBERATELY, our entry-159 path needs config only). - validation_hook (44,51) + moe_hook (247): VERIFIED -- union landed complete (no lost defs by AST diff; NGRAM+replayssm reject, b12x validator + FLASHINFER_B12X_W4A16 enum present; server_args 7F stays B6's re-pin). - component_manager (140): RE-PLANTED IN FULL from the 309 tip -- the merge DROPPED the whole parallel-stage half while parallel_executor.py CALLS supports_parallel_stage_group/parallel_stage_group (LIVE runtime crash class) + AST-proved the ported methods byte-equal, _uses_seen/ ResidentStrategy/ResidencyState(batch_is_warmup) all present, the 309->HEAD removed WarmupPhasePeak trio has ZERO callers (upstream's own in-file deletion). Diffusion import chain blocked at this seat by the missing imageio wheel (ENV class -- diffusion never ran here); battery = AST + py_compile + the executor's own unit contract file. - mm scheduler (133,137): fold VERIFIED LANDED (all upstream-side symbols present: _wait_idle/_FINALIZE_IDLE_WAIT_S/release_wait_s/progress_reporter). ACTUALS (damage -> after, CVD=9, prod venv, per-file): test_io_struct 1F -> ** 68P/2S/10sub ** unit/model_executor/runner ** 7F -> 103P/2S/73sub EXACT-GREEN ** (7F proven merge-era at ae6ee52 in a scratch worktree) unit/model_loader 161P/19S/91sub GREEN; unit/distributed 156P/1S/35sub UNMOVED; pcie selection+ar GREEN. DIR unit/managers 1025P/83F/9S/427sub -> ** 1056P/2F/9S/474sub ** (the 2F = the two DOCUMENTED order-dependent test_mm_process_config ENV-class F's, reproduce at base -- damage-map population preserved). DIR unit/spec 363P/20F/21S/238sub/2err -> ** 388P/0F/21S/238sub/0err ** (seven consecutive full-dir runs EXACT; the one unrepeated 1F sighting during iteration is recorded as a non-reproducing flake, candidate class = the same cross-file module state as managers' mm_process_config pair). test_pcie_ipc_vocab_gather 27F IDENTICAL at ae6ee52 = register_cuda_ci 4-gpu-b200 file run without GPU (ENV class, not this batch's dirs). serving_responses module import blocked by openai 2.6.1 vs required ==2.8.0 (upstream's own beam.py, pre-existing at merge -- ENV class). DoD(c) FULL-DIR totals recorded above; DoD(d) trailing_endpoint re-run ** 17P = 17P ** post-B4 (B3 gate unmoved). Lint: ruff format + F821/E9 clean across all touched files. No GPU; no push. Co-Authored-By: OpenCode <noreply@opencode.ai>
4f0a18f to
b44c973
Compare
|
[BBuf Review Agent] Could you split it below 3000 changed lines (now 4625)? |
|
[BBuf Review Agent] Could you split it below 3000 changed lines (now 4146)? |
BBuf
left a comment
There was a problem hiding this comment.
[BBuf Review Agent] A few things to fix before this can go in.
| "raw K/V write layout must match the owner-local cache: " | ||
| f"cache={tuple(cache_k.shape)}, buffer={tuple(key_buffer.shape)}" | ||
| ) | ||
| owner_mask, loc = sharding.local_write_targets(loc) |
There was a problem hiding this comment.
[BBuf Review Agent] With shard size 4, production writes still go through the hybrid layer router directly to full_kv_pool, so this localization never runs. Owner attention reads slot // 4 while the writer uses the global slot, producing wrong KV reads and eventually out-of-bounds writes. Could we apply the mapping on the routed write path?
| compressed_page_table, | ||
| compressed_lengths, | ||
| max_model_len, | ||
| min_scored_len=self.block_topk if use_fast_topk else 0, |
There was a problem hiding this comment.
[BBuf Review Agent] With shard size 4 and 256 compressed blocks per rank, this skips every local score even though the global merge still has to choose 512 of 1024 blocks. _maybe_merge_distributed_topk then ranks values from torch.empty, so token selection is incorrect. Could we disable the scoring shortcut when distributed merging is active?
| forward_batch, | ||
| topk_indices: torch.Tensor, | ||
| ) -> torch.Tensor: | ||
| output = self._dispatch_owner_sparse_attention( |
There was a problem hiding this comment.
[BBuf Review Agent] On B200 with BF16 queries and head dimension 256, decode returns from _try_fused_kv_attention before reaching this owner dispatch. That path uses global slots against the rank-local cache and skips the owner merge, causing incorrect reads or out-of-bounds accesses. Could we disable fused eligibility for sharded pools until it handles this layout?
|
[BBuf Review Agent] Could you split it below 3000 changed lines (now 3107)? |
|
/tag-and-rerun-ci |
|
/rerun-failed-ci |
Rebase the validated cache-sharding feature state onto the latest upstream main while preserving owner-local attention and distributed candidate merge behavior.
Preserve graph-safe shard ownership and widening semantics while removing unused owner packing and consolidating redundant transport tests.
Inline the owner attention pipeline and replace redundant test matrices with focused multi-GPU coverage so the change remains reviewable without affecting measured performance.
Send cache-sharded QSA writes through the pool ownership mapping and exclude sharded layouts from the incompatible fused decode path.
Keep the local fast top-k path while forcing initialized scores whenever rank-local candidates participate in a global merge.
Consolidate sharding setup and regression fixtures so the PR remains below the 3,000-line review threshold without changing runtime behavior.
|
/rerun-failed-ci |
3936f90 to
c35bb78
Compare
Summary
This PR introduces QSA cache sharding for QSA-enabled Qwen3.8 hybrid models. It shards the sequence-length-dependent QSA states:
The implementation reuses SGLang's page-interleave placement and allocator infrastructure, and adds:
The implementation is functionally validated and benchmarked with the Qwen3.8-Flash-Next-FP8 checkpoint on NVIDIA H20 GPUs.
Performance
The benchmark uses the
Qwen3.8-Flash-Next-FP8model on 8 NVIDIA H20 GPUs with TP8 and EP8. It uses random token IDs with an output length of 1, a maximum concurrency of 1, and a chunked prefill size of 8192, with Decode CUDA Graph enabled and Radix Cache disabled.QSA cache sharding increases the logical QSA cache capacity from
2,692,032to10,218,240tokens, approximately 3.80x.Concurrent Long-Context Workload
With an input length of 1M tokens, 8 requests, and a maximum concurrency of 8:
All 8 requests completed successfully in both configurations.
Accuracy
The standard SGLang GSM8K evaluation used 1,314 examples. The LongBench-v2 evaluation used 120 examples filtered to 10K-200K context lengths, with temperature
0.6, top-p0.95, and up to16,384output tokens.Both evaluations used
chunked_prefill_size=8192.The sharded results remain close to the unsharded baseline on both short-context reasoning and long-context evaluation.
Motivation
The QSA raw K/V history and compressed cache grow linearly with sequence length.
Before this change, every TP rank stored the complete QSA history. This caused two long-context bottlenecks:
The impact becomes increasingly significant as the context length grows. On Qwen3.8-Flash-Next, the unsharded implementation spends substantially more time scanning and maintaining QSA history at 512K and 1M context lengths.
This PR assigns each QSA cache page to one owner rank. Each rank stores and scans only its local shard while preserving the exact unsharded QSA semantics.
Implementation
QSA cache placement
QSA raw K/V history and compressed cache reuse SGLang's existing page-interleave placement and allocator infrastructure.
Each QSA cache page is assigned to one owner rank. The scheduler, request token mapping, cache allocator, and QSA KV pools operate on the same widened logical slot space, while each rank stores only its local physical shard.
The allocator supports both prefill and decode allocation, keeping cache placement consistent throughout the request lifecycle.
Distributed QSA execution
Each rank scans only its local compressed-cache shard and produces local candidates.
The local candidates are merged into an exact global Top-K using deterministic global token-ID tie-breaking. The selected tokens are then routed to their raw K/V owners for sparse-attention computation.
Owner-side partial outputs are merged using FP32 log-sum-exp normalization, preserving the original unsharded QSA semantics.
The distributed path supports both eager execution and CUDA Graph replay.
CUDA Graph buffers
QSA communication uses fixed-address buffers for CUDA Graph compatibility.
All QSA layers share one collective buffer arena because the layers execute serially and do not require concurrent per-layer storage.
For the largest tested graph bucket with 48 QSA layers, this reduces the collective arena from approximately
4.42 GiBto94.35 MiB.CUDA Graph metadata
QSA CUDA Graph metadata construction is limited to compressed pages visible to the current sequence.
Unused page-table tails are not cleared during every replay because downstream consumers are bounded by
graph_compressed_lengths.This preserves correctness while reducing metadata overhead for long contexts.
Memory accounting
The KV cache configurator reserves NCCL CUDA Graph private pools and PLE prefill workspace before sizing the KV cache.
The QSA collective arena additionally uses exact byte accounting verified against its allocated tensor storage.
Usage
--qsa-cache-sharding-size 4creates a four-rank QSA cache-sharding group inside the TP group.Testing
The implementation is validated with the Qwen3.8-Flash-Next QSA configuration.
Correctness coverage includes:
Production-path distributed integration tests run with:
The distributed prefill and decode outputs are compared against the unsharded reference, including CUDA Graph replay after input mutation.
Test results:
test_qsa.py: 201 passed, 1 skippedtest_qsa_kv_pool.py: 5 passedgit diff --check: passedScope
This PR only shards QSA cache states for Qwen3.8-Flash-Next-style hybrid QSA models.
The following remain unchanged:
Pipeline parallelism and PD disaggregation are not supported by QSA cache sharding in this PR.
CI States
Latest PR Test (Base): ⏳ Run #42155951214
Latest PR Test (Extra): ❌ Run #42155951040
Latest PR Test (AMD ROCm 10): ❌ Run #42155951136
Tip
Base CI only covers the core cases, and its workload may not exercise your change. Use
/rerun-testto run the tests your change touches, or copy this prompt for an agent: