
缓存命中率 96%成本降到原来的三成左右。如果负责过大模型推理服务看到这句话第一反应应该是这不是一个普通的“性能优化指标”而是一条直接写在预算上的数字。很多团队在优化 LLM 服务时习惯盯 GPU 利用率、吞吐量、平均延迟这些指标当然重要但容易忽略一个更前置、更致命的问题大量 GPU 算力在重复计算已经算过的内容。同一个 system prompt、同一份知识库上下文、同一条用户前缀被不同请求反复 prefill。这部分的算力浪费不会直接显示在账单上却会实打实推高成本。缓存命中率低本质上是 KV Cache 没有被充分利用。要解决这个问题需要先回答三件事命中率怎么测、命中率怎么提升、命中率变化怎么被监控告警。本文以昇腾 NPU 环境的 vLLM-ascend 为例结合 Prometheus Grafana讲清楚缓存命中率从指标暴露、采集可视化到成本估算的完整链路并解释为什么命中率到了 96%成本可以出现数倍级别的下降。文章会涉及部署命令、指标配置、PromQL 查询、告警规则和常见排错适合正在做 LLM 推理服务、希望把成本监控落到实处的开发和运维同学。1. 缓存命中率为什么是 LLM 推理的“成本命门”先看一个容易忽略的现象LLM 推理的成本分布并不是均匀的。请求处理分为 prefill 和 decode 两个阶段prefill 阶段需要并行计算整段输入 prompt 的注意力矩阵计算量随输入长度线性增长是整个推理过程中计算密度最高的部分。多个请求如果包含相同前缀这部分计算会在每个请求里被重复执行。传统缓存解决的是“数据读取”问题比如 Redis 命中后不用查数据库。而 LLM 的 prefix cache 解决的是“计算复用”问题如果新请求的前缀 token 和之前某次请求完全一致那么这段前缀的 KV Cache 可以直接从显存中复用不需要重新 prefill。缓存命中率越高需要重复计算的输入占比就越低GPU 算力越能花在真正的新内容上。这就解释了为什么命中率会和成本强相关。假设一条生产链路中有大量长上下文请求比如客服知识库、代码助手、Agent 多轮任务这些请求的共同前缀可能占到输入 token 的 70% 到 90%。如果命中率只有 30%意味着大部分前缀都在重复烧钱如果把命中率提升到 96%重复计算部分被压缩到极低比例单位请求成本自然大幅下降。更要命的是缓存命中率还是一个“会突然恶化”的指标。近期社区里就有团队反馈模型客户端升级到新版本之后请求中的 prompt 模板、工具描述或 system prompt 发生变化缓存命中率一夜之间从 90% 跌到 60%。指标没有监控这种问题可能要等到账单出来才发现。所以缓存命中率不是“锦上添花”的调优项而是 LLM 服务的成本命门。它需要被采集、被可视化、被告警尤其在高并发、长上下文的实际业务里。2. KV Cache、前缀缓存与 vLLM-ascend2.1 KV Cache 是什么Transformer 模型在生成每个 token 时需要计算当前 token 与之前所有 token 的注意力。为了避免每次生成都重新计算历史 token 的 K、V 矩阵推理框架会把已经生成的 token 对应的 K、V 矩阵缓存在显存中这就是 KV Cache。KV Cache 有一个特点它和输入内容强相关。两个请求如果前缀不同KV Cache 就无法共享前缀相同则理论上可以复用。2.2 Automatic Prefix Caching 做了什么vLLM 引入的 Automatic Prefix Caching自动前缀缓存把 KV Cache 按块管理并对每个块的内容做哈希。新请求进入时框架会从第一个 token 开始匹配已有缓存块尽可能复用已经计算过的前缀只对未命中的部分执行 prefill。要实现高命中率前提是请求之间的前缀足够“整齐”。如果每个请求动态拼接了时间戳、随机 ID 或者可变的 system prompt那么前缀缓存会全部失效。这也是模型/客户端升级后命中率大跌的常见原因。2.3 vLLM-ascend 是什么vLLM-ascend 是 vLLM 在昇腾 NPU 上的适配实现。昇腾环境无法直接使用原版 vLLM 的 CUDA 内核需要经过 vLLM-ascend 兼容层来调度 Attention、KV Cache 和推理算子。从使用角度讲vLLM-ascend 仍然遵循 vLLM 的主体接口和指标体系因此 Prometheus 采集方案可以和 vLLM 基本对齐只是在部署和部分参数上存在差异。2.4 缓存命中率的两种口径需要区分两种命中率口径含义对成本的影响token 级命中率被缓存复用的 token 数占总输入 token 数的比例直接决定 prefill 计算量下降幅度请求级命中率有多少请求在 prefix 上命中了缓存反映业务前缀的整齐程度需结合 token 级一起看监控时优先看 token 级命中率因为它和成本换算的关系最直接。请求级命中率则更偏向业务层用于判断是否需要调整 prompt 规范。3. 环境准备与 vLLM-ascend 启动本文以昇腾 910B 系列 NPU 为例操作系统为 Linux版本信息请以你实际项目的兼容矩阵为准这里重点演示通用思路。3.1 基础环境需要准备以下部分昇腾 NPU 服务器例如 Atlas 800/900 训练服务器或推理服务器。已安装对应版本的 CANN toolkit并完成驱动、固件检查。Python 3.9 及以上版本建议使用独立的虚拟环境。vLLM-ascend 以及对应版本的 vLLM。安装命令示例# 创建虚拟环境 python3 -m venv /opt/venvs/vllm-ascend source /opt/venvs/vllm-ascend/bin/activate # 安装 vllm-ascend # 注意请先查看官方 README 中的版本对照表选择与 CANN 版本匹配的版本 pip install vllm-ascend安装完成后可以通过python -c import vllm; print(vllm.__version__)确认 vLLM 已可正常导入。3.2 启动 vLLM-ascend 服务vLLM-ascend 的常见启动方式是通过环境变量VLLM_USE_VLLM_ASCEND1激活昇腾后端然后使用 vLLM 的 OpenAI 兼容服务入口。下面是一个带前缀缓存和指标端口的启动示例VLLM_USE_VLLM_ASCEND1 vllm serve /data/models/Qwen2.5-14B-Instruct \ --served-model-name qwen14b \ --max-model-len 32768 \ --gpu-memory-utilization 0.90 \ --enable-prefix-caching \ --metrics-port 8001 \ --host 0.0.0.0 \ --port 8000参数说明--enable-prefix-caching开启前缀缓存。如果当前版本默认开启则此参数可省略如果版本不识别该参数请通过vllm serve --help确认实际参数名。--metrics-port 8001Metrics 指标端口。该端口用于 Prometheus 采集--port 8000是 API 服务端口。--gpu-memory-utilization 0.90允许框架使用 90% 的显存其中一部分会分配给 KV Cache pool。KV Cache 池越大缓存越不容易被淘汰。启动成功后可以先确认服务内部是否正常暴露了缓存指标。用下面命令查看curl http://127.0.0.1:8001/metrics | grep -i cache | head -30如果你看到的输出里有cache_hit、prefix_cache之类的指标名说明指标已正常暴露。不同版本、不同适配层的指标名会有差异后续 PromQL 请以这一步的实际输出为准。4. 暴露指标确认 /metrics 里的缓存命中率在配置监控之前先搞清楚当前版本的指标命名是最重要的一步。vLLM 社区版本中缓存相关指标可能叫vllm_cache_hit_rate、vllm_cache_hit_tokens_total、vllm_cache_miss_tokens_total、vllm:prefix_cache_hit_rate等不同版本前后缀不一致vLLM-ascend 可能还会叠加自己的命名方式。下面给出一个完整的确认流程# 查看全部 cache 相关指标名 curl -s http://127.0.0.1:8001/metrics | grep -i cache | awk {print $1} | sort -u假设输出包含类似下面的名称vllm_cache_hit_rate vllm_cache_hit_tokens_total vllm_cache_miss_tokens_total vllm_cache_len_total那么后面可以这样理解vllm_cache_hit_rate当前采集周期内的缓存命中率通常是一个 Gauge可以直接绘图。vllm_cache_hit_tokens_total累计命中 token 数Counter适合做趋势和速率计算。vllm_cache_miss_tokens_total累计未命中 token 数Counter。vllm_cache_len_total当前缓存块的规模。如果指标是 Counter 类型而不是直接的命中率可以用 PromQL 计算sum(rate(vllm_cache_hit_tokens_total[5m])) / (sum(rate(vllm_cache_hit_tokens_total[5m])) sum(rate(vllm_cache_miss_tokens_total[5m])))如果指标本身就是 Gauge比如vllm_cache_hit_rate直接查询即可avg(vllm_cache_hit_rate)这里必须强调写 PromQL 之前一定先用curl确认每个指标的真实名称和类型。监控配置写错了面板上只会一直显示空数据或 0这种问题在实操里非常常见。5. 部署 Prometheus 采集 vLLM-ascend 指标Prometheus 通过 HTTP 周期性拉取/metrics指标。下面用一台独立的监控服务器或容器来部署 Prometheus目标是把昇腾节点上的 vLLM-ascend 指标采集进来。5.1 Prometheus 安装可以直接通过官方二进制或 Docker 安装。这里以 Docker 方式为例docker run -d \ --name prometheus \ --restart unless-stopped \ -p 9090:9090 \ -v /etc/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \ prom/prometheus5.2 编辑采集配置创建/etc/prometheus/prometheus.yml写入 vLLM-ascend 节点的抓取配置# 文件路径/etc/prometheus/prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: vllm-ascend metrics_path: /metrics static_configs: - targets: - 10.0.0.11:8001 # 节点1 - 10.0.0.12:8001 # 节点2 labels: service: vllm backend: ascend配置要点scrape_interval建议设置为 10 到 15 秒既能及时发现命中率下跌又不会给推理节点增加明显的采集开销。target 地址必须是 vLLM-ascend 服务实际绑定的 IP 和端口。如果启动时--host 127.0.0.1Prometheus 从外部无法抓取应该绑定到内网可达地址。通过labels给不同节点打上标签方便在 Grafana 中按实例过滤。5.3 验证采集是否成功Prometheus 启动后访问http://prometheus_ip:9090/targets可以看到vllm-ascend这个 job 的状态。如果 State 是UP说明采集链路已经打通。也可以直接在 Prometheus 的 Graph 页面执行一句最简单的查询up{jobvllm-ascend}正常情况下会返回1表示目标在线。6. Grafana 可视化与告警采集到指标只是第一步。没有可视化时命中率下降仍然是隐性问题没有告警时问题只能靠人肉发现。下面在 Grafana 中把缓存命中率变成面板和告警规则。6.1 添加 Prometheus 数据源在 Grafana 中进入 Configuration - Data Sources - Add data source选择 PrometheusURL 填写http://prometheus_ip:9090点击 Save Test 完成。6.2 创建缓存命中率面板新建 Dashboard添加一个 Panel查询语句根据第 4 节确认到的指标名选择# 如果指标是 Gauge avg(vllm_cache_hit_rate{servicevllm}) # 如果指标是 Counter需要按速率计算 sum(rate(vllm_cache_hit_tokens_total{servicevllm}[5m])) / (sum(rate(vllm_cache_hit_tokens_total{servicevllm}[5m])) sum(rate(vllm_cache_miss_tokens_total{servicevllm}[5m])))Panel 建议配置Unit 选择percent即 0-100 的百分比。如果指标是 0-1 的小数可以在 Transform 中乘以 100或直接在查询中乘以 100。最好在同一张图里叠加不同实例的命中率用 legend 按instance维度区分。另外建议增加一个“命中 token 速率”面板sum by (instance) (rate(vllm_cache_hit_tokens_total[5m]))它可以直观看出命中量是否在上升。6.3 配置命中率告警规则可以有两种做法在 Grafana Panel 的 Alert 页里配置或者在 Prometheus 的 rules 文件中配置。推荐使用 Prometheus rules便于版本管理。创建告警规则文件/etc/prometheus/rules/llm_cache.yml# 文件路径/etc/prometheus/rules/llm_cache.yml groups: - name: vllm_ascend_cache_alerts rules: - alert: LLMPrefixCacheHitRateLow expr: avg(vllm_cache_hit_rate{servicevllm}) 0.8 for: 15m labels: severity: warning annotations: summary: vLLM-ascend 缓存命中率低于 80% description: 当前缓存命中率 {{ $value }}请检查 prompt 模板和最近的模型/客户端升级。然后在 Prometheus 主配置中引入该文件rule_files: - /etc/prometheus/rules/llm_cache.yml改动后执行curl -X POST http://prometheus_ip:9090/-/reload热加载配置或重启 Prometheus。注意告警表达式里的指标名要和实际情况一致否则告警不会触发。6.4 告警阈值的设定思路阈值不建议拍脑袋定。可以先跑一周观察不同业务时段的命中率分布再把告警阈值设在正常值的下降点附近。比如正常命中率稳定在 90% 以上阈值可以设在 80%并持续 15 分钟确认避免短时间波动误报。7. 用成本模型解释 96% 命中率与 3.2 倍成本标题里的“成本降 3.2 倍”并不是一个硬性的性能测试结论而是一个在长上下文、高命中业务里接近真实情况的估算。它可以被简化为一个成本模型。LLM 推理成本大致可以分为两部分prefill 计算成本与输入 token 量强相关。decode 生成成本、调度开销、显存占用等其他成本。假设 prefill 占整体计算成本的 70% 到 75%缓存命中率是 h那么整体计算成本比例可以近似为综合成本比例 (1 - h) × prefill成本占比 非prefill成本占比当 h 96%prefill 占比为 72% 时综合成本比例 0.04 × 0.72 0.28 0.3088 ≈ 30.9%也就是说综合成本约为原来的 31%按行业常见的“成本降幅”口径大约相当于“成本降了 3.2 倍”。下面这张表可以直观看出不同命中率下的成本比例缓存命中率需要重新计算的 prefill 比例prefill 占成本 70% 时的综合成本比例行业习惯说法0%100%100%基线50%50%65%约降 1.5 倍80%20%44%约降 2.3 倍96%4%28% - 33%约降 3 倍以上注意这是简化模型真实场景里还要考虑请求长度分布、batch 大小、缓存淘汰策略、显存池配置等因素。但它给出了一个非常重要的判断缓存命中率每提升一截都会直接折算成 GPU 算力成本的下降。这也是为什么值得把命中率接进 Prometheus Grafana 的原因指标一旦可视化就可以在业务大促前、模型升级前先评估命中率对成本的潜在影响而不是事后看账单。8. 常见问题与排查思路下面整理了一些监控 vLLM-ascend 缓存命中率时常见的问题以及对应的排查方法。问题现象可能原因排查方式解决方案命中率突然从 90% 跌到 60%模型或客户端升级后system prompt、工具描述、prompt 模板发生变化对比升级前后请求日志中的原始 prompt检查前缀是否变化固定 prompt 模板先灰度升级异常时回滚命中率一直为 0未开启 prefix caching或请求前缀里有时间戳、随机 ID查看启动参数和一次完整请求的报文启用前缀缓存对 prompt 做规范化处理Prometheus targets 显示 DOWN指标端口未绑定对端可达 IP或防火墙拦截用 curl 从 Prometheus 所在机器访问 /metrics启动时绑定内网 IP开放访问策略Grafana 面板无数据PromQL 指标名写错或 label 不匹配在 Prometheus 查询页面手动执行表达式检查返回结果按 curl /metrics 的实际指标名替换缓存命中率低但显存持续增长KV Cache pool 太小缓存块被频繁淘汰查看 KV Cache 利用率指标和平均请求并发提高 gpu-memory-utilization或降低服务并发多副本负载均衡后命中率整体下降请求被分散到不同实例各实例缓存无法共享观察不同 instance 的命中率分布按业务/用户做一致性路由或引入分布式 KV Cache 方案指标名在文档和实际环境对不上vLLM 和 vLLM-ascend 版本差异较大直接抓取当前 /metrics 的指标名后续 PromQL 与告警全部以实际指标名为准8.1 命中率下降排查顺序遇到命中率下降建议按下面的顺序快速定位看最近是否有代码、模型、客户端或者 prompt 模板变更。对比变更前后同一批请求的完整输入前缀。确认是否存在动态内容混进了公共前缀。看 Prometheus 面板确认下降是瞬间发生的还是逐步发生的。如果是逐步下降优先怀疑缓存块被淘汰检查 KV Cache 池大小和并发数。如果只有某一台实例下降检查该实例负载均衡策略和缓存状态。9. 最佳实践与工程建议9.1 把公共前缀做成固定常量要让缓存命中率稳定在 90% 以上最重要的工程手段不是调框架参数而是控制 prompt 的“整齐度”。把 system prompt、工具描述、知识库背景、角色设定等静态内容放在 prefix 的最前面并且作为固定常量管理把动态内容放到 prompt 尾部。如果动态内容必须出现在中间缓存会变得很难命中。此时可以考虑对 prompt 做分段静态前缀 动态知识检索 用户问题并让框架识别出静态前缀。9.2 升级前后先看命中率模型版本升级、vLLM-ascend 版本升级、客户端 SDK 升级都可能影响缓存命中率。原因是升级后 tokenizer、tool 描述甚至 system prompt 模板可能变化。建议把“缓存命中率对比”纳入发布检查清单升级前记录基线升级后灰度观察不达标就回滚。9.3 监控指标不要只看命中率命中率需要和以下指标一起看才能定位问题TTFT首次 token 延迟命中率高时TTFT 应明显下降。平均吞吐量和 GPU 利用率命中率提升后应看到单位时间处理请求数上升。KV Cache 利用率用于判断缓存块是否充足。prefill 和 decode 的计算量分布确认成本结构是否发生变化。9.4 指标端口的访问安全Prometheus 抓取指标时会暴露内部运行数据。不要把/metrics端口直接映射到公网。实测中更稳妥的做法是指标端口绑定到内网地址。Prometheus 与 vLLM-ascend 通过内网通信。如果跨网络使用认证代理或公司内部的指标网关。Grafana 同样建议做访问控制避免面板和数据源被未授权访问。9.5 告警要能驱动行动告警阈值只是一个开始。出现命中率告警后需要让值班人员知道下一步做什么。最佳实践是在告警描述里写清楚命中率当前值。最近一次模型或配置变更时间。可能受影响的业务线。需要联系谁。没有操作指引的告警最后会被习惯性忽略。9.6 定期做成本复盘缓存命中率不是一个“配完就完了”的指标。建议每两周或每个迭代周期做一次复盘命中率均值、峰值、低点分别是什么业务变化对命中率的影响以及命中率变化折算出来的成本变化。这样监控体系和成本优化才能形成闭环而不是停留在看板好看。末尾一个最容易踩的坑最后提醒一个实际项目里最容易踩的坑不要在一个请求里把所有内容拼成一大段动态文本。有人把用户 IP、当前时间、trace ID、session ID 全拼进 system prompt 或 prompt 前缀导致前缀缓存全局失效命中率无限接近 0。这类问题在代码 review 阶段很难发现但一旦接上 Prometheus 监控命中率曲线会立刻暴露出来。所以给缓存命中率配上监控不只是为了看一个好看的百分比更是为了让成本问题在代码变更时第一时间暴露。部署 prometheus grafana 收集 vllm-ascend 的缓存命中率指标本质上是把 LLM 推理成本从“事后账单”变成“实时反馈”这比优化一个具体参数有意义得多。