拓冰建站拓冰建站
首页 / 资讯中心 / 正文

vLLM 性能监控与调优:TTFT、KV Cache 与 GPU 利用率实践指南

先把 vLLM 部署到大模型推理服务上之后很多人第一件事就是看能不能正常出字。能出字不代表服务能扛业务。一个自托管的 LLM 服务跑得好不好我判断的维度基本就三条首字延迟高不高、KV Cache 会不会被打满、GPU 是不是真的在干活。这三个维度对应到 vLLM 暴露的 Prometheus 指标上就是 TTFT、KV Cache 相关指标和 GPU 利用率。vLLM 自带 /metrics 端点Prometheus 直接抓取Grafana 画曲线整个推理服务的健康度和性能瓶颈就能可视化地呈现出来。这篇文章会从指标含义讲到部署落地再到实际调优经验适合那些已经用 vLLM 部署好模型、但还想把服务压榨得更透的朋友。1. 先想清楚为什么调优自托管 LLM 必须看指标1.1 从“能用”到“可观测”差的是一整套采集链路我第一次用 vLLM 起服务的时候也是 curl 一下接口看到返回文本就觉得自己已经“部署完成”了。等真把服务接到业务上才发现问题根本不是“能不能出字”而是“并发一上来为什么响应越来越慢”“为什么显存快满了GPU 利用率却不高”“为什么同一个问题换个时间问首字输出时间差了 3 倍”。这些问题靠看日志是看不出因果关系的。日志只能告诉你某个请求耗了多少时间但它回答不了“耗在哪”“为什么耗”“是不是系统性的抖动”。我后来习惯的做法是把 vLLM 服务当成一个需要持续监测的中间件来对待。vLLM 很贴心地内置了一个 Prometheus 指标端点路径就是 /metrics只要服务在跑访问 http://127.0.0.1:8000/metrics 就能拿到一大串结构化指标。把这些指标接入 Prometheus再用 Grafana 展示才算是把服务的运行状态真正“打开”了。为什么选 Prometheus 而不是自己写监控因为 Prometheus 这套组合在自托管场景里几乎零成本vLLM 官方也默认兼容指标命名和语义都保持稳定。相比之下自己用 Python 脚本去轮询 vLLM 的内部状态既慢又脆弱还容易错过瞬时峰值。1.2 vLLM 指标端点能覆盖哪些性能问题vLLM 的 /metrics 端点暴露的指标非常多从请求排队数、运行中请求数、生成 token 总数到 TTFT 直方图、TPOT 直方图、KV Cache 占用率、缓存命中率都有。对做自托管 LLM 的人来说这基本就是官方白送的一套可观测数据不需要额外埋点也不需要改 vLLM 代码。我建议刚接触的朋友先不要被几十个指标吓到。日常调优盯住三个指标组就够了TTFT首字延迟、KV Cache 相关指标、GPU 利用率。它们分别对应了用户能感知到的“慢”、系统内存侧的“挤”和硬件侧的“闲”。把这三类指标的关系搞清楚服务的大部分故障和性能问题都能定位到方向。还有一点值得提指标采集本身也会有一点开销但 vLLM 暴露指标用的是独立的 metric 线程正常并发下对推理延迟影响非常小几乎可以忽略。不要因为担心这点开销而不去接监控那样反而会失去对整个服务的洞察力。2. 核心指标逐个拆解TTFT、KV Cache 与 GPU 利用率2.1 TTFT用户感知到的“第一个字”等了多久TTFTTime To First Token指的是从客户端发起请求到服务端返回第一个输出 token 的时间。在流式输出场景下用户感知到的“首字速度”就是这个指标。TTFT 一旦偏高用户的第一印象就是“这 AI 怎么这么慢”哪怕后续 token 生成速度很快也没用。不过 TTFT 不能只看平均值。直接用curl测一次得到的时间是冷启动或低负载时间不代表真实业务的体感。我建议把 TTFT 分成几个维度看P50 代表典型情况P95 代表尾部延迟再结合请求数曲线判断 TTFT 是随并发升高而恶化还是始终稳定。vLLM 在指标里暴露的是vllm:time_to_first_token_seconds这类直方图Prometheus 里很自然就能用 histogram_quantile 取出 P95。TTFT 偏高的常见原因一般有三个方向一是 Prompt 太长预填充阶段算力都花在处理输入上二是并发太高请求在 queue 里等着分配资源三是 KV Cache 被挤占太狠新请求拿不到足够的缓存块调度器不得不做等待或抢占。这三种原因对应完全不同的优化手段不看指标只能瞎猜。顺带提一下 TTFT 和 TPOT 的关系。TPOT 是每个输出 token 的平均生成时间决定的是出字流畅度。判断“对话飞快但首字慢”还是“首字还慢出字也慢”对调优方向的区分非常关键。我在 4.1 节会专门讲不同场景的处理方式。2.2 KV Cache推理时的“内存账本”KV Cache 是 Transformer 解码过程中保存历史 Key 和 Value 向量的一块显存缓存。每生成一个 token模型都只需要拿新的 query 去和缓存的 K、V 做注意力计算。没有 KV Cache模型就得把之前所有 token 重新算一遍计算量呈平方级膨胀。所以 KV Cache 本质上是用显存换算力。vLLM 的 KV Cache 是分块管理的缓存块被组织成一张 PagedAttention 的块表。指标里的vllm:gpu_cache_usage_perc表示 GPU 上缓存块的使用率vllm:cpu_cache_usage_perc表示 CPU 侧的缓存块使用率。这个比例越接近 1说明 KV Cache 越紧张新请求可能进不来老请求可能被 swap 到 CPU进而产生抢占和性能抖动。KV Cache 占用到底多大其实可以估算。以 Qwen2.5-7B 这类模型为例层数 28、KV head 数为 4、head_dim 为 128每个 token 对应的 KV 元素数量大约是 2 × 28 × 4 × 128 28672 个用 FP16 存储每个 token 大概 56 KiB。如果单条请求上下文 8192 tokens就需要约 448 MiB 的 KV Cache。模型规模越大、上下文越长缓存开销增长越明显。缓存命中率也是一个重要指标对应 vLLM 的vllm:prefix_cache_hit_rate。它衡量的是带前缀复用能力比如系统提示词相同的情况下新一轮请求能不能直接复用之前算好的前缀缓存。命中率高TTFT 会显著下降。开启--enable-prefix-caching之后这个命中率会明显上升对问答场景收益尤其大。2.3 GPU 利用率别被 nvidia-smi 的 100% 骗了GPU 利用率这个指标最容易被误读。很多人喜欢用nvidia-smi里那个 GPU-Util 百分比觉得看到 100% 就是“显卡跑满了”其实那个百分比是采样周期内 CUDA 核被占用的比例不是真正的 SM 计算单元干活效率。有时候显存带宽打满、SM 在等待数据传输nvidia-smi 也可能显示一个不低的比例但那不代表有效算力被充分利用。vLLM 自己的指标端点里并不直接暴露 SM 利用率。它给的主要是请求调度层面的指标比如vllm:num_requests_running、vllm:num_requests_waiting。所以我通常把 GPU 利用率分成两层看一层是硬件层的真实使用率用 NVIDIA DCGM Exporter 或 nvidia-smi 工具拿另一层是 vLLM 调度层的“活跃度”看 running、waiting 和 preemption 指标。把两个图层叠在一起能发现很多有意思的现象。比如 running 请求很多但 GPU 利用率低说明单个请求可能都在 CPU 侧做 tokenizer 或调度GPU 处于“吃不饱”状态。再比如 GPU 利用率高但 TTFT 依然增长说明瓶颈更多在算力或显存带宽而不是调度排队。2.4 三大指标速查表指标组典型指标核心含义关注时机TTFTvllm:time_to_first_token_seconds首个输出 token 的耗时用户反馈首字慢、流式体验差KV Cachevllm:gpu_cache_usage_perc、vllm:prefix_cache_hit_rate显存缓存占用率、前缀命中率OOM、并发受限、首字飘忽GPU 利用率DCGM/nvidia-smi 的 SM 利用率、显存带宽硬件真实负载吞吐上不去、并发增加不线性调度相关vllm:num_requests_running、vllm:num_requests_waiting当前活跃和排队请求数并发压测、限流策略抢占相关vllm:num_preemptions_total被抢占的请求数显存紧张、延迟抖动3. 从零搭建vLLM Prometheus Grafana 的完整链路3.1 启动 vLLM 时怎么把指标基础打牢跑 vLLM 最常见的方式还是 Docker。很多人喜欢一行 docker run 跑起来就不管了我认为启动阶段就得多花点心思不然后面的监控和调优全是在补救。先看一个我实际用的启动命令docker run -d --name vllm-qwen \ --gpus all \ -p 8000:8000 \ -v /data/models:/models \ vllm/vllm-openai:latest \ --model /models/Qwen/Qwen2.5-7B-Instruct \ --gpu-memory-utilization 0.88 \ --max-num-seqs 32 \ --max-model-len 8192 \ --enable-prefix-caching这里几个参数是给监控和调优打底子的。--gpu-memory-utilization 0.88表示 vLLM 最多只占用 88% 的显存剩下的留给 CUDA 上下文和 Driver 开销避免显存顶到 100% 导致进程崩溃。--max-num-seqs限制并发序列数防止并发太高把 KV Cache 撑爆。--enable-prefix-caching开启前缀缓存对稳定 TTFT 和提升缓存命中率非常有用。启动之后用curl -s http://127.0.0.1:8000/metrics看一下输出如果能看到以vllm:开头的指标行就说明指标端点已经正常暴露。这一步千万别跳过很多版本升级之后指标名会有细微变化提前确认能省掉后面排查的很多麻烦。3.2 Prometheus 采集配置Prometheus 本身部署很简单我用的也是 Docker 方式。关键是写好 prometheus.yml把 vLLM 服务的地址加进去。示例如下global: scrape_interval: 5s evaluation_interval: 5s scrape_configs: - job_name: vllm metrics_path: /metrics static_configs: - targets: [192.168.1.100:8000]这里的192.168.1.100要替换成你 vLLM 服务所在机器的实际 IP。scrape_interval我习惯设成 5 秒既能捕捉到请求峰值又不会给 vLLM 增加太多负担。如果 vLLM 和 Prometheus 在同一台机器上写成localhost:8000也可以。启动 Prometheus 之后去它的 Target 页面看 Endpoint 是否变成 UP状态是 DOWN 的话优先检查网络连通性和端口。Prometheus 的部署命令docker run -d --name prometheus \ -p 9090:9090 \ -v /data/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \ prom/prometheus:latest3.3 Grafana 展示与告警Prometheus 负责存数据Grafana 负责把数据变成人看得懂的图。Grafana 部署也很简单docker run -d --name grafana \ -p 3000:3000 \ -e GF_SECURITY_ADMIN_PASSWORDadmin \ grafana/grafana:latest登录 Grafana 之后先添加一个 Prometheus 数据源地址填http://prometheus-ip:9090。接下来建 Dashboard我一般先建四个只展示核心指标的面板当前运行请求数、TTFT 的 P95、KV Cache 使用率、GPU 利用率。四个面板放在一屏服务状态一目了然。如果想让 Grafana 在指标异常时主动告警可以配置 Alert Rules。比如vllm:gpu_cache_usage_perc 0.95持续 5 分钟就触发告警TTFT 的 P95 超过某个阈值也触发告警。告警渠道无论是邮箱还是企业微信 Webhook 都行关键是别让监控只停留在“事后看图”的阶段。3.4 几组常用的 PromQL 查询Grafana 面板本质上是填 PromQL 查询语法的。下面几组是我日常最常用的直接抄到面板里就能用平均 TTFTrate(vllm:time_to_first_token_seconds_sum[5m]) / rate(vllm:time_to_first_token_seconds_count[5m])TTFT 的 P95histogram_quantile(0.95, sum(rate(vllm:time_to_first_token_seconds_bucket[5m])) by (le))每分钟生成 token 数sum(rate(vllm:generation_tokens_total[1m]))KV Cache 使用率vllm:gpu_cache_usage_perc当前运行/等待请求数vllm:num_requests_running vllm:num_requests_waiting这组 PromQL 针对的是 vLLM 0.6 系列之后的主流指标命名不同小版本可能在标签上略有差异。遇到查询不出数据时先到 Prometheus 的 Graph 页面敲一个vllm:前缀看看自动补全清单里实际有哪些指标再照着填。4. 真实调优场景手把手解决慢、卡、满4.1 场景一TTFT 居高不下首字输出很慢现象是流式响应概率很高但迟迟打印不出第一个 token。我用前面搭好的监控一看TTFT 的 P95 一路飙到 3 秒以上而 TPOT 正常说明瓶颈集中在预填充阶段等待阶段。按之前说的三个方向排查第一看 running 请求数如果一直高说明并发调度已经挤满第二看 prefix_cache_hit_rate如果偏低说明系统提示词和用户前缀没有被复用第三看 prompt tokens 是不是特别长如果单条请求前缀就有上千 token预填充时间自然慢。我的处理顺序是先开--enable-prefix-caching这能立竿见影地提升前缀命中率特别是系统提示词固定的场景。接着调--max-num-seqs把并发从 32 降到 24牺牲一点吞吐换取更稳定的首字延迟。如果 Prompt 长度确实很长还可以考虑--max-model-len是否设得过大因为过大上下文会加剧预填充压力。极端情况下还可以用 vLLM 的 chunked prefill 机制把长预填充切成更小的 chunk避免一个长请求独占 GPU 太久。这里要提醒一个坑chunked prefill 的 chunk_size 参数不是万能的。有一个版本里我遇到设置 chunk_size 后性能反而波动更大的情况后来发现是和某个 vLLM 版本的 batch 调度逻辑冲突。遇到这种问题先回退参数默认值再做单一变量对比不要同时改一堆参数。4.2 场景二KV Cache 占用接近 95%并发上不去这个场景最典型的反馈是“显存看着还有很多但只要多来几个请求就报 OOM 或者请求失败”。我的监控里 gpu_cache_usage_perc 长期在 0.9 以上num_preemptions_total 持续增长说明 KV Cache 确实成了瓶颈。KV Cache 被占满本质是单个请求的上下文太长或者并发请求太多导致缓存块不够分。我的优化思路是分步骤来第一步降低--max-num-seqs从 32 降到 16先减少同时占用缓存的请求数量。第二步检查--max-model-len如果业务根本用不到 16K 上下文果断把最大长度降到 8K释放大量 KV Cache 块。第三步如果显存还有富余调高--gpu-memory-utilization给 KV Cache 多预留一些空间。第四步评估是否需要换更省显存的数据格式例如 KV Cache 用 FP8 存储能显著降低单 token 的缓存占用。多说一句 KV Cache 计算的意义。我算过自己这个 Qwen2.5-7B 的模型8K 上下文单请求缓存大概 448 MiB。看起来不多但并发 32 个请求同时跑满 8K就是 14 GiB 级别的显存消耗占一张 24G 卡的近 60% 了。所以别只看模型权重占了多少显存KV Cache 才是并发上限的真正瓶颈。4.3 场景三GPU 利用率低加并发也没有吞吐提升监控显示 GPU 利用率只有 40% 左右但 TTFT 和 TPOT 都正常只是并发上来后总吞吐不见涨。这种“鱼和熊掌都没得到”的情况我第一个想到就是 CPU 侧的瓶颈。大模型推理不只是 GPU 上跑矩阵乘法还包括 tokenizer、采样、调度、通信、数据传输。如果 vLLM 的处理循环被 CPU 拖住GPU 就会空转等待。判断方法很简单把请求并发从 1 提到 8看每分钟 token 数是否线性增长。如果涨得很慢而且 GPU 利用率还是不高说明瓶颈很可能在 CPU 或显存带宽。我的解决办法是增加实例内的 batch 能力让 GPU 单次能处理更多序列。具体操作是调高--max-num-seqs让 VLLM 有机会把更多请求拼进同一个 batch。同时检查 CPU 有没有被其他进程抢占尤其是 Grafana 和 Prometheus 如果也部署在同一台机器上数据采集本身也可能消耗 CPU。另外如果模型支持张量并行比如用两张卡跑同一个模型可以开启--tensor-parallel-size 2。但要注意小模型在单卡和双卡上的表现差距可能不大通信开销反而拖后腿。一般 7B 模型单卡就能跑得很好70B 模型才值得考虑多卡并行。4.4 常见问题速查表现象可能原因排查手段建议操作首字慢出字正常预填充过长或前缀命中率低看 TTFT、prefix_cache_hit_rate开启前缀缓存、缩短 Prompt显存够但 OOMKV Cache 不足看 gpu_cache_usage_perc降并发、降上下文、调大显存利用率GPU 利用率低CPU 调度瓶颈并发阶梯压测调大 max-num-seqs、优化机器配置请求被抢占缓存块不够看 num_preemptions_total调小 max_num_seqs 或 max_model_len某个版本指标查不到指标命名变化curl /metrics 核对按实际指标名重写 PromQL5. 最后再分享一点我的真实体会这套 vLLM Prometheus Grafana 的监控体系我落地过不止一次。刚开始做的时候我也觉得麻烦觉得“先跑起来再说”。但后来每次线上出问题靠着监控曲线定位到具体瓶颈比几个人围着服务器猜半天高效太多了。有一点我必须强调指标是用来辅助判断的不是拿来压 KPI 的。不要把 gpu_cache_usage_perc 调到 99% 就觉得自己“优化到位”了。缓存占用高不一定代表服务高效它还可能是潜在风险。有几次我把并发调得很大缓存使用率看着很饱满结果一遇到稍长的 Prompt整个服务就开始频繁抢占用户侧延迟反而飙升。所以我的建议是每次只改一个参数观察半小时的监控曲线再决定下一步动作。把 TTFT、KV Cache、GPU 利用率这三条曲线放在同一时间轴上对比你会发现很多问题在趋势里早就埋下伏笔。把监控搭好等于给 vLLM 服务装了一块仪表盘后面不管是调参还是扩容都有了“凭数据说话”的底气。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门