KVTop
Serving · Multi-GPU · Communication

Your GPU finished the kernel. What is the next stage waiting for?

≈ 59%of each token's 58 ms57.8 ms per token minus 23.6 ms of GPU kernel time: an arithmetic difference, not a measured interval
13–20 µsmoving the datadecode-sized message, one way
n = 1evidenceoriginal run output; one profile, trace files no longer available
Core result · measured
−48.2%24 KiB (one decode row): one-way time24.9 µsNCCL12.9 µsmailbox−27.4%192 KiB (eight decode rows): one-way time27.0 µsNCCL19.6 µsmailbox
How much handoff latency did the mailbox prototype remove? Lower is better. One GPU pair without usable P2P; one-way time is half the mean of 200 timed round trips after 20 warm-ups, receiver always ready. Python/ctypes prototype against NCCL 2.29.7, not the current Rust pprelay, and not an end-to-end serving gain. Larger messages go the other way (next figure). The benchmark script was later committed at 223d25be8; the exact revision that produced this run was not recorded. Values checked byte-for-byte against the original run output.
pair
GPU 0 ↔ GPU 1 on one 8×RTX 5090 rental host, no usable P2P
implementation
Python/ctypes GPU mailbox prototype (the design input for pprelay), not the Rust pprelay crate
stack
torch 2.13.0+cu130, NCCL 2.29.7
repeats
one run: 200 timed round trips after 20 warm-ups; no repeat runs
date
2026-10-01
code revision
script committed at 223d25be8; run revision not recorded

Source: raw stdout of the mailbox ping-pong prototype (internal); one-way = half the logged round trip · 223d25be8 (script as committed; run revision not recorded)

You’re profiling a multi-GPU inference run. One stage finishes its computation, but the next stage doesn’t start immediately. There’s a gap in the timeline.

What happens inside that gap?

The activations need to reach the next GPU. Before that can happen, processes may need to coordinate, buffers need to be ready, and execution dependencies need to be satisfied. Each of those steps can contribute to the time a request spends waiting.

If we want to shorten that wait, we first need to understand what it contains.

Are we waiting for the data to move, or for the transfer to begin?

It’s tempting to describe the whole gap as “communication overhead.” That label is useful until we need to decide what to change.

Consider two possible cases. In one, the transfer begins promptly, and moving the tensor takes most of the time. In the other, the tensor is ready, but coordination delays the start of the transfer.

Both leave the next stage waiting. They point to different optimizations.

A faster transfer path could help the first case. In the second, much of the delay may remain even if the copy itself becomes faster. We need to inspect how the handoff is initiated and what must happen before it can proceed.

How do we see what’s happening between the two stages?

Start by following a single handoff through the timeline:

  • When does the producing stage finish?
  • When is the transfer submitted, and when does data movement actually begin?
  • When is the data available to the receiving stage?
  • When does that stage begin its computation?

Figure 1 · illustration, not data
SCHEMATIC · example ordering; spacing is not durationProducerfinishesTransfersubmittedMovementbeginsDataavailableConsumerstartsReady → submitted: inspect coordination and submission dependencies.Submitted → movement: inspect stream, event and resource waits.Movement → available: trace the actual transfer path.Available → consumer: inspect scheduling and execution dependencies.CPU and GPU work may overlap. This is not a measured or additive time breakdown.
Which events must we align to explain the handoff gap? Illustration only, not the missing real trace. These are event markers to collect on a shared timebase. A transfer can include multiple copies; preposted operations and overlapping streams require dependency analysis rather than summing isolated durations.
scope
Conceptual single-handoff event map
scale
Event spacing has no time meaning

Source: Explanatory illustration for the section: How do we see what’s happening between the two stages?

Those events help separate coordination, data movement, and the wait before the receiving computation starts.

They also keep the analysis honest. Work on different CPU threads and GPU streams can overlap, so adding up individual operation durations may not explain the elapsed time. The useful question is which dependencies are holding up this request.

What led us to investigate this in pprelay?

Our work involved a single eight-GPU host whose tested configuration had no usable GPU peer-to-peer transfer path. That made the host-mediated handoff between stages worth examining.

Figure 2 · measured
QuantityRecorded / derived valueEvidence boundary
Inter-token latency, profiled runp50 57.8 ms (p90 60.0 ms)One 300-token request at c = 1; a second profiled run gave p50 57.9 ms
GPU busy time, all 8 stages118.0 ms over 5 profiled steps → 23.6 ms per stepSum of per-stage busy time from the profiler; overlap between stages not checked
Arithmetic residual57.8 − 23.6 = 34.2 msNot a measured host-coordination duration
Same configuration after a restartp50 51.1 ms and 51.0 msTwo later runs; the 57.8 ms level did not recur, so the interval itself is not stable across restarts
What does the recorded profile actually establish? One decode-side profile of an eight-stage pipeline on one 8×RTX 5090 host: 131,465-token prompt, c = 1, 300 output tokens. Per-stage GPU busy time is summed and divided by the five profiled steps; subtracting it from the token interval leaves an arithmetic residual, not a timed host-coordination interval. The raw traces were not kept. After a restart the same configuration ran at about 51 ms per token, so treat these numbers as one recorded profile, not a stable breakdown. Engine revision not recorded; the profiler traces are no longer available. Values checked byte-for-byte against the original run output; the 34.2 ms residual is arithmetic, not a measurement.
host
one 8×RTX 5090 rental host, decode side, eight pipeline stages, no usable P2P
workload
131,465-token prompt, c = 1, 300 output tokens (finish = length)
profiler
torch profiler, GPU activity only, 5 steps
repeats
one profiled run (plus one profiled rerun and two later unprofiled runs, listed)
date
2026-10-03
code revision
not recorded

Source: raw benchmark and profiler-summary output (internal)

The question was concrete: once a stage had produced its output, what work remained before the next stage could use it?

pprelay explores that handoff using shared-memory coordination and pinned tensor slots between processes on the same machine. It works within the available transfer path; it does not enable hardware P2P.

Figure 3 · illustration, not data
DOCUMENTED DESIGN · host-mediated tensor path, not a performance traceMetadata / controlTensor dataProducer GPUWait for output readyPinned shared-host slotRegistered by both endsConsumer GPUWait for data-readyD2HH2DHeader FIFO: tensor names, shapes and offsetsPublish data-ready after D2H completes.ACK after H2D completes → sender may reuse this host slotThe receiver need not finish compute before the host slot is reusable.Unsupported / oversized messages use the documented fallback; no hardware P2P is enabled.
How does pprelay hand a tensor over through the host? Schematic of the historical tensor-mailbox design, not current implementation validation. Metadata and tensor readiness are distinct. The sender waits for source readiness and the previous slot acknowledgement before D2H; data-ready follows D2H, and ACK follows receiver H2D. This ACK protects the host slot; source-buffer reuse has separate completion fences.
scope
Same-host tensor-mailbox design in PPRELAY.md §3.2
data_path
Producer GPU → registered pinned host slot → consumer GPU
control
Header FIFO plus stream-ordered data/ack flags; not a measured timeline

Source: src/reports/2026-09-30-vllm-gguf-b/PPRELAY.md:89–103, especially 94–95 (data/ACK order), 98 (fallback), 103 (source-buffer fences)

The aim is to reduce the overhead of passing work between stages. Whether that helps a particular deployment depends on how much of its request latency comes from that overhead.

The separate historical prototype microbenchmark below shows where a host-mediated mailbox beat NCCL and where it did not; it is not evidence of a serving speedup.

Figure 4 · measured
0.42×0.71×1×1.24×1.47×no change−48%24 KiB12.9 vs 24.9 µs−27%192 KiB19.6 vs 27.0 µs+28%1.5 MiB69.8 vs 54.7 µs+36%48 MiB1,782.8 vs 1,308.4 µsmailbox ÷ NCCL one-way time (below 1 = mailbox faster)
Where does the mailbox advantage stop paying off? Historical Python/ctypes prototype: lower latency at the two smaller measured sizes, higher latency at 1.5 MiB and 48 MiB. One-way estimate = half mean RTT over 200 timed iterations after 20 warmups, with receiver ready. This includes the benchmark path and is not a serving coordination measurement. Lack of chunked pipelining is a possible explanation to test, not a causal result of these four points. The benchmark script was later committed at 223d25be8; the exact revision that produced this run was not recorded. Values checked byte-for-byte against the original run output.
pair
GPU 0 ↔ GPU 1 on one 8×RTX 5090 rental host, no usable P2P
implementation
Python/ctypes GPU mailbox prototype (the design input for pprelay), not the Rust pprelay crate
stack
torch 2.13.0+cu130, NCCL 2.29.7
repeats
one run: 200 timed round trips after 20 warm-ups; no repeat runs
date
2026-10-01
code revision
script committed at 223d25be8; run revision not recorded

Source: raw stdout of the mailbox ping-pong prototype (internal); one-way = half the logged round trip · 223d25be8 (script as committed; run revision not recorded)

If the handoff gets faster, will the request get faster too?

That is the next measurement.

A handoff benchmark can show that a local operation improved. We then need to put it back into the pipeline and check whether the request finishes sooner, or whether the serving system completes more work under the same conditions.

Keep the model, GPU layout, and request distribution fixed. Verify execution ordering and safe buffer reuse alongside performance. Then inspect the timeline again: did the gap shrink, and did that change reach the end of the request?

That is the result we want to understand.

The next time a GPU finishes its computation and the following stage stays idle, zoom in on the space between them. Finding out what it is waiting for gives us a concrete place to start.

Method

HardwareOne 8×RTX 5090 rental host, pipeline over 8 ranks; no usable P2P in the tested setup
TopologyPCIe only; transfers staged through host memory
RuntimeSGLang fork (D side); exact commit not recorded in the report
Transportpprelay design: shared-memory coordination + pinned tensor slots (prototype measured)
Message sizes24 KiB, 192 KiB, 1.5 MiB, 48 MiB
TraceD-side profile at c = 1, 128K context (131,465-token prompt), 5 profiled steps; trace files no longer available
Repeatsnot recorded

Evidence and references

Code and data for this note will be released as open source; more is coming.

What this does not show

Mailbox and NCCL numbers at different message sizes do not add up to a general advantage; a local iteration time and a whole-machine token rate cannot be chained into one causal result.