Skip to content

perf(ds4): compute greedy argmax on GPU (monolithic and layer split)#531

Open
Graffioh wants to merge 6 commits into
Luce-Org:mainfrom
Graffioh:codex/ds4-gpu-argmax
Open

perf(ds4): compute greedy argmax on GPU (monolithic and layer split)#531
Graffioh wants to merge 6 commits into
Luce-Org:mainfrom
Graffioh:codex/ds4-gpu-argmax

Conversation

@Graffioh

@Graffioh Graffioh commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

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

  • greedy prefill/decode emit one int32 argmax token instead of ~505 KiB of logits
  • cached, dynamic, fused, and layer-major output graphs build only the requested logits-or-argmax path
  • CPU/CUDA/HIP argmax tie-breaking unified to the lowest token id
  • prefix-cache snapshots record their terminal representation (logits vs argmax); an incompatible restore falls back to fresh prefill

Performance

Reduces the greedy return payload from ~505 KiB (full n_vocab logits) 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:

  • monolithic HIP (Strix Halo, unified memory): greedy 24.8 vs sampled 23.2 tok/s. Greedy edges ahead, though that gap also reflects greedy skipping the sampler, not the argmax read alone.
  • layer split, 3090 CUDA[0,10) + Strix Halo HIP[10,43): greedy 12.4 vs sampled 12.2 tok/s — within noise; per-token time is compute-bound on the remote shard and the local IPC copy is cheap.
  • scales up for networked shards (eliminates ~505 KiB/token of boundary traffic) and faster-compute setups where the fixed transfer is a larger share of per-token time.

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

  • unit tests (CPU + CUDA + HIP): output-path selection, argmax tie-breaking, snapshot compatibility + fresh-prefill fallback, sampler history
  • GPU e2e: monolithic HIP greedy/sampled; layer-split CUDA+HIP greedy/sampled — valid tokens, no invalid argmax token
  • fixed during GPU validation: layer-split greedy prefill dropped the argmax on the compressor-split batch path for non-zero-layer_begin shards (returned -1); the last chunk's argmax is now propagated

Rebase 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.

@Graffioh
Graffioh force-pushed the codex/ds4-gpu-argmax branch from ef28977 to 274f11a Compare July 16, 2026 13:01
@Graffioh Graffioh changed the title perf(ds4): compute layer-split greedy argmax on GPU perf(ds4): compute greedy argmax on GPU across execution paths Jul 16, 2026
@Graffioh Graffioh changed the title perf(ds4): compute greedy argmax on GPU across execution paths perf(ds4): compute greedy argmax on GPU( monolithic and layer split) Jul 16, 2026
@Graffioh Graffioh changed the title perf(ds4): compute greedy argmax on GPU( monolithic and layer split) perf(ds4): compute greedy argmax on GPU (monolithic and layer split) Jul 16, 2026
@Graffioh
Graffioh force-pushed the codex/ds4-gpu-argmax branch from 2fd6f64 to 22a4cbe Compare July 16, 2026 19:19
@davide221
davide221 force-pushed the codex/ds4-rocmfpx-server branch from e943f2a to d5fae56 Compare July 17, 2026 05:33
@Graffioh
Graffioh force-pushed the codex/ds4-gpu-argmax branch 2 times, most recently from 330fc31 to ce486ff Compare July 17, 2026 15:04
@Graffioh
Graffioh force-pushed the codex/ds4-gpu-argmax branch from bd8eec5 to 250327d Compare July 17, 2026 17:47
@Graffioh
Graffioh changed the base branch from codex/ds4-rocmfpx-server to main July 17, 2026 17:49
@Graffioh
Graffioh force-pushed the codex/ds4-gpu-argmax branch 3 times, most recently from 2ecaf9c to 8046b89 Compare July 20, 2026 09:35
@Graffioh
Graffioh force-pushed the codex/ds4-gpu-argmax branch from 8046b89 to 0aa3048 Compare July 20, 2026 09:56
@Graffioh
Graffioh marked this pull request as ready for review July 20, 2026 12:24

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will be done eventually in another PR to not clutter this one

Comment thread server/src/deepseek4/deepseek4_target_shard_ipc_daemon.cpp Outdated
Graffioh and others added 2 commits July 21, 2026 14:56
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant