fix(dspark): reduce V2 working VRAM - #27
Conversation
32fdb44 to
d82b645
Compare
ced1c66 to
92d4c2b
Compare
|
Sorry, I missed this one |
dspark_inv_rope_bf16_layout and dspark_markov_greedy_argmax (and the two Triton kernels backing the latter) have no production callers anywhere in the tree; the live draft path uses the BF16 WO_A projection and the fused probabilistic Markov sampler (dspark_markov_probs_sample). Drop the dead wrappers, kernels, and their standalone tests.
92d4c2b to
f056ff7
Compare
Upstream vllm-project#48849 re-added `and self.data_parallel_size > 1` to `ParallelConfig.use_sequence_parallel_moe`, which disables sequence-parallel MoE for every DP=1 deployment — including DeepSeek-V4-Flash served at TP=2 with expert parallelism, where it costs ~5% decode throughput. The rationale comment directly above the property is about replication across the *tensor* parallel group: with the all_reduce at the end of attention, every TP rank holds the same tokens, so under EP the experts redo the same work on every rank. That argument does not depend on data_parallel_size, and the code had drifted away from its own comment. vllm-project#48849 was motivated by memory — ~5.6 GiB of SP buffers on a pure-TP Nemotron 550B — but it measured only memory, never throughput, and concluded "no benefit for any model" at DP=1. That does not hold here. Measured on DeepSeek-V4-Flash, 2x RTX PRO 6000 Blackwell (SM120), TP=2 + EP, DP=1, FP8 KV, DSpark MTP, at a fixed 250 W cap: C=1 decode (2 sets) 274.6 / 276.7 -> 288.2 / 289.8 tok/s (+5%) prefill 1K 5,948 -> 6,259 tok/s (+5%) prefill 4K 7,518 -> 7,536 tok/s (flat) available KV cache 11.44 GiB -> 11.44 GiB (identical) GPU KV cache tokens 1,189,790 -> 1,189,790 (identical) peak activation 1.89 GiB -> 1.13 GiB i.e. on this configuration the SP buffers cost no KV capacity whatsoever, so the trade vllm-project#48849 describes is not present and only the slowdown is. Draft acceptance was flat at 80.7% across both configurations, and GPU utilization stayed at 90-92%, so this is expert-path work and not a scheduling or speculation artifact. If the pure-TP memory regression still needs addressing, the gate should be made config-aware (or user-selectable) rather than switching SP-MoE off for all DP=1 deployments.
|
Merged — thanks, this was a substantial piece of work and it holds up well under review. The seven Validation on GB10 (2× SM121a, TP=2, MTP2, mml 49152, util 0.85, fp8 KV)
Unit suite green: 795 kv_offload, 6 DSv4 sparse-SWA/ubatch/indexer, 35 rejection sampler. Re-gated after merging 34 upstream commits on top: arthur c=1 2/2, IMA 0, and GSM8K three times Kernel-test A/B at Review notesThe parts that needed the most scrutiny were the in-place mutations, since geometry- and
Two deltas relative to your current branch head1. 2.
Your own commit message points at the right answer: make the gate config-aware rather than Two things worth knowing
Closing as merged. Thanks again. |
Upstream moved fused_q_kv_rmsnorm out of deepseek_v4/common/ops into the shared vllm/models/common/ops (kimi_k3 uses it too) and updated deepseek_v4/attention.py. nvidia/dspark.py carries our PR #33 / #27 deltas, so the merge kept our side of that import line and it went on pointing at the old path. Nothing catches this statically: every file compiles and there is no conflict marker. It only shows as ImportError when the DeepSeek-V4 package is first imported -- i.e. the model fails to load at serve time.
Two upstream commits drive every one of the 20 conflict hunks: 38a466e [DSV4] Sequence Parallelism (vllm-project#46789) df71917 [DSv4 Perf] Optimize workspace reuse for eager break (vllm-project#49236) Sequence parallelism (dspark.py, model.py) ------------------------------------------ _use_sequence_parallel requires expert-parallel AND (deep_gemm_mega_moe or DP>1), so it is inert on the SM12x config (Marlin MXFP4 MoE, DP=1) even at 4-node TP=4 + EP. It is still wired correctly, because this branch serves multi-node users whose configs can reach it, and a half-applied SP is worse than none. nvidia/dspark.py resolves to ours byte-for-byte. PR #27 replaced the two-kernel mhc_post + hc_head sequence with the fused mhc_post_hc_head_tilelang, which never materializes the [T, hc_mult, H] intermediate -- and that intermediate is exactly what upstream's sp_all_gather needs between the two kernels. Upstream's DeepseekV4DecoderLayer/ModuleList shape is also incompatible with our DSparkLayer, and sp_shard on dim 0 would split DSpark blocks across ranks, breaking the batch_size = num_input_rows // block_size geometry. nvidia/model.py combines both sides, plus three SP correctness edits the merge did not produce on its own: the auto-merged sp_all_gather of hidden_states sits ahead of tail consumers that still take a sharded residual, so the deferred mhc_post path is now disabled under SP and materialization is forced before the gather. Upstream's `if layer is not None:` is not adopted; it is an unbound local when a PP rank owns no layers. Workspace reuse (csrc, attention.py, cache_utils.py) ---------------------------------------------------- Upstream split the fused qnorm-rope-kv-insert op into an allocating `..._insert` and a caller-buffered `..._insert_out` with an explicit q_head_padded argument. We adopt that shape wholesale and retire our local 53b6d11 variant: it solved the same problem, upstream's split is a strict superset, and the merged tree was already half-way there (ops.impl registered both, attention.py auto-merged to call `_out`). Keeping ours would mean reverting an upstream change on a file upstream actively develops -- the same reason we retired vllm-project#48304/vllm-project#48911/vllm-project#48959. ops.h had auto-merged into a broken hybrid, carrying our void/q_out declaration of the base name alongside upstream's `_out`; taking upstream's .cu without fixing it is an ODR mismatch at link time. torch_bindings.cpp's base schema had likewise auto-merged to our `Tensor! q_out` text. attention.py keeps PR #27's value: q is written in place whenever padded_heads == n_local_heads (true on SM12x TP=2), falling back to upstream's eager-scratch pool and then to our class scratch. All three satisfy the op's q_out.size(1) == q_head_padded contract. Side effect: amd/dspark.py:243 already called the 9-argument allocating form, which was broken against our out-form schema. Adopting upstream's shape fixes that pre-existing ROCm break. cache_utils.py takes upstream's `output_buffers` tuple and keeps our four dtype/device asserts on top of its shape-only pair. The d64074e block-table gather bound is untouched. Also updated because the op split is a cross-file contract: - fix_functionalization.py registered only the old name, so our hot path (now `_out`) would have silently lost its copy elision. Both forms are registered with the correct mutated-args maps. - flashinfer_sm120_decode.py and the two kernel-test helpers move to the new signatures.
Summary
Reduces DeepSeek V4 / DSpark V2 working VRAM and increases usable KV cache on the 2x SM120 DSpark serving config.
Main changes:
mtp.n_local_heads == padded_heads; in that case the fused qnorm/RoPE/KV-insert op can write Q in place.[T, hc, H]just to producehc_headoutput and Eagle aux means.[T, hc, H]allocation.[T, H]profile allocation per rank without changing kernel math or launch count.Live memory result
Measured on 2x RTX PRO 6000 Blackwell / SM120, TP=2, DSpark V2, GMU 0.975, max model len 524288.
Net from baseline: +1.07 GiB available KV and +110,442 KV tokens.
Validation
git diff --check, targetedpy_compilepassed.tests/v1/spec_decode/test_dspark_config.py,test_rejection_sampler_utils.py,test_dspark.pypassed (48 passed).30 passed).7*8 -> 56.309.2 tok/s, median310.9, range304.7-311.4, versus current-step post-JIT baseline median297.7.6106 / 8004 / 7710 / 6369 tok/sat1K / 4K / 16K / 65Kprompt sizes.17 + 25 -> 42.tests/v1/spec_decode/test_dspark_config.py->2 passedtest_dense_draft_logits_index_mapping_matches_state_indexedandtest_gumbel_sample_stores_processed_logits_inplace->2 passed20 passed; deployed profile11.61 GiB/1,205,591tokens /2.30x; warm decode A/B307.6vs307.5 tok/s; final deployed warm median310.0 tok/s; coherency17 + 25 -> 42.max_tokens=500,temperature=0, thinking off):c=2275.7 aggregate tok/s (52.95% draft accept),c=4393.2 tok/s (55.07%),c=8577.9 tok/s (56.92%).286.5 tok/s; 65,539-token prompt median287.9 tok/s; both ~7% below the310.0 tok/sshort-context median with no further 64K degradation.