1. 调研方法 & 目录
本报告基于 Playwright + Python urllib 直接从 raw.githubusercontent.com 拉取 vLLM main 分支的 design docs、用户文档及核心源码,聚焦 disaggregated prefill/decode(disagg_prefill)设计中 D 实例的 KV Cache 内存容量。
2.1 整体流程 (Scheduler → KVCacheManager → BlockPool → GPU tensor)
vLLM v1 把 KV Cache 管理抽象成三层(详见 hybrid_kv_cache_manager.md):
┌──────────────────────────────────────────────────────────────┐
│ Scheduler (engine core) │
│ └─ on_new_request / allocate_slots / free (per-request) │
└────────────────────┬─────────────────────────────────────────┘
│ KVCacheBlocks (per-request)
▼
┌──────────────────────────────────────────────────────────────┐
│ KVCacheManager (vllm/v1/core/kv_cache_manager.py) │
│ └─ get_computed_blocks (prefix cache hit 第一次扫描) │
│ └─ allocate_slots (调度一个 step 的分配) │
│ └─ free (请求完成释放, 反向 order LRU) │
│ └─ usage() (0.0~1.0 占用率, 用于 watermark) │
└────────────────────┬─────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ BlockPool (vllm/v1/core/block_pool.py) │
│ blocks: list[KVCacheBlock] # 预分配好的所有 block │
│ free_block_queue: FreeKVCacheBlockQueue (doubly-linked) │
│ cached_block_hash_to_block: BlockHashToBlockMap │
│ └─ allocate() / free() / cache_full_blocks() / evict_blocks│
└────────────────────┬─────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ KVCacheTensor (per-buffer, 跨 KV cache group 共享) │
│ block_size × kv_hidden_size 的 slice 拼成 cuda 连续 tensor │
└──────────────────────────────────────────────────────────────┘
2.2 谁是"D 端"的决定者
- Scheduler (engine core process):在 D 实例上,
NixlConnectorScheduler 在每次 scheduler step 里调用 build_connector_meta:
- push 模式 D 端 →
meta.push_registrations(把自己的 logical block_ids 给 P)
- pull 模式 D 端 → 等待 P 的 KV READ, 通过
_pending_completion_notifs 完成通知
- Worker (GPU, 每 TP rank 一个):执行实际的 KV Transfer 以及 KV Cache 的写入/读取。
nixl-push-writer 是新增的专用后台线程(nixl_kv_push_connector.md §Threads)。
- KV Cache 调度器视角:D 端容量完全取决于
KVCacheManager 初始化时做出的 num_gpu_blocks 决策(基于 determine_available_memory() profiling 结果)。P→D 的 KV transfer 落到同一块 block里——push 时 P 直接 WRITE 到 D 预分配好的 block("logical block ids (preallocated)",参见 push_connector.md)。
2.3 PD 分离后 D 端的 KV Cache 来源(三种路径)
| 路径 | 机制 | D 端 block 状态 |
| pull 模式 (默认) |
D 发起 NIXL READ, P 端被读 |
D 端 先分配,再由 RDMA 填充 |
| push 模式 |
P 端 NIXL WRITE 到 D 预分配的 block |
D 端 先分配, 再被写入 (_handle_push_reg_notif) |
| prefix cache hit |
D 端 BlockPool 中已缓存相同 token(s) 的 block |
直接复用, ref_cnt++, 不占 KV transfer 带宽 |
关键洞察:无论 pull/push, D 端的 block 都是预分配的——这意味着 D 端必须在收到 KV 之前就承诺内存。当 PD 规模较大时, 这种"预分配 + lease"机制会在 D 端形成大量的内存承诺。Lease renewal(参见 §8)正是为了缓解这一点。
3. block_size / gpu_memory_utilization / kv_cache_memory_bytes 默认值及影响 (vllm/config/cache.py 实测源码)
3.1 关键参数默认值
| 参数 | 默认值 | 所在配置类 / CLI |
block_size | 16 tokens | CacheConfig.DEFAULT_BLOCK_SIZE |
gpu_memory_utilization | 0.92 (92%) | CacheConfig.gpu_memory_utilization |
cache_dtype (kv_cache_dtype) | "auto" (= model dtype) | CacheConfig.cache_dtype |
enable_prefix_caching | True | CacheConfig.enable_prefix_caching |
kv_cache_memory_bytes | None (自动推导) | CacheConfig.kv_cache_memory_bytes |
kv_offloading_size | None (CPU offload 关闭) | CacheConfig.kv_offloading_size |
num_gpu_blocks_override | None (仅测试用) | CacheConfig.num_gpu_blocks_override |
mamba_block_size | None | Hybrid Mamba-attention 模型专用 |
3.2 源码级证据(CacheConfig)
# vllm/config/cache.py (GitHub main, 2026-07-15)
class CacheConfig:
DEFAULT_BLOCK_SIZE: ClassVar[int] = 16
block_size: int = Field(default=None, gt=0)
# After construction, always int (resolved to DEFAULT_BLOCK_SIZE)
gpu_memory_utilization: float = Field(default=0.92, gt=0, le=1)
"""The fraction of GPU memory to be used
for the model executor, which can range from 0 to 1."""
cache_dtype: CacheDType = "auto"
"""Data type for kv cache storage. If 'auto',
will use model data type."""
kv_cache_memory_bytes: int | None = None
"""... ignores gpu_memory_utilization when not-None"""
num_gpu_blocks: int | None # set after profiling
num_cpu_blocks: int | None # set after profiling
3.3 各参数对 D 端容量的实际影响
| 参数 | 变化方向 | 对 D 端 KV Cache 容量的影响 | 副作用 |
block_size ↑ | 16 → 64 / 128 |
每个 block 占更多显存, 总 block 数 ↓(总 KV 显存 ≈ page_size × num_blocks) |
① prefix cache hash 粒度变粗, hit-rate 降低 ② 一个请求最小分配单位变大, 内部碎片变大 |
gpu_memory_utilization ↑ | 0.92 → 0.97 |
D 端可用显存 ↑ ⇒ num_gpu_blocks ↑(翻倍效应) |
留给 activation 的空间更小, decode 长 batch 更易 OOM |
gpu_memory_utilization ↓ | 0.92 → 0.70 |
D 端可用显存 ↓ ⇒ num_gpu_blocks ↓ |
更安全, 但同时 D 实例能承载的并发 request 数 ↓(甚至 PD 中 lease 被频繁 evict) |
kv_cache_memory_bytes 手动指定 | >0 |
完全旁路 gpu_memory_utilization |
精细控制, 但需要自己估算(PD 推荐用法) |
cache_dtype 调低 | fp16 → fp8_e4m3 |
KV Cache 容量翻倍 |
精度损失(需要 per-token-head scale) |
prefix caching | True (默认) |
共享 prefix 的 block 被不同 request 共用, 等效提升 D 端 KV Cache 利用效率 |
PD 中 P/D 端必须 hash 一致 (PYTHONHASHSEED 固定) |
vLLM 官方建议 (conserving_memory.md):如果 D 端 OOM, 优先尝试 (1) 增加 TP, (2) 降低
--gpu-memory-utilization, (3) 启用量化, (4) 限制
max_model_len /
max_num_seqs。
4.1 核心调度逻辑(KVCacheManager.allocate_slots)
# vllm/v1/core/kv_cache_manager.py
watermark_blocks = int(watermark * kv_cache_config.num_blocks) # 默认 watermark ≈ 0.01
required_blocks = num_blocks_to_allocate + watermark_blocks # 只有 waiting/preempted 请求才加
if self.block_pool.get_num_free_blocks() < required_blocks:
# 水位线未通过, 拒绝新的 waiting/preempted 请求 admission
return None, None
# ... 正常 prefix cache hit 扫描 + 分配新 block
4.2 请求生命周期内的 KV Cache 操作
| 阶段 | 操作 | 代码位置 | D 端 PD 场景的特殊性 |
| 请求进入 |
get_computed_blocks() 扫描 prefix cache |
KVCacheManager L207-260 |
若 P/D 共享 prefix 且 hash 一致, 可命中 |
| 调度分配 |
allocate_slots() 为新 token 分配 block |
KVCacheManager L380-470 |
新到的 PD request 需要同时承接 P→D 传来的 KV |
| PD-PUSH 完成 |
_handle_push_reg_notif + send_notif |
nixl_kv_push_connector.md |
D 端注册的 block 被 P 端 WRITE 填充, 触发 completion |
| 请求完成 |
free() 反向顺序释放 block → 加到 free queue 尾部 |
KVCacheManager L505-560, block_pool.py |
尾部的 block hash 更长、复用概率低, 优先被 evict |
| free queue 不够 |
FreeKVCacheBlockQueue.popleft() evict LRU block |
block_pool.py |
从 free queue 的 head 取 block; 若 block 已 cached(BlockHashToBlockMap), evict 掉其 hash 条目 |
4.3 与 CPU swap / OffloadingConnector 的协同
vLLM 提供 OffloadingConnector(详见 §5 kv_offloading_usage.md):当 D 端 HBM 不足时, 可以把 KV block 以 DMA(cudaMemcpyAsync) offload 到 pinned CPU memory。配置入口:
--kv-transfer-config '{
"kv_connector": "OffloadingConnector",
"kv_role": "kv_both",
"kv_connector_extra_config": {
"block_size": 64, # 必须为 GPU block_size 倍数
"cpu_bytes_to_use": 1000000000, # ~1 GiB CPU pinned mem
"eviction_policy": "lru" # 或 "arc"
}
}'
PD 场景关键配置开关 (kv_offloading_usage.md):
offload_prompt_only: true (默认):只 offload prefill blocks, decode blocks 跳过(对 PD 中的 D 端非常友好)
store_threshold:block 被 lookup N 次后才 offload(减少冷 block 的拷贝开销)
max_tracker_size:默认 64000 条
4.4 Swap / Preemption 策略
vLLM v1 不支持把 KV swap 到 CPU 的 preemption/restore—— Scheduler 的 preemption 策略是 RECOMPUTE(见 v1 scheduler):一个请求如果分配不到 block, 直接丢弃已有 KV, 恢复到 waiting 队列, 等待 free block 水位线恢复后重新 allocate_slots。watermark_blocks(watermark 乘 num_blocks, 默认 0.01)是避免"频繁 preempt"的唯一护栏。
这意味着:D 端高并发时, PD 请求等待队列中的 lease 往往因为 preemption 而被作废——P 端已经传过来的 KV 也将被重新传输。
5.1 支持的 KV Cache dtype 全量
来自 vllm/config/cache.py CacheDType 类型别名:
"auto"
"float16"
"bfloat16"
"fp8"
"fp8_e4m3"
"fp8_e5m2"
"fp8_inc"
"fp8_ds_mla"
"turboquant_k8v4"
"turboquant_4bit_nc"
"turboquant_k3v4_nc"
"turboquant_3bit_nc"
"int4_per_token_head"
"int8_per_token_head"
"fp8_per_token_head"
"nvfp4"
5.2 各 dtype 存储容量折算(相对 fp16 baseline 的比值)
| dtype | bits/KV token | 相对 fp16 容量 | 硬件要求 | 备注 |
| float16 / bfloat16 | 16 | 1.0× | 通用 | baseline |
| fp8 (fp8_e4m3 / fp8_e5m2) | 8 | 2.0× | SM80+, ROCm | DeepSeek V3.2 默认 fp8 |
| fp8_inc (Intel Gaudi) | 8 | 2.0× | HPU | Intel 定制 |
| fp8_ds_mla | 8 | 2.0× | SM90 | DeepSeek MLA |
| turboquant_k8v4 | 6 | 2.67× | SM90+ | K 8-bit / V 4-bit |
| nvfp4 | 4 | 4.0× | SM100 (Blackwell) | 需要 per-head scale |
| int4_per_token_head | 4 | 4.0× | - | per-token-head 动态 scale |
| turboquant_3bit_nc | 3 | 5.3× | - | 极限压缩, 精度损失大 |
5.3 对 D 端工程取舍
- PD 分离下 D 端 KV 复制倍数大——D 端要存储每个 request 所有 prefix + decode 的 KV, 选择 fp8 / nvfp4 可让 D 端 HBM 中 KV Cache 容量翻倍乃至 4×
- 精度降级需要配套 per-token / per-head scale——
fp8_per_token_head、int4_per_token_head 在运行时动态计算 scale, 牺牲一点算力换容量
- DeepSeek V3/R1 系列默认 KV Cache 已是 fp8(
cache_dtype=auto → fp8)—— 这已经是 PD 部署的"甜区"配置
- DeepSeek V3.2 特殊条款:官方建议把
kv_cache_dtype 从 fp8 设为 bfloat16 以换取精度(参见 cache.py 注释)
6. D 端 Memory Profiling:权重 + KV Cache + 激活 + 碎片 各占多少 (gpu_worker.py · mem_utils.py)
6.1 核心函数 GPUWorker.determine_available_memory()
# vllm/v1/worker/gpu_worker.py (GitHub main, 2026-07-15, 简化)
@torch.inference_mode()
def determine_available_memory(self) -> int:
# (1) 手动指定 kv_cache_memory_bytes 时, 直接旁路 profiling
if kv_cache_memory_bytes := self.cache_config.kv_cache_memory_bytes:
self.model_runner.profile_run()
return self._reserve_mm_ipc_gpu_memory(kv_cache_memory_bytes)
# (2) 否则执行 profile pass
with memory_profiling(self.init_snapshot,
weights_memory=int(self.model_runner.model_memory_usage)
) as profile_result:
self.model_runner.profile_run() # 一次 dummy forward
profile_torch_peak = torch.accelerator.memory_stats(...)
.get("allocated_bytes.all.peak", 0)
# (3) cudagraph 占用的额外显存估计
cudagraph_memory_estimate = 0
if compilation_config.cudagraph_mode != NONE:
cudagraph_memory_estimate = self.model_runner.profile_cudagraph_memory()
profile_result.torch_peak_increase = \
profile_torch_peak - profile_result.before_profile.torch_peak
profile_result.non_kv_cache_memory = (
profile_result.non_torch_increase # 非 torch 显存增量 (NCCL buffer, attn backend)
+ profile_result.torch_peak_increase # activation 峰值
+ profile_result.weights_memory # 模型权重
)
self.available_kv_cache_memory_bytes = (
self.requested_memory # = total_gpu_mem * 0.92
- profile_result.non_kv_cache_memory
- cudagraph_memory_estimate # (VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS=1 时计入)
)
return self._reserve_mm_ipc_gpu_memory(self.available_kv_cache_memory_bytes)
6.2 vLLM 官方分类公式(memory_profiling docstring,mem_utils.py L242-276)
The memory in one GPU can be classified into 3 categories:
1. memory used by anything other than the current vLLM instance
2. memory used by torch in the current vLLM instance
3. memory used in the current vLLM instance, but not by torch
Quantitive example:
Before creating the current vLLM instance:
category 1: 1 GiB, category 2: 0 GiB, category 3: 0 GiB
After creating the current vLLM instance and loading the model:
category 1: 1 GiB, category 2: 2 GiB (model weights take 2 GiB)
, category 3: 0.5 GiB (memory used by NCCL)
During profiling (peak):
category 1: 1 GiB, category 2: 4 GiB (peak activation tensors take 2 GiB)
, category 3: 1 GiB (NCCL + attn backend buffers)
After profiling:
category 1: 1 GiB, category 2: 3 GiB (after GC of activation tensors)
, category 3: 1 GiB
=> non-kv cache takes 5 GiB in total, including:
a. 2 GiB model weights (category 2)
b. 2 GiB reserved for peak activation tensors (category 2)
c. 1 GiB used by non-torch components (category 3)
6.3 命名与公式速查
| 组成分量 | 代码字段 | 含义 | 配置影响 |
| 模型权重 |
weights_memory = self.model_runner.model_memory_usage |
PyTorch 分配器中 model params 的占用量(m.consumed_memory) |
TP↑↓↓, 量化↓ |
| 激活峰值 |
torch_peak_increase = peak_alloc - before_profile.torch_peak |
profile pass 中 activation 张量的最大瞬时增量 |
cudagraph_capture_sizes, max_num_batched_tokens |
| 非 torch 增量 |
non_torch_increase (category 3) |
NCCL buffer、attention backend 的 persistent buffer(不在 torch allocator 内) |
TP, attention backend 选择 |
| CUDA Graph |
cudagraph_memory_estimate |
cudagraph capture 占用的显存(VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS v0.21 起默认开启) |
每一位 capture size 约几 MB |
| KV Cache 可用 |
available_kv_cache_memory_bytes |
= requested_memory − non_kv_cache_memory − cudagraph_memory_estimate |
最终决定 D 端 num_gpu_blocks |
| 模型 | GPU (80/96 GiB) | 权重 | 激活+非 torch | CUDA Graph | KV Cache (默认 0.92) |
| Llama-3.1-70B (TP4) | 4 × H100 80G | ~17 GiB (每卡) | ~3 GiB | ~3 GiB | ~51 GiB (≈64%) |
| Llama-3.1-8B | 1 × H100 80G | ~15 GiB | ~2 GiB | ~2 GiB | ~58 GiB |
| DeepSeek-R1 (TP8) | 8 × H100 80G | ~8 GiB (fp8 weight) | ~4 GiB | ~4 GiB | ~60 GiB (fp8 KV) |
| Qwen2.5-VL-72B (TP8) | 8 × H20 96G | ~22 GiB (每卡) | ~5 GiB | ~4 GiB | ~60 GiB |
注:上表基于社区讨论 + 加权估算, 真实数字因 batch shape、attention backend、TP 数量差异显著。实际运行请参考 vLLM 启动时打印的 "Available KV cache memory: X GiB" 日志。
6.5 关键环境变量(显存 profiling 细粒度控制)
# 手动指定 KV cache 字节, 旁路 profiling(PD 场景最稳)
--kv-cache-memory-bytes $((50 * 1024**3))
# 关闭 cudagraph memory 估计 (v0.21+)
export VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS=0
# 多模态 IPC 显存保留 (multimodal PD 才需要)
# vllm/v1/worker/gpu_worker.py 中 _reserve_mm_ipc_gpu_memory() 自动维护
7. 长 context (1M) 下 vLLM 的 HBM 挑战 (kv_cache_utils.py)
1M context 意味着每个 request 的 KV Cache 需求急剧放大。以 DeepSeek-V3 (MLA, 57B A23, kv_lora_rank=512, qk_rope_head_dim=64, num_heads=128) 为例:
7.1 单 token KV 显存
| 模型架构 | kv_lora_rank | qk_rope_head_dim | v_head_dim | 单 token KV bytes (fp16) |
| Llama-3.1-70B (MHA) | 4096 (num_kv_heads×head_dim) | - | 128 | ~16,384 |
| Qwen2.5-72B | 2,048 | - | 128 | ~4,096 |
| DeepSeek-V3 (MLA) | 512 | 64 | 128 | ~1,408 |
| Llama-4 (GQA hybrid) | 1,024 | 64 | 128 | ~2,048 |
7.2 1M token KV Cache 需求(单个 request)
KV_per_request ≈ 1,000,000 × (kv_lora_rank + qk_rope_head_dim + v_head_dim) × 2 (K+V)
Llama-3.1-70B (fp16): 1M × 16,384 bytes = 15.3 GiB ← 单卡放不下一个 1M 的 request
Qwen2.5-72B (fp16): 1M × 4,096 bytes = 3.8 GiB
DeepSeek-V3 (fp8): 1M × 704 bytes = 0.66 GiB ← MLA + fp8 占优
DeepSeek-V3 (fp16): 1M × 1,408 bytes = 1.3 GiB
7.3 PD 分离对 D 端 1M context 的影响
- D 端 KV Cache = 整个 1M 的 prefix KV——需要 62,500 个 block(block_size=16), 远超一般 D 实例
num_gpu_blocks(通常 5k~30k)
- Lease renewal 压力——5 分钟以上的 decode 要不断给 P 端 heartbeat(§8)
- OffloadingConnector 成刚需——CPU-only Tier 容量远超 HBM, 可以把 90%+ 的 block offload
- prefix caching 是另一个刚需——1M 共享 system prompt, 不同 conversation turn 的 prefix 大概率 cache hit, 大幅减少 D 端 KV 重建
- CPU bytes 预留建议——
cpu_bytes_to_use 应比 D 端 GPU KV 容量大, 否则"镜像 CPU tier"没有意义
7.4 缓解路径
| 方法 | 收益 | 代价 |
| MLA (DeepSeek) | KV 10× 压缩 vs GQA/MHA | 模型架构绑定 |
| nvfp4 KV | 4× 容量 vs fp16 | SM100+, 精度下降 |
| fp8 KV (DeepSeek V3) | 2× 容量 | 几乎免费 |
| CPU offload (PD 用 OffloadingConnector) | CPU 512GB 级别, 廉价 | 延迟, 主机带宽瓶颈 |
prefix cache (per-request max_offload_tokens) | prefix 不重复计算 | 一致性依赖 hash |
kv_cache_memory_bytes 指定 | 避开 profiling 误差锁定 KV 容量 | 需要 sizing 经验 |
8.1 关键参数与默认值
| 参数 | 默认值 | 含义 |
kv_lease_duration | 30 s | P 端初始 lease(prefill 完成后锁住 KV 的时长) |
decoder_kv_blocks_ttl | 480 s | 双向 transfer 下 D 端 block TTL |
| heartbeat interval | lease_duration // 6 ≈ 5 s | D 给 P 发 HB 的频率 |
| lease extension | lease_duration * 2 // 3 ≈ 20 s | 每次 HB 续多长 |
_PUSH_WRITER_POLL_INTERVAL_MS | 1.0 ms | push writer 自轮询间隔 |
push_registration_timeout | = decoder_kv_blocks_ttl (480 s) | D 端注册超时被 watchdog 取消 |
- 注册阶段:D scheduler 在
update_state_after_alloc 后, 把 local_block_ids(D 端预分配的 logical block ids) 打包成 PUSH_REG:<msgpack> 通知, 经 nixl-push-writer 线程发给 P
- 写入完成:P 端 WRITE 完所有 block, 发 completion notif → D
_pending_completion_notifs → finished_recving → scheduler 清除 watchdog
- Watchdog 兜底:超过
push_registration_timeout 未完成, 取消注册, 由 engine 的 abort path 兜底 fail request
vLLM PD 中 D 端 heartbeat 必须在 request 入 scheduler 时就启动, 而不是被调度后才启动。这意味着:
- 等待队列中每个未调度的 request 都持续给 P 端续 lease
- D 端给 P 端续 lease 的代价:NIXL send_notif 一次 / 5 秒 per engine
- 但如果 D 端 scheduler 自身被 blocking 或排队太长, D 端等待队列中积累的 request 可能在 D 端还没被 schedule 就占着 D 端的 KV block——这就是 PD 下 D 端 KV Cache 的第一个"隐形杀手"
8.4 Lease 设计总结
P 端 lease 生命周期:
prefill 完成 ---(30s)--> HB1 (D~5s 一次) ---(+20s)--> HB2 ... --> KV transfer 完成/超时清空
三种回收路径(lease.md §Lease lifecycle):
(1) D 完成 KV transfer → P 立即 free
(2) D 持续 HB → lease 永远续
(3) D crash / 超时 → lease 失效, P 在 ~20s 内 free
参考 PR:lease 机制由 PR #41383 引入
9. 引用 & 调研过程附件
9.1 主要引用来源
| # | 文档 | 链接 | 说明 |
| 1 | nixl_kv_push_connector.md | GitHub raw | push mode 线程/队列/调度交互设计 |
| 2 | nixl_kv_cache_lease.md | GitHub raw | lease renewal / heartbeat / watchdog |
| 3 | hybrid_kv_cache_manager.md | GitHub raw | KVCacheManager 3 层抽象 + 5 种 allocation case |
| 4 | paged_attention.md | GitHub raw | 历史 paper 实现(已注明"不再描述现行代码") |
| 5 | prefix_caching.md | GitHub raw | hash-based prefix cache / LRU / sha256 default |
| 6 | arch_overview.md | GitHub raw | V1 多进程架构 |
| 7 | disagg_prefill.md | GitHub raw | PD 总览 + 9 种 connector |
| 8 | conserving_memory.md | HTML · raw | 官方显存指南 |
| 9 | kv_offloading_usage.md | raw | OffloadingConnector / CPU+FS+P2P tier |
| 10 | CacheConfig 源码 | GitHub | block_size=16 / gpu_memory_util=0.92 / cache_dtype 默认 / CacheDType 枚举 |
| 11 | GPUWorker.determine_available_memory | GitHub L440-620 | KV Cache 容量 profiling 入口 |
| 12 | KVCacheManager | GitHub | watermark / allocate_slots / free / prefix |
| 13 | BlockPool | GitHub | LRU free_block_queue + BlockHashToBlockMap |
| 14 | memory_profiling() | GitHub | 3-category 显存分类示例 |
| 15 | disagg_prefill (HTML SPA) | docs.vllm.ai | 返回 GitHub issue MoRIIO(重定向) |
| 16 | disaggregated_serving (HTML SPA) | docs.vllm.ai | 同 15,返回 GitHub issue |
| 17 | paged_attention (HTML SPA) | docs.vllm.ai | 未单独访问 |
9.2 下载到本地的文件(vllm_pd_decode_memory/)
- design docs: nixl_kv_push_connector.md, nixl_kv_cache_lease.md, hybrid_kv_cache_manager.md, paged_attention.md, prefix_caching.md, arch_overview.md
- feature docs: conserving_memory.md, disagg_prefill.md, kv_offloading_usage.md
- 源码: vllm_config_cache.py (CacheConfig 完整定义), vllm_v1_worker_gpu_worker.py + gpu_model_runner.py, vllm_v1_core_kv_cache_manager.py + block_pool.py + kv_cache_utils.py, vllm_utils_mem_utils.py
9.3 关键发现总结
- D 端 KV Cache 容量 =
(total_gpu_memory × gpu_memory_utilization − non_kv_cache_memory − cudagraph_memory_estimate) ÷ page_size, 其中 page_size = block_size(默认 16) × num_layers × kv_hidden_size × num_kv_cache_groups(取 lcm)。
- 所有关键默认值透明可查:
block_size=16 / gpu_memory_utilization=0.92 / cache_dtype="auto" / enable_prefix_caching=True(均来自 vllm/config/cache.py)。
- PD 后 D 端 KV Cache 的"承诺"模式:pull/push 都要求 D 端先分配 block, 再由 P 端 RDMA 写入。这是 D 端高并发时 HBM 紧张的第一原因。Lease renewal(30s + 20s/5s)是避免 P 端 KV 被长时间无效占用(D crash 等)的关键机制。
- prefix caching + CPU offloading 是 PD D 端的双保险:
OffloadingConnector(默认 offload_prompt_only=true)把已满 block 异步搬到 pinned CPU memory(DMA cudaMemcpyAsync)。
- kv_cache_dtype 非常丰富的量化选型:支持 fp8 / fp8_ds_mla / nvfp4 / turboquant系列 / per-token-head 动态 scale, 让 D 端 KV 容量最多放大 5×。DeepSeek V3/R1 默认已是 fp8 KV。
- 1M context 场景:1M token × 稀疏化 KV(MLA+fp8) ≈ 0.66 GiB / request, 但 MHA/GQA 模型高达 15 GiB / request——D 端 HBM 必须走 CPU offload + MLA + nvfp4 组合路径才有经济可行性。
- vLLM v1 不支持 swap/restore preemption, 只有 RECOMPUTE, 所以
watermark_blocks 是避免频繁 preempt 的唯一护栏。PD 长等待队列会加剧这一路径的触发。