feat(qwen35): overlap Unified prefill with active decode - #786
Conversation
Signed-off-by: CAICAIIs <3360776475@qq.com>
Signed-off-by: CAICAIIs <3360776475@qq.com>
Signed-off-by: CAICAIIs <3360776475@qq.com>
92538a4 to
f9f9cc2
Compare
Signed-off-by: CAICAIIs <3360776475@qq.com>
Signed-off-by: CAICAIIs <3360776475@qq.com> # Conflicts: # pegainfer-qwen35/src/scheduler.rs # pegainfer-qwen35/src/unified_forward.rs # pegainfer-server/src/bin/bench_serving/main.rs # pegainfer-server/src/main.rs
FeathBow
left a comment
There was a problem hiding this comment.
Ran this on sm_89, CUDA 12.9, Qwen3.5-4B.
| # | Check | Result |
|---|---|---|
| 1 | qwen35 lib tests / kernels lib tests | 74 + 15 passed |
| 2 | Qwen3.5 hf_golden_gate |
2 passed, deltas match your table |
| 3 | Qwen3 hf_golden_gate (cross-model probe of the shared kernels change) |
1 passed |
| 4 | Shared-SM lifecycle e2e | pass |
| 5 | Boundary parity: bg = max_batch - 1 decoders so bucket 4/32 actually replays during an 8192-token prefill, overlap witnessed structurally, all 16 injection steps asserted bitwise (token id, selected logprob bits, top-5 ids and value bits) |
pass at both boundaries |
| 6 | Gate-lifted probe: max_batch=64, 40 decoders overlapping successive prefills, oracles on both sides, off-vs-off / stream-vs-stream controls |
no corruption; all divergence reproduces in off-vs-off |
Not covered: TP2, sm_120, HTTP layer, performance (cross-arch numbers aren't comparable).
| impl CliDecodeOverlap { | ||
| #[cfg(feature = "qwen35")] | ||
| pub(crate) fn resolve(self) -> openinfer_qwen35::Qwen35DecodeOverlap { | ||
| match self { |
There was a problem hiding this comment.
should be pegainfer. As-is, cargo build --features qwen35 fails with E0433.
btw consider unifying this enum with the same-named one in pegainfer-server/src/config.rs (different variants and fallibility).
| | GPU | 1x NVIDIA GeForce RTX 5090, 32 GB | | ||
| | Driver / CUDA toolkit | `595.71.05` / `12.8.93`, target `sm_120` | | ||
| | Rust / Triton build env | nightly 2026-07-10 / Triton 3.7.1 | | ||
| | Source | benchmark binaries: `c405b556` on upstream/main `e3f91120`; the RLCR follow-up only narrows generic-vs-prefill override classification and was separately GPU-lifecycle verified | |
There was a problem hiding this comment.
Could you add the exact server command (--max-batch, --max-prefill-tokens, CUDA Graph, features)? These binaries predate the --max-batch <= 32 gate and the server default (64) no longer starts — if the runs used the default, the table needs a re-run at <= 32.
| /// cuBLAS handle today; running them while async prefill is in flight would make | ||
| /// two streams share that handle/workspace. Keep overlap opt-in below that | ||
| /// boundary until the larger decode buckets have an independent GEMM route. | ||
| pub const MAX_SHARED_SM_DECODE_BATCH: usize = ops::GEMM_LT_MAX_N; |
There was a problem hiding this comment.
Two suggestions here
-
Add
const _: () = assert!(MAX_SHARED_SM_DECODE_BATCH <= ops::GEMM_LT_MAX_N);and a back-reference at the tune filter (weights.rs:691) — the equality is load-bearing. (Non-bucketmax_batchis fine: the loader rounds atweights.rs:203; verified at mb=5.) -
The "workspace-backed" rationale in this comment needs an update: cuBLAS 12.9 says
cublasSetStream()"unconditionally resets the cuBLAS library workspace back to the default workspace pool", andgemm_cudacalls it every invocation — so the 32 MiB bound incublas_init(linear.cu:234) detaches after the first GEMM and is a dead allocation today (remove it, or re-bind per stream). The accurate rationale for the gate: both paths share one handle's default pool, replay-vs-concurrent-call isolation is undocumented, and the docs flag multi-stream single-handle as nondeterministic — per-stream workspaces/handles are the documented fix, and the clean path to bucket-64 later. The gate itself is right; my sm_89 probes found no corruption, which bounds nothing on other archs.
Signed-off-by: CAICAIIs <3360776475@qq.com>
Thanks for the validation! I pushed a follow-up commit ( I also validated the follow-up on RTX 5090 / sm_120 / CUDA 12.8:
|
FeathBow
left a comment
There was a problem hiding this comment.
LGTM on the impl :)
Two non-blocking suggestions: retain decoder-side parity in the Shared-SM E2E, and rerun the HTTP A/B with explicit --max-batch 32.
Summary
Closes #715.
Qwen3.5 previously executed Unified prefill chunks serially before active decode. This PR adds an opt-in Shared-SM overlap path so one prefill chunk can run on a dedicated CUDA stream while active decode continues on the model stream.
The default remains unchanged:
--decode-overlap off.Changes
Qwen35DecodeOverlap::{Off, SharedSm}.--decode-overlap off|streamto the server and serving benchmark CLI.ITL_STEPactions:overlap_launch,overlap_decode,overlap_complete, andoverlap_wait.scheduler-policy=autowithdecode-overlap=stream;Results
Verified on 1x NVIDIA RTX 5090 32 GB, CUDA 12.8,
sm_120, with Qwen3.5-4B.All HTTP runs completed with zero failures, timeouts, error strings, or zero-token outputs. The c1 tradeoff and higher loaded TTFT keep overlap opt-in.
Nsight Systems confirmed concurrent prefill/decode kernel execution across the two CUDA streams. The trace contained 28,082 cross-stream kernel-overlap pairs.
Verification
73 passed, 6 ignored.overlap_waitactions.2 passed, 2 ignored.cargo fmt --all -- --check: passed.git diff --check: passed.The evidence covers single-GPU Qwen3.5-4B on RTX 5090. It does not claim tensor-parallel overlap, Green Context support, prefix-cache coverage, broad hardware coverage, vLLM parity, SOTA, or production readiness.