Skip to content

perf(glm52): launch-ahead decode — hide the ~0.7ms cuGraphLaunch under the previous step's execution#570

Merged
xiaguan merged 5 commits into
mainfrom
feat/glm52-device-fed-decode
Jul 5, 2026
Merged

perf(glm52): launch-ahead decode — hide the ~0.7ms cuGraphLaunch under the previous step's execution#570
xiaguan merged 5 commits into
mainfrom
feat/glm52-device-fed-decode

Conversation

@xiaguan

@xiaguan xiaguan commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

The whole-step decode graph's `cuGraphLaunch` costs ~700µs of host-side driver work per rank per step with the GPU idle through it (root-caused in the #568 stagger investigation; the double-replay probe measured a 5.4µs intra-step replay gap vs 810µs at an unhidden boundary). This PR enqueues the next step's replay while the current step still executes:

  • a feed kernel advances `token_ids`/`positions`/`slot_mapping`/`seq_lens` in place from the step's device argmax; rope rows re-gather on device; the argmax D2H lands in per-bucket pinned buffers so the copy is truly asynchronous. Zero graph changes — everything is eager launches stream-ordered behind the executing replay, so the leased path is byte-identical by construction (parity-gated).
  • speculation is all-ranks-or-none: a speculative replay is a full set of collectives. The coordinator decides both flags on global data — `lease` (next step repeats this shape, incl. model-length headroom) and `consume` (this step IS the speculation: unchanged shapes AND no slot handoff). Rank-side checks are hard ensures, not silent fallbacks.
  • every bucket × tier graph is pre-captured at startup (captured-ness must be uniform; the old mid-serving capture stall goes away).

Measured (jz-38 8×H200, same-day baselines)

metric main launch-ahead
inter-step device idle (graph-trace own-gap) 641–804 µs 43.0 µs (p90 44.8)
cross-rank step-start spread 350–364 µs (max 965–1097) 32.6 µs (max 42.5)
`cuGraphLaunch` p50 700 µs on the critical path 660 µs, fully overlapped
solo ms/step ×5 19.6–20.4, run-to-run wander 19.5, zero variance

Gates (full suite on the final commit): determinism + byte-parity vs the main record PASS; 8-way identical & 8-way 128-tok concurrent (391 tok/s aggregate; 1–2/8 land on the known deterministic near-tie variant — the established cross-bucket contract); staggered-admission churn + post-churn determinism PASS; post-disconnect + teardown PASS.

Desync lesson (cost one debug round)

The first gate run desynced: the near-tie variant hit EOS early, that rank's inputs mismatched its speculation and it silently fell back (two replays) while the others consumed (one) — the collective pairing slipped into the ~100s DeepEP device-timeout trap. Fix: both decisions moved to the coordinator on global data; every rank-side condition became an invariant.

Toxic review: Request Changes → all findings fixed in the last commit: the `(0,0)` in-band padding sentinel (a single-token prompt `[0]` legally feeds `(0,0)`) replaced by an explicit `active_rows` in `Glm52StepShape`; pre-capture failure now broadcasts Shutdown before returning (serial-join deadlock); the lease/consume decision extracted to a pure `launch_ahead_flags()` with 4 unit tests; `Glm52StepFlags` struct instead of bare bools; launch-ahead state machine split into `model/launch_ahead.rs` (1k-line cap); feed-kernel row cap tied to the batch const at compile time.

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 464491ad62

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +525 to +529
log::error!(
"GLM5.2 graph pre-capture (bucket {bucket}, full_tier {full_tier}) \
failed on rank {rank}: {err:#}"
);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Broadcast shutdown on pre-capture failure

When any pre-capture response returns an error, this early return skips the normal shutdown broadcast at the bottom of run_dp8_coordinator. Because Glm52RankWorker::drop sends Shutdown and joins one worker at a time, a rank that has already set up DeepEP can block in its collective context drop while the remaining ranks have not been told to exit; a startup graph-capture failure then hangs until the device timeout instead of shutting down cleanly. Broadcast request_shutdown() to all workers before returning here and in the submit-error branch above.

Useful? React with 👍 / 👎.

xiaguan and others added 5 commits July 5, 2026 11:16
… the ~0.7ms cuGraphLaunch

The whole-step graph's cuGraphLaunch costs ~700us of host-side driver work
per rank per step with the GPU idle through it (measured on jz-38; the
double-replay probe showed an intra-step replay gap of 5.4us vs 810us at an
unhidden boundary). This change enqueues the next step's replay while the
current step still executes:

- a feed kernel advances token_ids/positions/slot_mapping/seq_lens in place
  from the step's device argmax (rope rows re-gathered on device),
- the argmax D2H lands in pinned host buffers so the copy is truly async,
  and the speculative launch goes out before the host blocks on it,
- the coordinator grants a lease only for pure single-token decode steps
  with nothing queued; the rank consumes the speculation only when the next
  command matches exactly (bucket, slots, inputs), so any admission, EOS,
  or spec-decode round degrades to the full prologue path and the stale
  replay is a harmless overwritten recompute (rows are isolated).

Byte-identical by construction on the leased path: the feed kernel writes
exactly the values the host prologue would have written.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cudarc's memcpy_dtoh copies the DESTINATION's byte count — an 8-row pinned
buffer against a smaller bucket's argmax scratch read past the device
allocation (CUDA_ERROR_INVALID_VALUE on the first step). The landing
buffers now live in Glm52BucketState, sized exactly rows, next to the
scratch they mirror.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A speculative replay is a full set of collectives, so both the decision to
speculate and the decision to consume must be uniform across ranks — the
first gate run desynced when the 1/8 near-tie variant hit EOS early: that
rank's inputs mismatched its speculation, it fell back to the full path
(two replays) while the others consumed (one), and the collective pairing
slipped into the ~100s DeepEP device-timeout trap.

- the coordinator now decides both flags on global data: 'lease' (next
  step will repeat this shape; includes the model-length headroom check —
  pad rows never outrun active rows) and 'consume' (this step IS the
  speculation; requires unchanged shapes AND no slot handoff, since a
  finish+admission can reuse a slot id under an identical shape),
- every bucket x tier whole-step graph is pre-captured at startup while
  the ranks are trivially in lock-step, making captured-ness uniform and
  retiring the mid-serving capture stall,
- all rank-side conditions became hard ensures: a rank that cannot honor
  a lease fails the step instead of silently skipping into a desync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-804us -> 43us, solo 19.5 ms/step

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…apture shutdown broadcast, testable flag decision

- Glm52StepShape carries active_rows: a padding input is not
  value-distinguishable from an active one (single-token prompt [0] feeds
  (0,0)), so the sentinel classification could kill the engine via the
  consume desync ensure; argmax finite/vocab checks narrowed to active rows
- pre-capture failures broadcast Shutdown before returning (the sequential
  worker Drop otherwise deadlocks in the collective destroy barrier)
- lease/consume decision extracted to launch_ahead_flags() + unit tests;
  Step carries a Glm52StepFlags struct instead of two bare bools
- launch-ahead state machine split into model/launch_ahead.rs (1k line cap)
- compile-time tie of the feed kernel's 32-row cap to the batch const

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@xiaguan xiaguan force-pushed the feat/glm52-device-fed-decode branch from 464491a to 672e064 Compare July 5, 2026 15:18
@xiaguan xiaguan merged commit 632b965 into main Jul 5, 2026
1 check passed
@xiaguan xiaguan deleted the feat/glm52-device-fed-decode branch July 5, 2026 15:24
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