Why Your GPU Sits at 30% While Serving Requests

A card at 30% is not a card you need to replace. It is a card waiting.

In short

Generation is memory-bandwidth bound, not compute bound. One request at a time leaves most of the card idle, and batching fixes that without buying hardware.

What that percentage is measuring

Generation is memory-bandwidth bound, not compute bound. One request at a time leaves most of the card idle because the arithmetic units finish long before the weights arrive from memory.

That is why a 30% reading is not a hardware problem. The card has capacity; nothing is asking it to use that capacity. Batching converts headroom into throughput — the same weight matrix serves several requests in one pass instead of being read several times.

Static batching versus continuous batching

Static batching waits for a full batch, runs it, waits again. Throughput improves, latency worsens, and the worst case is a short request stuck behind a long one for the whole batch duration.

Continuous batching admits new requests as slots free up at each decode step. That is the mechanism behind most of the throughput gains people attribute to "a faster inference server" — the scheduler is doing the work, not the kernel.

The cost is that latency becomes a distribution instead of a number. Measure the mean and batching looks free; measure p99 or you will ship a regression nobody reports until users say "sometimes it hangs".

Where the bottleneck usually is

SymptomLikely causeFirst thing to try
GPU 25–35%, latency fineNo batchingEnable continuous batching
GPU high, latency highBatch too large for the cacheCap max batch, shorten context
GPU low, CPU highTokenisation on the hot pathBatch the tokeniser
Spiky utilisationNo admission controlLimit queue depth, shed load

Measure these before changing anything

Common questions

Does batching make every request slower?

Slightly, yes — while total throughput rises a lot. That is the trade. If one user's latency is a hard requirement, cap batch by size rather than by waiting for a full batch.

Will a bigger GPU fix low utilisation?

No — it lowers utilisation further. If the card is idle because nothing is scheduled on it, a bigger card is idle more expensively.

Should I just raise max batch size?

Only together with a KV cache calculation. Batch size multiplies cache usage, and the OOM that follows arrives under load — which makes it look like a stability problem instead of a sizing one.

A measurement pass that takes an hour