How a Transformer Layer Maps to GPU Memory
The key is to think of vLLM GPU memory as a budget shared by several very different kinds of data. Some memory is essentially fixed when the model starts; some grows with request length and concurrency.
For your vLLM workload, the mental model is:
GPU VRAM
│
├── Model weights ← mostly fixed
├── CUDA runtime / kernels ← mostly fixed
├── CUDA graphs ← reserved at startup
├── Activations ← temporary during computation
└── KV cache ← grows with active token contextsAnd most vLLM tuning is really about deciding:
After loading the model, how much VRAM can I leave for KV cache without hurting stability or latency?
vLLM explicitly uses gpu_memory_utilization as part of determining available cache capacity, while kv_cache_memory_bytes can override that automatic sizing. vLLM
1. Start with one Transformer layer
Consider a simplified transformer layer:
Input hidden states
│
▼
RMSNorm
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Q proj K proj V proj
│ │ │
└─────────────┼─────────────┘
▼
Attention
│
▼
O projection
│
+ residual
│
▼
RMSNorm
│
┌─────────────┼──────────────┐
▼ ▼ │
Gate proj Up proj │
└─────────────┬──────────────┘
▼
Activation
│
▼
Down proj
│
+ residual
│
▼
Next layerOn the GPU, the pieces map approximately to:
GPU MEMORY
Weights
─────────────────────────────────────────────
Q weight
K weight
V weight
O weight
Gate weight
Up weight
Down weight
Normalization weights
Activations
─────────────────────────────────────────────
Current hidden states
Q values
attention intermediate results
MLP intermediates
KV Cache
─────────────────────────────────────────────
K token 1
V token 1
K token 2
V token 2
...
K token N
V token NThe important distinction is that Q is not cached.
Only:
K
Vare retained for previous tokens.
2. Model weights: the large fixed allocation
Suppose you have a model containing:
12 billion parametersAt BF16:
2 bytes / parameterThe rough raw-weight requirement is therefore:
12B × 2 bytes
≈ 24 GBAn 8B model would be roughly:
8B × 2
≈ 16 GBbefore accounting for architecture details, tied weights, quantization, metadata, and runtime allocations.
Once loaded:
GPU
│
├─ layer 0 weights
├─ layer 1 weights
├─ layer 2 weights
├─ ...
└─ layer N weightsthese weights stay resident.
During normal inference:
Weights = constantThey're repeatedly read by the GPU but not normally changed.
This is fundamentally different from training, where you'd also need gradients and optimizer states.
3. Why inference needs much less memory than training
Training could require something conceptually like:
Weights
+ Gradients
+ Optimizer states
+ Large activation storageInference mostly needs:
Weights
+ KV cache
+ temporary activations
+ runtime memorySo a rough comparison is:
Training:
┌───────────────────────────────┐
│ weights │
│ gradients │
│ optimizer states │
│ saved activations │
│ temporary buffers │
└───────────────────────────────┘
Inference:
┌───────────────────────────────┐
│ weights │
│ KV cache │
│ small transient activations │
│ CUDA graphs/runtime │
└───────────────────────────────┘For LLM serving, the KV cache often becomes the dominant variable memory component.
4. Activations are temporary working memory
Imagine a hidden dimension of:
4096When tokens enter a transformer layer, intermediate tensors are created:
X
↓
X × Wq → Q
X × Wk → K
X × Wv → VThen:
QKᵀ
softmax(...)
attention output
MLP intermediateThese are activations.
Unlike the KV cache, most do not have to remain around after the layer/iteration is finished.
So:
Weights persistent
KV cache persistent while request is alive
Activations temporaryThis explains why your vLLM startup profile reports something like a:
peak activation memoryrather than allocating that exact amount permanently to every request.
5. Prefill uses much more compute than a single decode step
Take a prompt containing 2,000 tokens.
During prefill:
2000 tokens
│
▼
┌──────────────────┐
│ Transformer │
│ Layer 0 │
│ Layer 1 │
│ ... │
│ Layer N │
└──────────────────┘The GPU processes a large amount of token work.
During decode:
1 new token
│
▼
Transformer
│
▼
1 new tokenbut that new token attends to existing KV cache.
So the workload differs considerably:
PREFILL
──────────────
many input tokens
large matrix operations
high compute utilization
DECODE
──────────────
usually one new token per sequence
reads existing KV cache
smaller matrix operations
often memory-bandwidth sensitiveThis is why vLLM scheduling and batching matter so much.
6. The KV cache
This is the most important vLLM concept to understand.
For every token and every attention layer, the model stores its:
Key
Valuerepresentations.
Suppose a request has:
10,000 tokensthen conceptually:
Request A
Layer 0
K[0...9999]
V[0...9999]
Layer 1
K[0...9999]
V[0...9999]
Layer 2
K[0...9999]
V[0...9999]
...
Layer N
K[0...9999]
V[0...9999]So KV usage is roughly proportional to:
active tokens
× number of layers
× number of KV heads
× head dimension
× K + V
× bytes per elementA simplified formula is:
KV bytes/token ≈
2 × layers × KV_heads × head_dim × bytes_per_elementThe 2 is for:
K + V7. Why GQA greatly reduces KV cache
Suppose:
Attention heads = 32
KV heads = 8This is grouped-query attention, or GQA.
Instead of storing K/V for all 32 attention heads:
32 KV headsyou only store:
8 KV headswhich makes the KV cache approximately:
4× smallerfor that component.
This is one reason modern architectures heavily use:
GQA
MQA
sliding-window attentionThey aren't only architectural choices—they have major serving implications.
vLLM also has specialized KV management for hybrid architectures, including models that combine full and sliding-window attention. vLLM
8. vLLM does not allocate KV cache as one giant contiguous region per request
This is one of vLLM's key innovations.
A naive implementation could look like:
Request A
[ ]
Request B
[ ]
Request C
[ ]This creates allocation and fragmentation problems.
vLLM instead manages cache using blocks/pages.
Conceptually:
KV cache pool
┌───────┐
│Block 1│ ← Request A
├───────┤
│Block 2│ ← Request A
├───────┤
│Block 3│ ← Request B
├───────┤
│Block 4│ ← Request C
├───────┤
│Block 5│ ← Request A
├───────┤
│Block 6│ ← free
├───────┤
│Block 7│ ← Request B
└───────┘The cache manager can associate logical request-token positions with physical blocks.
vLLM's current design describes the cache memory pool as being divided into blocks/pages allocated to layers and requests. vLLM
This is the basis of the idea usually associated with PagedAttention.
9. Now map this onto your GPU
From one of your previous startup logs, your GPU was approximately:
Total GPU memory ~22.03 GiB
Model weights ~15.18 GiB
Peak activation ~0.27 GiB
CUDA Graphs ~0.78 GiB
KV cache ~4.31 GiBThe exact numbers can vary depending on vLLM version, profiling, allocator behavior, and options, but this is a very useful real-world example.
Visually:
22 GiB GPU
┌─────────────────────────────────────────┐
│ │
│ MODEL WEIGHTS │
│ │
│ ~15.18 GiB │
│ │
├─────────────────────────────────────────┤
│ CUDA graphs ~0.78 GiB │
├─────────────────────────────────────────┤
│ activations/runtime │
├─────────────────────────────────────────┤
│ │
│ KV CACHE │
│ ~4.31 GiB │
│ │
└─────────────────────────────────────────┘Notice what dominates:
Weights ≫ everything elseBut after the model is loaded, the most valuable remaining resource is:
KV cachebecause that controls how many active tokens you can serve.
10. Your real KV-cache numbers are very instructive
Your previous startup output reported approximately:
KV cache memory: 4.31 GiB
KV cache token capacity: 208,625 tokensThat implies roughly:
4.31 GiB / 208,625
≈ 21.7 KiB per cached tokenSo, very approximately:
1K tokens ≈ 21 MiB
8K tokens ≈ 173 MiB
32K tokens ≈ 693 MiBA single fully occupied 32,768-token context is therefore around:
~0.68 GiB KV cachefor that specific model/runtime configuration.
This is a very useful operational number.
11. That's where your reported 6.37x concurrency came from
vLLM reported roughly:
KV cache tokens = 208,625
max model len = 32,768Therefore:
208625
───────
32768
≈ 6.37Hence your startup message:
maximum concurrency for
32,768 tokens/request ≈ 6.37xThis does not mean vLLM can only handle six simultaneous HTTP requests.
It means:
If every active request actually consumed the entire 32K context window, the KV cache could hold roughly 6.37 such contexts.
That's an important distinction.
12. Real concurrency can be much higher
Suppose your average active context is only:
4,000 tokensUsing your cache capacity:
208,625 / 4,000
≈ 52You could theoretically hold around:
~52 such contextsfrom a KV-capacity perspective.
At 2K:
208625 / 2000
≈ 104At 8K:
208625 / 8000
≈ 26So a useful approximation is:
| Average active context | KV-based concurrency |
|---|---|
| 2K | ~104 |
| 4K | ~52 |
| 8K | ~26 |
| 16K | ~13 |
| 32K | ~6 |
These are capacity estimates, not promises of good latency.
13. --gpu-memory-utilization
Now this setting becomes easier to understand.
For example:
--gpu-memory-utilization 0.90means vLLM is allowed to target roughly:
90% of GPU memoryfor its model executor.
Current vLLM documentation describes the setting as the fraction of GPU memory usable by that vLLM instance; automatic KV sizing uses that budget after accounting for other required memory. vLLM
Conceptually:
GPU = 24 GB
--gpu-memory-utilization 0.90
Target vLLM budget
≈ 21.6 GBThen:
21.6 GB
- model
- activations
- CUDA graphs
- other runtime allocations
────────────────────────────
= KV cacheIt's therefore useful to think:
gpu-memory-utilization
↓
available vLLM budget
↓
subtract fixed/runtime allocations
↓
remaining KV capacity14. Increasing --gpu-memory-utilization
For example:
0.85 → 0.90 → 0.95usually gives more room to the cache:
More GPU utilization
0.85 0.90 0.95
│ │ │
▼ ▼ ▼
small KV larger KV largest KV
cache cache cachePotential benefit:
↑ active cached tokens
↑ concurrency capacity
↑ throughput potentialPotential cost:
↓ safety margin
↑ chance of OOMSo:
0.70is conservative but wastes memory.
0.99may be aggressive enough that runtime fluctuations push you over the edge.
15. gpu-memory-utilization does NOT mean GPU compute utilization
This is commonly misunderstood.
This:
--gpu-memory-utilization 0.90does not mean:
GPU compute runs at 90%It means approximately:
vLLM memory budget = 90% of VRAMYour actual:
SM utilization
Tensor Core utilization
GPU utilization %
memory bandwidthare separate performance metrics.
16. You can directly control KV cache now
Current vLLM also exposes:
--kv-cache-memory-bytesWhen explicitly supplied, it provides more direct control of the KV-cache allocation and overrides automatic cache sizing based on gpu_memory_utilization. vLLM
Conceptually:
--kv-cache-memory-bytes 4Gmeans:
Give KV cache roughly this explicit budgetrather than saying:
Use 90% total GPU memory and figure it out.For repeatable benchmark experiments, this can be useful.
17. What --max-model-len really controls
Suppose you use:
--max-model-len 32768vLLM defines this as the maximum combined model context:
prompt + outputand if it isn't set, it normally derives the supported context length from the model configuration. vLLM
For example:
Prompt 28,000
Generated 4,000
─────────────────────
Total 32,000
✓ fitsbut:
Prompt 30,000
Generated 5,000
─────────────────────
Total 35,000
✗ exceeds 32,76818. max-model-len does not automatically consume all that KV memory
This is another important distinction.
Setting:
--max-model-len 32768doesn't necessarily mean every request immediately gets a 32K KV buffer.
With paged cache management, memory is consumed according to the blocks actually needed.
So:
Request A: 2K
Request B: 3K
Request C: 1Kdoes not become:
32K + 32K + 32KThe logical maximum is 32K per request, while actual allocation tracks active token use.
That's a major reason vLLM can achieve high concurrency.
19. Why lowering max-model-len can still help
Current vLLM explicitly recommends reducing max_model_len and maximum batch size when memory is tight. vLLM
Suppose your application never needs more than:
8KThen using:
--max-model-len 8192instead of:
32768has several benefits:
prevents runaway long requests
simplifies capacity planning
reduces worst-case memory pressure
can improve scheduling predictabilityIt also means your worst-case KV calculation changes dramatically.
Using your approximate cache capacity:
208,625 / 8192
≈ 25.5instead of:
208,625 / 32768
≈ 6.37So your worst-case capacity goes conceptually from:
6 × 32K requeststo:
25 × 8K requestsusing the same KV pool.
20. Concurrency and memory
Now suppose 10 requests arrive simultaneously.
Each has:
input = 1,500 tokensAfter prefill:
10 × 1,500
= 15,000 KV-cache tokensThen each generates 200 tokens:
10 × 200
= 2,000 additional tokensSo approximately:
17,000 cached tokensare needed.
Compare that with your roughly:
208,625 token capacityThat's only about:
8%of the token cache capacity.
So your previous 10-request benchmark wasn't close to exhausting the KV cache purely from token count.
This helps explain why increasing concurrency can still significantly increase throughput.
21. Why throughput rises with concurrency
One request might look like:
GPU
token
│
▼
matrix multiply
│
▼
GPU partially utilizedWith many requests:
Request A ─┐
Request B ─┤
Request C ─┤
Request D ─┤
Request E ─┤
▼
BATCH
│
▼
GPU
│
▼
larger matrix opsGPUs love larger parallel workloads.
So:
Concurrency ↑
↓
Batching ↑
↓
GPU utilization ↑
↓
aggregate tok/s ↑That is why one-user latency and server throughput are very different optimization targets.
22. But concurrency eventually hurts latency
The curve generally looks conceptually like:
Throughput
^
| ────────── saturation
| ───
| ───
| ──
| ──
| ──
+─────────────────────────────> concurrencywhile latency can look like:
Latency
^
| /
| /
| /
| /
|________________/
+────────────────────────────> concurrencyInitially, concurrency improves hardware utilization.
Eventually:
scheduler queues grow
batches become larger
KV pressure increases
requests wait longer
TTFT risesThe goal isn't:
maximum concurrencyIt's:
maximum throughput
while satisfying your latency SLO23. Why vLLM has --max-num-seqs
Suppose:
--max-num-seqs 32This limits how many sequences the scheduler can process in a single iteration.
Current vLLM defines it as the maximum number of sequences processed in one iteration. vLLM
Think:
100 waiting requests
│
▼
vLLM scheduler
│
├── 32 active
└── others queuedIncreasing it may:
↑ batch size
↑ throughputbut can also:
↑ activation/workspace requirements
↑ CUDA graph memory
↑ scheduling pressure
↑ per-request latency24. --max-num-batched-tokens
This controls another dimension:
number of sequences
vs
number of tokensCurrent vLLM defines:
--max-num-batched-tokensas the maximum number of tokens processed in one scheduler iteration. vLLM
For example:
Request A prefill = 2,000 tokens
Request B prefill = 3,000 tokens
Request C decode = 1 token
Request D decode = 1 tokenThe scheduler needs to decide how much work goes into the next iteration.
A larger token budget tends to favor throughput.
A smaller one can improve interactivity by avoiding huge prefill jobs monopolizing execution.
25. CUDA graphs
Without CUDA graphs, a decode step involves many kernel launches:
CPU
│
├── launch RMSNorm
├── launch Q projection
├── launch K projection
├── launch V projection
├── launch attention
├── launch O projection
├── launch MLP
├── ...
▼
GPUEven though GPU operations are fast, repeatedly launching them has CPU/driver overhead.
CUDA graphs let vLLM capture a reusable sequence:
Capture once:
┌─────────────────────────┐
│ RMSNorm │
│ QKV │
│ Attention │
│ projection │
│ MLP │
│ ... │
└─────────────────────────┘
↓
Replay
Replay
Replay
ReplayThis reduces launch overhead, particularly for decode. Current vLLM documentation explicitly notes CUDA graph capture improves performance by reducing dispatch overhead. vLLM
26. But CUDA graphs consume memory
The trade-off:
CUDA graphs enabled
+ lower kernel launch overhead
+ lower decode latency
+ better throughput
- additional GPU memory
- startup capture/warmupThis is why your startup log showed something like:
CUDAGraph memory ≈ 0.78 GiBThat ~780 MiB is meaningful on a 22 GiB GPU.
It's memory that could otherwise potentially contribute to the KV cache.
vLLM's memory-conservation guide specifically says CUDA graphs consume extra GPU memory and can be reduced or disabled when needed. vLLM
27. You can disable CUDA graphs
For memory troubleshooting:
--enforce-eagerforces eager execution and disables graph capture. vLLM documents this as a memory-saving option. vLLM
Conceptually:
CUDA graphs
ON
────────────────
more memory
lower overhead
OFF / eager
────────────────
less graph memory
higher dispatch overheadI wouldn't normally disable them for your production serving unless you need the VRAM or are troubleshooting an incompatibility.
28. Tensor parallelism changes the equation substantially
Suppose:
--tensor-parallel-size 2vLLM shards model parameters across GPUs within each layer. vLLM
Instead of:
GPU 0
16 GB weightsyou might conceptually get:
GPU 0 GPU 1
~8 GB weights ~8 GB weights
────────────── ──────────────
Layer 0 shard A Layer 0 shard B
Layer 1 shard A Layer 1 shard B
...It's more complicated than exactly 50/50 in real models, but that's the correct mental model.
29. A matrix gets split
Suppose:
Wq = [4096 × 4096]With:
TP = 2conceptually:
Full Wq
┌──────────────────────────────┐
│ │
└──────────────────────────────┘
│
TP = 2
/ \
/ \
GPU 0 GPU 1
┌──────────────┐ ┌──────────────┐
│ Wq shard 0 │ │ Wq shard 1 │
└──────────────┘ └──────────────┘Both GPUs perform their part:
GPU0 matmul
\
── communication ──► result
/
GPU1 matmulvLLM provides TP collective operations such as all-reduce, all-gather and reduce-scatter for this distributed execution. vLLM
30. TP gives you more KV-cache headroom
Imagine a 24 GB GPU.
With TP=1:
GPU 0
15 GB weights
~1 GB runtime
~7 GB available elsewhereNow two GPUs with TP=2:
GPU 0 GPU 1
~7.5 GB weights ~7.5 GB weights
runtime runtime
more KV room more KV roomThis is why vLLM recommends TP not only when a model doesn't fit, but also when reducing per-GPU model pressure can create more cache space for throughput. vLLM
31. But TP isn't free performance
Every transformer layer now needs inter-GPU communication.
Conceptually:
GPU 0
matrix result
│
├──── NVLink / PCIe ────┐
│ │
GPU 1 │
matrix result │
│ │
└───────────────────────┘
│
▼
combined resultTherefore:
TP ↑
↓
weight memory/GPU ↓
compute/GPU ↓
but
communication ↑If the GPUs have:
NVLink / NVSwitchTP is much more attractive.
If communication goes across a slower PCIe topology, the benefit may be smaller.
32. So don't automatically use TP=2 just because you have two GPUs
If a model fits comfortably on one GPU, you could instead use:
2 independent replicasrather than:
TP=2For example:
Option A
TP = 2
GPU0 ──┐
├─ one model instance
GPU1 ──┘Option B
GPU0 → Model instance A
GPU1 → Model instance BOption B can often deliver better total request throughput when the model fits on one GPU, because:
no TP synchronization between GPUswhile Option A may give:
more memory per model
lower single-request latency in some workloads
support for bigger models/contextsSo the right answer depends on whether your bottleneck is:
memory
latency
or throughput33. Putting all four controls together
Here's the tuning relationship I recommend memorizing:
--gpu-memory-utilization
│
▼
Total VRAM budget
│
┌─────────────────┴────────────────┐
│ │
fixed/runtime KV cache
│ │
│ ┌────────┴────────┐
│ │ │
▼ ▼ ▼
model weights context length concurrency
CUDA graphs --max-model-len active tokens
activations
▲
│
│
--tensor-parallel-size
│
▼
reduces weights per GPU
but increases communicationThat is essentially the entire vLLM memory problem.
34. How each parameter affects your benchmark
Here is the practical mapping:
| Parameter | Main effect | TTFT | TPOT/ITL | Throughput | Memory |
|---|---|---|---|---|---|
gpu-memory-utilization ↑ | More KV capacity | ~ | ~ | ↑ under load | ↑ |
max-model-len ↑ | Allows long contexts | can ↑ | can ↑ | may ↓ worst-case | KV pressure ↑ |
| concurrency ↑ | Larger batches | ↑ eventually | ↑ eventually | ↑ until saturation | KV ↑ |
tensor-parallel-size ↑ | Shards model | may ↓/↑ | may ↓/↑ | topology-dependent | weights/GPU ↓ |
max-num-seqs ↑ | More sequences/batch | may ↑ | may ↑ | ↑ until saturation | ↑ |
max-num-batched-tokens ↑ | Larger token batches | may ↑ | mixed | ↑ | activation/work ↑ |
| CUDA graphs | faster dispatch | ↓ | ↓ | ↑ | ↑ |
The ~ means little direct effect unless cache pressure or scheduling changes.
35. Now connect this to your benchmark
You previously had approximately:
Successful requests 10
Concurrent requests 10
Request throughput 0.76 req/s
Output throughput 156.92 tok/s
Total throughput 266.53 tok/s
Mean TTFT 351 ms
Mean TPOT 19.82 ms
Median TPOT 15.31 msThose numbers suggest a fairly healthy serving regime: batching was providing useful aggregate throughput, while TTFT remained well below one second under 10 concurrent requests.
The important next experiment isn't simply:
Can I make 156 tok/s become 170?It's finding the saturation curve.
36. The benchmark I would run next
Use the same:
input length
output length
model
sampling parametersand vary only concurrency:
Concurrency
1
2
4
8
16
32
64Then record:
TTFT p50
TTFT p99
TPOT p50
TPOT p99
output tok/s
total tok/s
requests/sYou'll probably see something like:
Concurrency Throughput TTFT
─────────── ────────── ────
1 low low
2 ↑ ~
4 ↑ ~
8 ↑ ↑
16 ↑ ↑
32 plateau ↑↑
64 plateau ↑↑↑The sweet spot is just before the latency curve bends sharply upward.
37. For your ~22 GiB GPU, I would start here
Based on the memory profile you've shown, I'd initially run:
vllm serve <model> \
--max-model-len 32768 \
--gpu-memory-utilization 0.90and avoid changing a dozen knobs simultaneously.
Then benchmark:
C = 1
C = 4
C = 8
C = 16
C = 32If production doesn't need 32K context, I'd strongly consider:
--max-model-len 16384or even:
--max-model-len 8192because controlling worst-case context length is one of the easiest ways to make inference capacity predictable.
Current vLLM specifically identifies context length and maximum sequence count as memory-conservation levers. vLLM
38. A useful production memory target
For production, I generally wouldn't try to make the picture:
GPU
██████████████████████████ 99.9%I'd rather aim for something conceptually like:
GPU
Weights █████████████████
KV Cache █████
CUDA/runtime ██
Safety margin ░░especially for multimodal models, compilation, unusual batches, or changing traffic patterns.
So your:
--gpu-memory-utilization 0.90is a sensible starting point.
Then validate under sustained load before considering:
0.92
0.94
0.95Current vLLM defaults can vary by interface/version, so I would set this explicitly rather than depend on the default. vLLM
39. The whole system in one diagram
You can now connect the model files from the previous explanation all the way to runtime:
HUGGING FACE MODEL
│
├── config.json
├── tokenizer
└── safetensors
│
▼
══════════════════════════════════════════════════
vLLM STARTUP
══════════════════════════════════════════════════
│
├─ Read architecture
│
├─ Build transformer
│
├─ Load tensors
│
▼
┌──────────────────────── GPU ────────────────────┐
│ │
│ MODEL WEIGHTS │
│ ┌────────────────────────────────────────────┐ │
│ │ Layer 0 Q/K/V/O + MLP │ │
│ │ Layer 1 Q/K/V/O + MLP │ │
│ │ ... │ │
│ │ Layer N │ │
│ └────────────────────────────────────────────┘ │
│ │
│ CUDA GRAPHS / COMPILED KERNELS │
│ ┌────────────────────────────────────────────┐ │
│ │ captured execution paths │ │
│ └────────────────────────────────────────────┘ │
│ │
│ ACTIVATION / WORKSPACE │
│ ┌────────────────────────────────────────────┐ │
│ │ temporary computation │ │
│ └────────────────────────────────────────────┘ │
│ │
│ KV CACHE │
│ ┌────┬────┬────┬────┬────┬────┬────┐ │
│ │ A1 │ A2 │ B1 │ C1 │ A3 │ B2 │free│ ... │
│ └────┴────┴────┴────┴────┴────┴────┘ │
│ │
└────────────────────────────────────────────────┘
▲
│
══════════════════════════════════════════════════
REQUEST EXECUTION
══════════════════════════════════════════════════
│
User request
│
▼
Tokenizer
│
▼
PREFILL
│
├──────────────► write K/V
│ │
▼ ▼
First token KV CACHE
│ ▲
▼ │
DECODE ──────────────────────┘
│
▼
next token
│
▼
DECODE
│
▼
...And the vLLM scheduler sits above all of that:
50 incoming requests
│
▼
┌────────────────┐
│ vLLM Scheduler │
└───────┬────────┘
│
batch/chunk work
│
▼
GPUThe scheduler, KV-cache manager and GPU workers are core pieces of the current vLLM architecture. vLLM
The four rules I'd keep in mind
1. Weights determine whether the model fits.
Model size → base VRAM requirement2. KV cache determines serving capacity.
context length × concurrent active tokens → KV memory3. Batching determines throughput.
more useful concurrent GPU work → higher tok/suntil saturation.
4. Tensor parallelism trades communication for memory.
TP ↑
→ model memory/GPU ↓
→ potential KV capacity ↑
→ GPU-to-GPU communication ↑That leads directly to the practical optimization objective for your vLLM deployment:
Fit the weights comfortably, maximize useful KV cache, batch enough requests to keep the GPU busy, and stop increasing concurrency when TTFT/TPOT begin violating your latency target.
For the benchmark numbers you've already collected, concurrency sweep + GPU/KV utilization is the next experiment I'd run before adjusting lower-level kernel or compilation settings.