Popping the CPU-GPU Latency Bubble in Inference
Pipelined decoding techniques show that software optimization, not just raw hardware scaling, is the key to maximizing GPU utilization.
When evaluating LLM or vision-language model (VLM) performance, the default instinct is to look at raw hardware specifications. We talk about memory bandwidth, tensor cores, and FLOPS. But in production, the most expensive component of your stack, the GPU, is often sitting idle. It is not waiting for complex mathematical operations to resolve. It is waiting for the CPU to tell it what to do next.
This idle state is known as a GPU bubble. During autoregressive decoding, where a model generates text one token at a time, the sequential nature of the process creates a constant, high-frequency coordination loop between the host CPU and the device GPU. Because token N depends entirely on token N-1, you cannot schedule the next step until the current one is resolved. If your inference engine relies on a naive, blocking execution loop, you are paying a heavy tax in idle silicon.
The Anatomy of the Decode Bubble
To understand why this bubble exists, we have to look at the division of labor during a single decode step. The GPU handles the heavy lifting, executing billions of arithmetic operations to project logits across the model's vocabulary. However, the CPU manages the administrative overhead. It schedules incoming requests, prepares metadata, selects the final token from the GPU's output, and updates the state machine.
In a standard blocking implementation, this process behaves like a strict baton pass:
- The CPU plans and launches the forward pass.
- The GPU executes the kernels.
- The CPU synchronizes, waiting for the GPU to finish and copy the results back to host memory.
- The CPU commits the token, runs termination checks, and plans the next step.
During steps 3 and 4, the GPU's compute engines are completely dark. Because a single token's worth of GPU execution is incredibly fast, especially on modern hardware like the NVIDIA B200, the CPU's fixed administrative overhead becomes the dominant bottleneck.
Pipelining the Execution Loop
The solution to this idle time is pipelined decoding, a technique implemented in Moondream's Photon inference engine to achieve near-realtime VLM inference (approximately 33ms on an NVIDIA B200). The core insight is that the CPU does not need to fully ingest and commit a token before the GPU can start the next forward pass.
The sampled token does not actually need to leave the GPU to begin the next step. The next forward pass can read the newly generated token directly from device memory. While the GPU is busy running that next forward pass, the CPU can asynchronously copy the previous token back to host memory, detokenize it, stream it to the client, and evaluate whether the sequence has reached an end-of-text token.
By overlapping the CPU's administrative bookkeeping with the GPU's execution phase, the idle bubble is effectively hidden.
Implementing Ping-Pong Slots and CUDA Graphs
Executing this pipeline safely without introducing synchronization bottlenecks requires careful memory architecture. If you launch a new forward pass while the CPU is still processing the previous step's outputs, you risk memory corruption.
To prevent this, Photon uses a dual-buffer strategy called ping-pong slots. A DecodeSlot is a pre-allocated bundle of memory containing:
- The input stage (the last generated token and its sequence position).
- The output stage (the logits).
- The destination buffer for the sampled token.
- Metadata for the attention kernels to locate the KV cache.
These buffers are allocated as pinned (page-locked) host memory, enabling asynchronous Direct Memory Access (DMA) transfers. This is critical: allocating GPU memory at runtime is a blocking operation that triggers device synchronization, which immediately reinstates the GPU bubble. Furthermore, keeping these buffer addresses static allows the engine to capture the entire decode step as a CUDA graph, drastically reducing kernel launch overhead.
To run the pipeline, the engine instantiates two of these slots and alternates between them. While the GPU executes the forward pass using Slot A, the CPU processes the results stored in Slot B.
sequenceDiagram
autonumber
participant CPU
participant GPU_Stream as GPU Compute Stream
participant Copy_Stream as GPU Copy Stream
Note over CPU,Copy_Stream: Step N (Slot A) & Step N+1 (Slot B)
CPU->>GPU_Stream: Launch Forward (Slot A)
activate GPU_Stream
GPU_Stream-->>Copy_Stream: Trigger Copy Event
deactivate GPU_Stream
activate Copy_Stream
Copy_Stream->>CPU: DMA Copy Token (Slot A)
deactivate Copy_Stream
CPU->>GPU_Stream: Launch Forward (Slot B)
activate GPU_Stream
Note over CPU: CPU processes Slot A<br/>(Detokenize, Stream, Check End)
GPU_Stream-->>Copy_Stream: Trigger Copy Event
deactivate GPU_Stream
activate Copy_Stream
Copy_Stream->>CPU: DMA Copy Token (Slot B)
deactivate Copy_Stream
Note over CPU: CPU processes Slot B
Both slots queue their forward passes onto a single compute stream, ensuring they run sequentially on the physical hardware. The device-to-host copies, however, are dispatched to a separate copy stream. By anchoring the copy stream to a specific CUDA event recorded immediately after the forward pass writes its outputs, the transfer begins the moment the data is ready, without waiting for subsequent work queued on the compute stream.
The Developer and Infrastructure Angle
For teams architecting AI infrastructure, this optimization shifts the economics of capacity planning. When Moondream's Photon engine applies these pipelining techniques, it delivers up to a 35% increase in decode throughput.
If you are planning infrastructure spend, a 35% throughput improvement directly translates to a 35% reduction in the number of active GPU instances required to serve a given request volume. Many teams scale their clusters horizontally under the assumption that their GPUs are running at maximum capacity, when in reality, their profiling tools are misinterpreting CPU-bound synchronization pauses as active GPU workloads.
Before committing to long-term cloud reservations or purchasing dedicated hardware, developers should evaluate the execution model of their serving stack:
- Audit your synchronization points: Check if your inference framework performs explicit device synchronizations (such as calling
cudaDeviceSynchronizeor accessing tensor values directly in Python via.item()) during the generation loop. - Use CUDA Graphs: Ensure your decode steps are captured as graphs to eliminate host-side launch latency.
- Evaluate the memory overhead: Pipelining requires doubling your host-side decode slot buffers. While this increases memory footprint slightly, host RAM is orders of magnitude cheaper than GPU high-bandwidth memory (HBM).
There are trade-offs. Pipelined decoding introduces complexity when handling constrained decoding (where the CPU must enforce grammar or schema constraints on the fly) and requires robust handling of "zombie" requests, where a sequence terminates early but the next forward pass has already been queued.
The Software-First Path to Efficiency
The race for faster silicon often obscures the massive inefficiencies in how we orchestrate workloads. As inference engines mature, software-level optimizations like pipelined decoding prove that we can extract significantly more utility from our existing hardware. Popping the CPU-GPU coordination bubble is not just a technical victory, it is an architectural necessity for sustainable AI infrastructure.
Sources & further reading
- Popping the GPU Bubble — moondream.ai
Emeka has spent over a decade tracking threat actors, vulnerability disclosures, and the evolving landscape of application security, bringing a sharp continent-spanning perspective to his reporting. He's known for translating dense CVE advisories into clear, actionable context that developers and security teams alike actually read.
Discussion 2
so how does pipelined decoding handle backfills when the model is generating text one token at a time, does it just buffer the output or is there some clever way to handle corrections mid-stream?
@data_eng_dee, cool question, but have you seen the bill for all that buffering?