为什么“权重装得下”仍会并发OOM
模型权重加载后基本固定,KV Cache却为每个请求、每一层保存注意力Key和Value,并随输入与输出Token持续增长。单用户短对话成功,只能说明权重和一个小缓存能装入;长上下文或多人并发可能迅速耗尽剩余显存。
基础估算公式
对常见Transformer,可用下面的粗略公式估算单个Token缓存:
每Token KV字节 ≈ 层数 × 2(K和V) × KV头数 × 头维度 × 每元素字节
单请求KV ≈ 每Token KV字节 × 最大序列长度
总KV ≈ 单请求KV × 同时活跃请求数BF16/FP16每元素通常2字节,FP8通常1字节。使用GQA/MQA的模型,KV头数小于查询头数,因此不能直接拿注意力总头数代入。还要注意张量并行会如何切分KV头,以及框架块管理和对齐开销。
举例:某模型40层、8个KV头、头维度128、BF16缓存,则每Token约为`40×2×8×128×2=163840`字节,约160KiB。8192 Token单请求理论约1.25GiB;8个完全占满的活跃请求约10GiB。这个例子不能替代具体模型配置,只展示增长关系。
从模型配置读取参数
python - <<'PY'
from transformers import AutoConfig
c=AutoConfig.from_pretrained('Qwen/Qwen3-8B',trust_remote_code=True)
for k in ['num_hidden_layers','num_attention_heads','num_key_value_heads','hidden_size','head_dim']:
print(k,getattr(c,k,None))
PY若`head_dim`没有显式字段,常见计算是`hidden_size/num_attention_heads`,但某些架构会覆盖该值,应以模型代码与框架日志为准。
把理论值与vLLM日志对齐
从保守配置启动:
vllm serve Qwen/Qwen3-8B \
--served-model-name qwen3-8b \
--max-model-len 8192 \
--gpu-memory-utilization 0.80保存启动日志中的权重占用、可用KV块或并发估算。另一个终端记录:
nvidia-smi --query-gpu=timestamp,memory.used,memory.free,utilization.gpu --format=csv -l 1 | tee gpu-memory.csv依次发送1K、4K、8K输入,每次固定输出长度。模型的Tokenizer必须实际计算Token,不能用汉字数或文件字节估计。
生产并发测试矩阵
不要只测试“8个相同短问题”。至少包含:短输入长输出、长输入短输出、长输入长输出以及真实长度分布。并发从1、2、4、8逐级增加,每级保持数分钟,记录TTFT、TPOT、P95/P99、排队时间、活跃序列、缓存使用和失败数。
PagedAttention用固定块管理缓存,减少传统连续分配造成的碎片,但不会创造无限容量。请求结束或被抢占后块才能回收;大量长请求可能让短请求长期排队。
FP8 KV缓存何时值得用
把KV由BF16降到FP8理论上接近减半,但需要硬件和框架支持,并可能使用额外缩放因子。必须同时比较显存、并发、速度和长上下文任务质量,尤其关注需要精确检索或代码定位的任务。不要仅凭服务启动成功判定“无损”。
容量治理而不只是扩卡
- 为普通用户设置合理上下文上限,而非默认开放模型最大窗口。
- 长文档任务进入独立队列或独立服务池,避免拖慢短对话。
- 对公共系统提示词和重复文档启用可验证的前缀缓存。
- 设置最大输出Token与请求超时,防止失控生成长期占用缓存。
- 监控实际长度分布,按P95而非极端最大值规划常态容量,并为峰值留余量。
最终并发上限应定义为“在目标P95延迟和错误率内持续运行的活跃请求数”,不是框架日志给出的理论最大序列数。

