perf(ds4): compute greedy argmax on GPU (monolithic and layer split)#531
Open
Graffioh wants to merge 6 commits into
Open
perf(ds4): compute greedy argmax on GPU (monolithic and layer split)#531Graffioh wants to merge 6 commits into
Graffioh wants to merge 6 commits into
Conversation
Graffioh
force-pushed
the
codex/ds4-gpu-argmax
branch
from
July 16, 2026 13:01
ef28977 to
274f11a
Compare
Graffioh
force-pushed
the
codex/ds4-gpu-argmax
branch
from
July 16, 2026 19:19
2fd6f64 to
22a4cbe
Compare
davide221
force-pushed
the
codex/ds4-rocmfpx-server
branch
from
July 17, 2026 05:33
e943f2a to
d5fae56
Compare
Graffioh
force-pushed
the
codex/ds4-gpu-argmax
branch
2 times, most recently
from
July 17, 2026 15:04
330fc31 to
ce486ff
Compare
Graffioh
force-pushed
the
codex/ds4-gpu-argmax
branch
from
July 17, 2026 17:47
bd8eec5 to
250327d
Compare
Graffioh
force-pushed
the
codex/ds4-gpu-argmax
branch
3 times, most recently
from
July 20, 2026 09:35
2ecaf9c to
8046b89
Compare
Graffioh
force-pushed
the
codex/ds4-gpu-argmax
branch
from
July 20, 2026 09:56
8046b89 to
0aa3048
Compare
Graffioh
marked this pull request as ready for review
July 20, 2026 12:24
Contributor
There was a problem hiding this comment.
1 issue found across 13 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:5961">
P2: Greedy layer-major prefill still transfers the full vocabulary logits buffer and computes argmax on CPU, so prompts with 5..`DS4_MAX_LAYER_MAJOR_PREFILL_TOKENS` miss the advertised 4-byte GPU argmax path. Consider adding an argmax output mode to `ds4_try_layer_major_prefill` rather than using `layer_major_logits_scratch`.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| is_last_shard && out_logits && ds4_backend_is_gpu(backend)) { | ||
| is_last_shard && (out_logits || out_argmax) && | ||
| ds4_backend_is_gpu(backend)) { | ||
| std::vector<float> layer_major_logits_scratch; |
Contributor
There was a problem hiding this comment.
P2: Greedy layer-major prefill still transfers the full vocabulary logits buffer and computes argmax on CPU, so prompts with 5..DS4_MAX_LAYER_MAJOR_PREFILL_TOKENS miss the advertised 4-byte GPU argmax path. Consider adding an argmax output mode to ds4_try_layer_major_prefill rather than using layer_major_logits_scratch.
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 5961:
<comment>Greedy layer-major prefill still transfers the full vocabulary logits buffer and computes argmax on CPU, so prompts with 5..`DS4_MAX_LAYER_MAJOR_PREFILL_TOKENS` miss the advertised 4-byte GPU argmax path. Consider adding an argmax output mode to `ds4_try_layer_major_prefill` rather than using `layer_major_logits_scratch`.</comment>
<file context>
@@ -5887,16 +5952,31 @@ bool deepseek4_step_layer_range(
- is_last_shard && out_logits && ds4_backend_is_gpu(backend)) {
+ is_last_shard && (out_logits || out_argmax) &&
+ ds4_backend_is_gpu(backend)) {
+ std::vector<float> layer_major_logits_scratch;
+ std::vector<float> & layer_major_logits =
+ out_logits ? *out_logits : layer_major_logits_scratch;
</file context>
Contributor
Author
There was a problem hiding this comment.
this will be done eventually in another PR to not clutter this one
The DeepSeek4 target-shard daemon derived its argmax output from !want_logits, conflating the two independent flags of the shared target-shard protocol. The generic daemon loop reads want_argmax and want_logits off the wire separately and writes the per-position argmax vector whenever want_argmax is set, but the DeepSeek4 forward callback never populates resp.argmax. A request that set want_argmax (alone or with logits) would therefore fail the loop's argmax size check, break the stream, and drop the argmax payload. DeepSeek4's GPU argmax only yields the final-position greedy token (returned via last_tok), not the per-position vector want_argmax promises, so reject want_argmax explicitly instead of silently desyncing, and rename the derived flag to want_final_token so it no longer masquerades as the protocol flag. Behavior is unchanged for the greedy (want_logits=0) and sampling (want_logits=1) paths the client actually uses; matches the Laguna/Gemma4 daemons' independent handling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgRW2HdErpbMYgp94DzMSq
Integrate the DS4 per-device HC-scratch scoping, full HC-state handoff between local shards, per-request split-cache clearing, and single-device monolithic dispatch from main with this branch's GPU greedy-argmax work. The two feature sets both changed deepseek4_step and deepseek4_step_layer_range: main added a `device` parameter (2nd arg), this branch added an `out_argmax` output. The merged signatures carry both; every call site was updated accordingly. Conflict resolutions: - deepseek4_step_layer_range prologue: keep both guards — this branch's logits/argmax XOR validation (+ argmax init) and main's per-device HC selection. - monolithic decode (backend.cpp): keep this branch's moe_hybrid vs layer-range branch so non-hybrid greedy decode still returns the GPU argmax token, and thread cfg_.device.gpu into both calls (preserving main's per-device scoping). - reset_request_state / test_reset_request_state: take main's version (reset_deepseek4_cache helper + remote-reset error check + real-buffer zero assertions); it already clears prefill_last_logits_, so this branch's top-of-function clear and its dead hc_size capture are dropped. - dspark verify and deepseek4_step delegation call sites: combine the device argument with the out_argmax argument. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgRW2HdErpbMYgp94DzMSq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Compute the DeepSeek4 greedy argmax in the GGML GPU graph and return a single token id instead of the full
n_vocab(129280) logits vector. Applies to the all-hot monolithic backend and to local/remote layer-split execution. Sampling and penalty requests keep the full-logits path; the hybrid MoE path keeps its CPU fallback.What changed
Performance
Reduces the greedy return payload from ~505 KiB (full
n_vocablogits) to 4 bytes (one token id) per token. The saving is small on this hardware — a unified-memory copy or a local IPC transfer was never the bottleneck — and scales with the transport cost:Correct and non-regressive in every configuration measured.
Follow-up
DSpark speculative decode does not benefit from this change: its greedy verify step reads back the full per-position logits (
all_logits_out) to check each drafted token, so it never touches the single-output GPU argmax path. A future PR could extend the same idea to the verify — compute the per-position argmax on the GPU and return the drafted token ids instead of q × full-logits — alongside skipping the output projection on non-final prefill positions.Validation
invalid argmax tokenlayer_beginshards (returned -1); the last chunk's argmax is now propagatedRebase notes
Rebased onto main after DSpark spec decode (#496), indexed layer-major prefill (#520), and sampler-history fixes (#527/#536). Greedy DSpark seeds from the retained argmax token; layer-major prefill satisfies argmax-only requests via a host-side scan; fused verify graphs require an argmax tensor only when built to compute one.