feat(ds4): add concurrent serving on Strix Halo and R9700 + Strix - #598
feat(ds4): add concurrent serving on Strix Halo and R9700 + Strix#598Graffioh wants to merge 14 commits into
Conversation
f5e8a26 to
17600d8
Compare
|
@cubic-dev-ai review this PR |
@Graffioh I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
3 issues found across 25 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="server/src/deepseek4/deepseek4_graph.cpp">
<violation number="1" location="server/src/deepseek4/deepseek4_graph.cpp:7249">
P2: For long-running ratio-4 lanes, crossing each compression boundary rebuilds the whole-model graph instead of replaying it, causing recurring latency spikes and reducing serving throughput. Use a capacity-padded compressed-history topology with runtime visibility masking, or otherwise keep the graph shape stable across row-count changes.</violation>
</file>
<file name="server/src/common/concurrency/seq_slot_manager.cpp">
<violation number="1" location="server/src/common/concurrency/seq_slot_manager.cpp:175">
P2: When a stochastic request uses `seed=0`, this branch replaces the requested deterministic stream with `std::random_device`, so repeated requests cannot reproduce. Use `needs_logit_processing()` alone as the seed gate and seed the RNG with zero.</violation>
</file>
<file name="server/src/deepseek4/deepseek4_backend.cpp">
<violation number="1" location="server/src/deepseek4/deepseek4_backend.cpp:527">
P2: When hybrid placement uses ratio-4 layers, `estimate_ds4_cache_bytes` underestimates the indexer compressor state by one factor of two. Charge the actual `2 * index_dim` state width so the expert budget does not leave the subsequent cache allocation without enough VRAM.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| cache.plan.physical_blocks, ratio, prepared[(size_t) il])) return false; | ||
| for (const auto & row : prepared[(size_t) il]) { | ||
| key.push_back((int64_t) row.raw_history.size()); | ||
| key.push_back((int64_t) row.compressed_history.size()); |
There was a problem hiding this comment.
P2: For long-running ratio-4 lanes, crossing each compression boundary rebuilds the whole-model graph instead of replaying it, causing recurring latency spikes and reducing serving throughput. Use a capacity-padded compressed-history topology with runtime visibility masking, or otherwise keep the graph shape stable across row-count changes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/deepseek4/deepseek4_graph.cpp, line 7249:
<comment>For long-running ratio-4 lanes, crossing each compression boundary rebuilds the whole-model graph instead of replaying it, causing recurring latency spikes and reducing serving throughput. Use a capacity-padded compressed-history topology with runtime visibility masking, or otherwise keep the graph shape stable across row-count changes.</comment>
<file context>
@@ -6854,6 +7156,259 @@ static bool initialize_layer_range_cache(
+ cache.plan.physical_blocks, ratio, prepared[(size_t) il])) return false;
+ for (const auto & row : prepared[(size_t) il]) {
+ key.push_back((int64_t) row.raw_history.size());
+ key.push_back((int64_t) row.compressed_history.size());
+ key.push_back(row.slot < 0 ? -1 :
+ (ratio ? row.position % ratio : row.position % DS4_PAGE_TOKENS));
</file context>
| s.sample_history = prompt; | ||
| // Same predicate the engine uses to pick CPU sampling over GPU argmax: | ||
| // a seed only means anything when the sampler actually draws. | ||
| if (sampler.needs_logit_processing() && sampler.seed != 0) { |
There was a problem hiding this comment.
P2: When a stochastic request uses seed=0, this branch replaces the requested deterministic stream with std::random_device, so repeated requests cannot reproduce. Use needs_logit_processing() alone as the seed gate and seed the RNG with zero.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/common/concurrency/seq_slot_manager.cpp, line 175:
<comment>When a stochastic request uses `seed=0`, this branch replaces the requested deterministic stream with `std::random_device`, so repeated requests cannot reproduce. Use `needs_logit_processing()` alone as the seed gate and seed the RNG with zero.</comment>
<file context>
@@ -0,0 +1,278 @@
+ s.sample_history = prompt;
+ // Same predicate the engine uses to pick CPU sampling over GPU argmax:
+ // a seed only means anything when the sampler actually draws.
+ if (sampler.needs_logit_processing() && sampler.seed != 0) {
+ s.rng.seed(sampler.seed);
+ } else {
</file context>
| // only in fixed-size state scratch and does not scale with context. | ||
| const size_t index_dim = (size_t) w.n_indexer_head_dim; | ||
| total_bytes += comp_cap * index_dim * sizeof(uint16_t); | ||
| total_bytes += state_rows * index_dim * sizeof(float) * 2; |
There was a problem hiding this comment.
P2: When hybrid placement uses ratio-4 layers, estimate_ds4_cache_bytes underestimates the indexer compressor state by one factor of two. Charge the actual 2 * index_dim state width so the expert budget does not leave the subsequent cache allocation without enough VRAM.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/deepseek4/deepseek4_backend.cpp, line 527:
<comment>When hybrid placement uses ratio-4 layers, `estimate_ds4_cache_bytes` underestimates the indexer compressor state by one factor of two. Charge the actual `2 * index_dim` state width so the expert budget does not leave the subsequent cache allocation without enough VRAM.</comment>
<file context>
@@ -503,20 +515,19 @@ static uint64_t estimate_ds4_cache_bytes(const DeepSeek4Weights & w, int max_ctx
+ // only in fixed-size state scratch and does not scale with context.
+ const size_t index_dim = (size_t) w.n_indexer_head_dim;
+ total_bytes += comp_cap * index_dim * sizeof(uint16_t);
+ total_bytes += state_rows * index_dim * sizeof(float) * 2;
+ total_bytes += (size_t) 2 * 2 * ratio *
+ (size_t) w.n_indexer_head * index_dim *
</file context>
| total_bytes += state_rows * index_dim * sizeof(float) * 2; | |
| total_bytes += state_rows * (2 * index_dim) * sizeof(float) * 2; |
Summary
Adds DeepSeek4 paged continuous serving for up to six concurrent lanes on the two qualified resident HIP deployments:
gfx1151), with every expert on the target; andgfx1201) target plus Strix Halo (gfx1151) route-level expert parallelism.It also lets the existing DSpark q=4 path remain resident beside the paged engine. A request admitted alone after the 20 ms idle coalescing window uses the classic speculative singleton path; a coalesced cohort uses the paged autoregressive engine.
Qwen concurrency remains unchanged at up to 64 slots.
Note
The absolute performance table below was measured at retained candidate
92b53237, four commits ahead of the current GitHub PR headdcf895cc. Those commits must be pushed tocodex/ds4-concurrency-strixbefore this is merge qualification rather than candidate qualification.System boundary
The DSpark and paged target caches are independent. DSpark is restricted to the idle singleton path; it is not mixed into a paged cohort.
Implementation
The retained optimization stack:
The four candidate-head additions are:
3c4dbbe8— DSpark singleton fast path;e30e23e8— redundant hybrid output-copy removal;1bb7e054— four expert pairs per MMVQ block; and92b53237— gfx1151 routed-kernel tuning.Current measured performance
Profile:
hip:0), dense work and selected experts;DeepSeek-V4-Flash-0731-ROCMFPX-MIX-STRIX.gguf;--ds4-expert-top-k 4;HumanEval here is a throughput workload; retained gold tests were not executed.
Aggregate throughput scales to 1.93x, 2.39x, and 2.61x the C1 result at C2, C3, and C4.
aggregate_tok_sincludes prefill and TTFT;output_window_tok_smeasures generation after first-token arrival.The retained C1 output contract is 455 completion tokens across the three cases, with matching content, reasoning, finish reason, and per-case hashes across retained comparisons.
Long-context exact prefill
A representative 1,195-token C1 request on the retained tokenwise exact path measured:
Exact prefill is therefore the primary remaining latency bottleneck.
Rejected batching experiments
No experimental prefill batching code is included in the retained candidate:
These candidates were discarded rather than weakening the fixed-profile output contract.
Prior paged-cohort qualification
Before the DSpark singleton extension, the retained paged optimization sequence improved mean C1-C4 output-window throughput by 32.93% over the original
4fa7112dPR head in chained fixed-concurrency A/B/A comparisons. Every retained transition matched its corresponding fixed-C baseline byte-for-byte. The final scheduler step reduced steady-roundhipStreamSynchronizecalls from 695 to 443 while leaving peer copies, event records, stream waits, and kernel count unchanged.This historical index used model-default top-6 routing, no speculative drafter, and at most 128 generated tokens. It is provenance for the paged scheduler changes, not a direct comparison to the current top-4 DSpark profile.
Guardrails
Paged concurrency fails closed for:
Four-expert routing is an explicit approximation from the model-default six experts. Exact prefill describes the prefill implementation, not equivalence to model-default top-6 routing.
Validation
dflash_server: passed;test_deepseek4_unit: passed on R9700 and Strix;test_rocmfp_mix_gateup_glu: passed on R9700 and Strix;test_rocmfpx_mmq: passed on R9700 and Strix;test_deepseek4_mmid_grouped_cuda: passed on R9700 and Strix;Draft limitations