GPU 性能没问题,模型也训练得不错,但 token 吞吐量就是上不去?问题多半出在 KV-cache 上。本文整理了 10 个实际可用的优化方向,都是能直接上生产环境的那种。
1、给 cache 足够的内存空间
vLLM 启动时会预分配一大块 VRAM 给 KV-cache 用。如果分配得太保守,批处理规模会急剧下降,吞吐量也跟着崩。两个关键参数:
--gpu-memory-utilization
控制预分配的激进程度
--max-num-seqs
限制并发序列数,避免内存碎片和频繁抢占
把 utilization 往上调,直到不再频繁出现 preemption;然后再调
max-num-seqs
,让批次保持密集但别超出承载能力。vLLM 官方优化文档里专门提到过这点。
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3-8b-instruct \
--gpu-memory-utilization 0.92 \
--max-num-seqs 256
2、FP8 量化 KV-cache(需要硬件得支持)
注意这里量化的是 KV-cache 本身,不是模型权重。降到 FP8 之后,同样的显存能塞下更大的上下文,批次规模也能跟着扩大,吞吐自然就上去了。
--kv-cache-dtype fp8
(或者
fp8_e4m3
、
fp8_e5m2
)
但这里有几个坑:FP8 支持强依赖硬件和后端实现。Hopper/Ada 架构(H100、4090)和 AMD MI300 系列可以跑,Ampere(比如 3090)就不行。而且部分 attention 后端压根不支持 FP8 模式,开了反而会更慢。
有人做过详细对比测试,FP8 确实能扩大批次,但在 vLLM 上的加速效果取决于后端支持情况;TensorRT-LLM 表现更稳定一些。所以需要针对自己的配置实测。
3、 分块预填充让解码和 prefill 并行
长 prompt 在 prefill 阶段会把 GPU 算力全占了,decode 只能干等着。Chunked prefill 把大段的 prefill 切成小块,这样就可以和 decode token 交错执行,GPU 利用率也可以保持在高位。并且在混合长度请求的场景下,端到端吞吐提升相当明显,vLLM 文档和社区反馈都验证过这个效果。
新版本默认开启了 chunked prefill,但还是要留意调度策略,别让 decode 被饿死。
4、前缀缓存的命中条件比较苛刻
vLLM 的 prompt 缓存基于 PagedAttention,粒度是 block 级别的。哪怕一个 block 里只有一个 token 不一样,整个 block 就没法复用。所以想提高命中率得把 prompt 做标准化处理,让前面的 token 尽可能对齐到 block 边界——比如固定的 system prompt、统一的前导模板之类的。社区里对这个 block-level 约束讨论得挺透彻。
5、 滑动窗口注意力配合混合管理器
SW-attention(也叫 local attention)只保留最近窗口内的 KV,长序列下的缓存增长能控制得很死。新版 vLLM 的 混合 KV-cache 管理器 能协调 SW 层和全注意力层,让缓存命中逻辑在不同层之间保持一致。结果就是 KV 工作集更小、访问更热,长对话场景下的持续吞吐会稳定很多。
6、ROPE 缩放要算清楚成本
ROPE scaling(线性或动态缩放)能把上下文窗口拉长,但每个被 attend 的 token 还是要占 KV-cache 空间的。这招虽然适合用在检索密集型任务或者评测里但它并不省 VRAM。vLLM 支持不同的缩放类型(
dynamic
等),会针对每种缩放方式缓存对应的 cos/sin 查找表。用之前需要证下 factor 和 type 设置。
--rope-scaling '{"type":"dynamic","factor":4.0}' --max-model-len 64_000
7、推测解码降低 memory-bound 延迟
当瓶颈卡在内存带宽上时,speculative decoding 能帮上忙。让一个轻量级 draft model 先猜几个 token,大模型只负责快速验证接受或拒绝。实测下来,根据请求类型和上下文长度,加速比能到 2.5 倍左右。vLLM 现在把这个做成了一等公民特性,配置好就能看到 inter-token latency 明显下降。
8、跨会话持久化 KV 状态
如果经常重启 pod 或者激进地做 autoscale,每次都会把热的 KV 状态扔掉。外部 KV 持久化方案(比如基于快速共享存储的 LMCache)可以让服务重新加载或者共享 KV 片段,避免冷启动的卡顿。对于那种有大量重复 header/prompt 的检索流水线特别管用。这个可以看看 vLLM 和文件存储 KV 命名空间的集成案例。
9、多模态 token 也会占 KV 槽位
跑 VLM 的话还要注意图像(和其他模态)会像文本 token 一样在 KV 里占位置,这会悄悄压缩并发序列的空间。规划
--max-num-seqs
和内存利用率时要把多模态 token 算进去,不然加了视觉功能后会发现批次莫名其妙变小了。
10、后端选择要和 KV dtype 匹配
后端实现直接决定了 KV dtype 能不能跑得快:
FlashAttention-2:默认的高性能选项,但有些版本/后端不支持 FP8 KV
XFormers / FlashInfer:FP8 场景下可能得用这些,牺牲一点绝对速度换兼容性
如果开了
--kv-cache-dtype fp8*
但吞吐反而掉了,多半是掉到慢速路径上了。换个后端重新测一下,这个性能差异是有实际 benchmark 数据支撑的。
简单的入门配置
- 从
--gpu-memory-utilization 0.90+
和合理的--max-num-seqs
起步 - Hopper/Ada/MI300 架构上试试
--kv-cache-dtype fp8_e5m2
,确认后端走的是快速 kernel - 确保 chunked prefill 开启,观察负载下的 decode 延迟表现
- 标准化 prompt 结构,提升 前缀复用 的 block 对齐命中率
- 长会话场景优先考虑 SW-attention 模型,用上 混合 KV 管理器
- memory-bound 的工作流加上 speculative decoding,配个小 draft model
OpenAI 兼容服务器 + FP8 KV + 调优后的内存利用率(需硬件支持):
python -m vllm.entrypoints.openai.api_server \
--model mistralai/Mistral-7B-Instruct-v0.3 \
--kv-cache-dtype fp8_e5m2 \
--gpu-memory-utilization 0.94 \
--max-num-seqs 192
(如果吞吐量暴跌,切换后端或恢复 dtype 重新测试)
推测解码配置(draft model + target model):
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3-8b-instruct \
--speculative-model microsoft/phi-3-mini-4k-instruct
(跑个快速 A/B 测试后调整接受阈值)
总结
现在大部分 LLM 服务栈的瓶颈不在算力,而是 KV-bound。好在 vLLM 提供了不少现成的调优手段:扩大缓存空间、优化批处理策略、真正能命中的复用机制、匹配 dtype 的后端选择,以及 memory stall 时的 draft model 加持。
如果只改两个配置,建议试试 FP8 KV(硬件支持的话)和 chunked prefill。测一下数据然后再叠加 prefix 标准化和 speculative decoding。tokens/sec 和延迟曲线会直接告诉你效果。
https://avoid.overfit.cn/post/321dd7c3c76444b59e97137c23ff6965
作者:Nexumo