perf: faster pipeline decode: TPS 10%-40% and Layer loading 60%+ (token relay + bandwidth-aware placement) - #2229
Open
jgawronek wants to merge 1 commit into
Conversation
Three changes that together raise multi-node pipeline decode throughput, plus a lockstep fix: 1. Token-relay decode: skip the per-token all_gather of the full hidden state. Only the last pipeline rank computes final norm / lm_head and samples; the sampled token ids are circulated to all ranks via a single-int collective, and other ranks drop the lm_head from their lazy graph entirely. Requests with logprobs fall back to the legacy lockstep path so rank 0 keeps real logits. 2. Bandwidth-aware layer allocation: nodes report their GPU name (nvidia-smi on Linux, system_profiler on macOS); placement estimates per-chip memory bandwidth and loads the fastest nodes to their memory caps first, instead of splitting layers proportionally to RAM. Falls back to RAM-proportional when any node's hardware is unrecognised. 3. Link-speed-aware ring host selection: Linux interfaces are classified via sysfs with their negotiated link speed, and ring neighbours prefer the fastest measured link (e.g. a DGX Spark's 200GbE ConnectX port over its management ethernet). Behaviour on macOS is unchanged. Fix: pipeline ranks now agree on KV prefix cache hits. Hybrid SSM/attention models sharded so a rank holds only attention layers can restore a different prefix length than SSM-holding ranks; ranks then prefill different token counts and deadlock the ring. Ranks gather their hit lengths and discard the hit everywhere on disagreement. Also adds per-token decode communication timing (recv/send/gather) logged at debug level every 64 tokens. Co-authored-by: Cursor <cursoragent@cursor.com>
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.
Multi-node pipeline decode currently pays three per-token costs that this PR removes or reduces:
Token-relay decode — during decode, every rank all_gathers the full hidden state and redundantly runs final norm + lm_head + sampling. With this change only the last pipeline rank samples; the sampled token id is circulated to all ranks with a single-int collective, and the other ranks never compute lm_head at all (it drops out of the lazy graph). Requests with
logprobsautomatically fall back to the legacy lockstep path so rank 0 keeps real logits.Bandwidth-aware layer allocation — layers were split proportionally to available RAM, which overloads slow nodes in heterogeneous clusters (per-token decode latency is the sum of per-stage times, and stage time is bytes-read / memory-bandwidth). Nodes now report their GPU name (
nvidia-smion Linux,system_profileron macOS), placement estimates per-chip memory bandwidth from a lookup table, and loads the fastest nodes to their memory caps first. Unrecognised hardware falls back to the existing RAM-proportional split, so behaviour is unchanged for clusters the table doesn't know.Link-speed-aware ring host selection — Linux interfaces were all classified
"unknown"(interface typing shelled out to macOS-onlynetworksetup), so ring links between Linux nodes were chosen arbitrarily. Interfaces are now classified via sysfs with their negotiated link speed, and ring neighbours prefer the fastest measured link — e.g. a DGX Spark's 200GbE ConnectX port over its management ethernet. macOS behaviour (Thunderbolt first) is unchanged.Also fixes a pipeline deadlock: shards of hybrid SSM/attention models (Qwen3-Next, Qwen3.5, Nemotron-H) can restore different KV prefix-cache lengths on different ranks — SSM shards need state snapshots, attention-only shards don't. Ranks then prefill different token counts and hang the ring's send/recv chain on the second request of a conversation. Ranks now gather their hit lengths and discard the hit everywhere when they disagree.
Adds per-token decode communication timing (recv / send / gather) logged at debug level every 64 tokens for future tuning.
Results
Measured on a heterogeneous 4-node cluster (M3 Ultra Mac Studio 256GB + 2× DGX Spark GB10 + RTX 3090) and smaller mixes:
Test plan
uv run pytest— 448 passed, including new two-process distributed ring tests covering token relay (last rank samples, all ranks converge on its token, non-last ranks skip lm_head)uv run basedpyrightanduv run ruff checkclean