
作者来自 Elastic Bahubali Shetti使用 Elastic 可观测性中的 Prometheus 指标调优自托管的 vLLM 推理 —— TTFT、KV Cache、前缀缓存和 DCGM GPU 计数器你们公司某个地方一定有一个团队无法使用 Claude、GPT 或 Gemini —— 不是因为他们不想用而是因为他们的数据不允许离开某个司法管辖区、网络边界或者受到合同限制。理赔文件、患者记录、受出口管制制度约束的源代码等等。这个团队仍然需要一个模型。所以请求就落到了 SRE 的桌面上听起来出奇地简单“你能不能为理赔团队部署一个开放权重模型60 个人使用。必须运行在我们的硬件上。” 他们甚至不允许使用新云服务商。这里面确实存在成本但我们不会讨论这一部分。我们只关注运行模型以及观测配置的部分。部署起来是比较容易的一半。四个清单文件一个下午你就可以让模型回答问题。困难的一周后才会出现有人说“感觉很慢”而你意识到自己完全不知道这个部署的配置是好、是坏还是糟糕到灾难性的程度——也没有明显的方法可以找出答案。本指南将介绍如何使用 Elastic 可观测性帮助你分析配置产生的指标。我们将使用 vLLM 已经提供的指标对一个真实的 vLLM 部署进行调优。vLLM 会在/metrics端点以Prometheus 展示格式公开这些指标——无需插桩、无需 sidecar、无需修改代码——这也是为什么本指南中的每个查询都从 Prometheus 抓取开始。目标是将“感觉很慢”转化为一个具体且有充分依据的决策。测试环境使用 NVIDIA A10G 在 Kubernetes 集群上运行 vLLM并配合 dcgm-exporter 和 Prometheus 指标本指南中的每一项数据都是在以下技术栈上测量的——一个副本、一块 GPU、没有自动扩缩容。工作负载——原生 Kubernetes 负载生成器并发请求数从 8 增加到 32。模型——Qwen/Qwen2.5-3B-Instructbf16--max-model-len 4096引擎——vLLMv0.23.0兼容 OpenAI 的服务器Prometheus/metrics位于:8000GPU——NVIDIA A10G24 GB——一台 AWSg5.xlarge集群——Amazon EKS 1.30带污点的 GPU 节点池minSize: 0遥测——Prometheus 每 15 秒抓取一次另外使用dcgm-exporter在:9400上提供指标并通过remote_write发送分析——Elastic 可观测性使用 ES|QL 和 PromQL 进行查询测量时间——2026-07-27为什么 SRE 很难自托管并调优开放权重 LLM困难不在于部署而在于调优因为调优没有反馈闭环。无论配置得非常出色还是极其浪费vLLM 都会启动、提供服务并报告成功。没有任何东西会告诉你到底是哪一种情况。加载模型时你的清单文件会包含以下配置containers: - name: vllm image: vllm/vllm-openai:v0.23.0 # pin an exact release — metric names shift between versions args: - --modelQwen/Qwen2.5-3B-Instruct - --max-model-len4096 # cap context → predictable KV-cache size # A10G has native bf16 — do NOT add --dtypehalf (T4-only). ports: - name: http containerPort: 8000 # OpenAI API /metrics但你可能会遇到一些具体问题例如Hugging Face 下载需要几分钟。如果你的集群要求服务器在 30 秒内启动它会认为应用已经失效并在下载过程中将其终止让你陷入无限崩溃循环。或者你可能遇到硬件不匹配的问题由于硬件架构各不相同这可能会降低模型的速度或精度。或者还有大量其他问题。模型终于运行起来之后优化性能就变成了完全靠猜因为默认指标无法告诉你是否正在高效运行。你可以使用nvidia-smi但它只了解原始硬件状态而不了解应用软件逻辑。现在模型已经运行起来了几个小时甚至一天之后团队说“感觉很慢。”实际上你是在盲目调优。如何调优自托管的 vLLM 部署你不是推理工程师除了这个服务之外你还负责另外 40 个服务而且没有模型供应商派驻的工程师随时待命。但事实证明调优 LLM 服务器恰恰需要一个你已经具备的技能读取遥测数据并分析饱和度。唯一缺少的是有实际意义的遥测数据。它确实存在。vLLM 开箱即用地提供了丰富的 Prometheus 端点——按照推理阶段分解的延迟、缓存命中率、批次占用率、令牌统计、完成结果。几乎没有人查看这些指标。本指南接下来的内容就是介绍如何读取这些指标定义工作负载60 个用户、短提示词、流式响应检测到并报告响应缓慢之后你收集用户的使用情况。其使用模式如下约 60 个用户但不是同时使用。实际峰值为8–12 个同时处于处理中的请求持续并发量更低。短提示词、长回答。用户粘贴一段文字并要求生成结构化摘要。提示词约 50 个令牌有用的回答约 500–1,000 个令牌。交互式流式 UI。用户感知的速度主要由**首令牌时间TTFT**决定而不是总时间——这与聊天界面的用户心理预期相同。大量提示词复用。每个请求都包含相同的系统提示词以及相同的策略语言模板。基于这些情况你制定了实际的服务目标——这是大多数自托管 LLM 项目都会跳过的一步TTFT p95 300 ms令牌间延迟 50 ms≥ 20 个令牌/秒比阅读速度更快12 个并发请求时零排队错误 中止率 0.5%这四个数字是后续所有内容的核心。没有它们“感觉很慢”就没有答案。有了它们下面的每个指标都可以根据一个明确的标准判断通过或失败。如何从 vLLM 和 GPU 获取 Prometheus 指标有两个来源而且两者都需要。vLLM 报告自身的运行情况——包括按阶段划分的延迟、缓存命中率、批次占用率、令牌数量——通过服务端口上的/metrics提供无需适配器也无需进行插桩工作。GPU 则通过 NVIDIA 的dcgm-exporter在:9400上单独报告指标NVIDIA Data Center GPU ManagerDCGM是一套工具和库旨在全面管理、监控和诊断集群和数据中心中的企业级 NVIDIA GPU。vLLM 告诉你推理引擎认为正在发生什么DCGM 告诉你显卡实际上正在做什么。第 6 步完全建立在这两个答案之间的差异之上。在这个 EKS 集群中这意味着三个组件并行运行vLLM作为一个普通的 Deployment运行在带污点的g5.xlargeGPU 节点池上。对于一张显卡上的一个模型而言一个 Deployment 和一个 Service 就构成了完整架构。dcgm-exporter作为一个 DaemonSet固定运行在相同的 GPU 节点上。一个 Prometheus 服务器运行在 CPU 节点上每 15 秒抓取这两个端点。这里没有任何 AWS 特有的内容。生产版本使用的是相同的清单文件只不过运行在配备 L40S 或 H100 节点的本地部署集群上——这正是选择在 Kubernetes 上完成这项工作的意义所在而不是使用供应商的平台。KServe 和 llm-d 怎么样本指南没有运行这两者而且它们都不会改变指标的来源。KServe 和 llm-d 位于 vLLM之上而不是取代 vLLM —— vLLM 仍然是推理引擎因此/metrics仍然是这里每个数据的来源。每一个都会在上面增加自己的层KServe自动扩缩容和版本指标llm-d路由器和缓存路由指标但底层的推理遥测数据完全相同。它们改变的是你什么时候需要它们—— 而且每次升级都是由你已经在收集的某个指标触发的如何将 vLLM 指标和 DCGM 指标发送到 Observability集群内的 Prometheus 抓取只能保存几个小时的数据 —— 这些指标必须进入一个你下周仍然可以查询的数据存储。有两种方式可以做到这一点而且它们并不等价。路径 A —— OpenTelemetry Collector。将推理指标放入与你的 traces 和 logs 相同的流水线中。一个 Collector、一条认证路径、一套思维模型。代价是通过 OTLP 发送的 Prometheus 指标会被标准化最终存储的 schema 既不是纯粹的 Prometheus也不是纯粹的 OTel而且指标名称也会发生变化。路径 B —— 原生 Prometheusremote_write。部署一个小型 Prometheus同时抓取两个端点的数据然后将数据推送到支持 remote-write 协议的后端。名称和标签保持不变_sum/_count/_bucket直方图部分也会保持完整现有查询可以继续使用。对于调优实验请选择路径 B。本指南中的每一个数据都是通过这种方式得到的。原因很简单但非常关键调优意味着需要与 vLLM 文档和 vLLM 社区进行对照而两者使用的都是精确的指标名称。当你的图表显示vllm:kv_cache_usage_perc时你可以直接搜索它。部署需要两个 YAML 文件——一个包含两个抓取任务和一个remote_write代码块的 Prometheus Deployment以及一个保存后端凭证的 Secret。在这个构建中目标是一个 Elastic Serverless 项目它提供 Prometheus remote-write 端点并将数据写入一个时间序列数据流metrics-vllm.prometheus-inference。有两件事花费了我大量实际时间。如果你的后端为 OTLP 和主 API 分别提供了不同的 ingest 主机那么 remote-write 通常位于主 API 主机上而不是 ingest 主机上——指向错误的主机会返回 404看起来像是路径错误。而且凭证需要索引写入权限而不仅仅是 ingest 认证权限一个能够正常用于 OTLP 的密钥可能会成功通过认证然后对每个样本都返回 403。在查看其他任何地方之前先检查 Prometheus 自身的prometheus_remote_storage_samples_failed_total。如何确认 vLLM 指标已经写入 Elastic流水线启动后查看字段列表。单个 vLLM Pod 加上 DCGM 大约会产生127 个指标序列这个页面比看起来更有用。扫描字段列表可以确认你的 vLLM 版本所使用的确切指标名称——不同主要版本的 vLLM 之间指标名称确实会发生变化而针对错误名称构建的仪表板不会报错而是静默地返回空结果因此会失效。查询 vLLM 指标时的两个注意事项这两种情况都会产生类似于“指标没有正常工作”的结果而实际上整个数据流水线完全正常。vLLM 指标名称包含冒号vllm:num_requests_running因此在大多数查询语言中都需要进行转义。更隐蔽的是如果你在多个指标之间按照指标名称进行过滤然后只聚合其中一个指标你仍然会得到结果行——其中充满null但不会报错。每个 Prometheus 指标都会写入自己的字段因此字段名称本身就是过滤条件你根本不需要指标名称谓词。计数器需要使用速率函数而仪表指标不需要。vllm:generation_tokens_total是累积且单调递增的——对它取最大值只能得到 Pod 的生命周期总量而不是吞吐量。像vllm:num_requests_running、vllm:num_requests_waiting和vllm:kv_cache_usage_perc这样的仪表指标是瞬时值应该使用最大值或平均值。把这两类指标混淆会产生错误但看起来合理的图表这比得到空图表要糟糕得多。哪些 vLLM Prometheus 指标真正重要下面是本指南中使用的指标参考包括每个指标能告诉你什么以及值得关注的情况。名称保持 vLLM 实际输出的形式DCGM_FI_*系列来自dcgm-exporter。指标类型它告诉你什么需要关注什么vllm:time_to_first_token_seconds直方图TTFT——首个 token 开始流式传输前需要多长时间p95 高于你的交互性能标准这里为 300 msvllm:inter_token_latency_seconds直方图首个 token 之后的流式传输速度高于约 50 ms 意味着速度低于阅读速度vllm:e2e_request_latency_seconds直方图请求总耗时TTFT 保持稳定但该值上升 解码或工作负载发生变化vllm:request_queue_time_seconds直方图等待接入所花费的时间最早的饱和信号——任何持续上升都值得关注vllm:request_prefill_time_seconds直方图处理提示词所花费的时间占主要比例 预填充受限的工作负载vllm:request_decode_time_seconds直方图生成 token 所花费的时间占主要比例 内存带宽受限vllm:num_requests_running仪表当前正在解码的请求数批处理占用情况vllm:num_requests_waiting仪表等待接入的排队请求数持续非零 增加一个副本vllm:kv_cache_usage_perc仪表KV block pool 的占用率——不是 VRAM自动扩缩容触发条件约 60%vllm:prompt_tokens_totalvllm:prompt_tokens_cached_total计数器前缀缓存命中率下降意味着路由将前缀分散到了不同位置vllm:generation_tokens_total计数器输出吞吐量单位为 tokens/sec核心吞吐量指标vllm:request_prompt_tokensvllm:request_generation_tokens直方图每个请求的 token 数量两者的比值反映工作负载的形态比值发生变化意味着工作负载的特征发生了变化vllm:iteration_tokens_total直方图每次前向传播处理的 token 数量并发情况下接近 1.0 批处理失效vllm:request_success_total{finished_reason}计数器完成结果error/abort SLOlength占比 发生截断http_requests_total{status}计数器服务端请求数量捕获 4xx 和格式错误的请求而这些请求不会被vllm:*指标看到DCGM_FI_DEV_FB_USED/_FB_FREE仪表实际 VRAM仅用于容量规划——绝不要基于它设置告警DCGM_FI_DEV_GPU_UTIL仪表“有一个 kernel 正驻留”不是有效工作量的衡量指标DCGM_FI_PROF_PIPE_TENSOR_ACTIVE仪表Tensor Core 活动情况这里较低 DRAM 较高 受内存限制DCGM_FI_PROF_DRAM_ACTIVE仪表内存带宽活动情况较高 瓶颈在带宽DCGM_FI_DEV_POWER_USAGE仪表功耗单位为瓦特与吞吐量结合用于计算每瓦特 token 数步骤 1vLLM 的延迟都花在哪里分解 TTFT、预填充和解码在优化任何东西之前先将总延迟分解为排队、预填充和解码。vLLM 会分别报告这三个阶段而它们对应的解决方法完全不同。这是整个配置中最有价值的一张图表。该查询会在同一个时间窗口内以请求数量为基准对每个阶段的累计耗时取平均值——用 Prometheus 的术语来说就是rate(vllm:request_prefill_time_seconds_sum) / rate(vllm:e2e_request_latency_seconds_count)排队和解码也是如此TS metrics-vllm.prometheus-inference | WHERE timestamp NOW() - 30 minutes | STATS reqs SUM(RATE(metrics.vllm:e2e_request_latency_seconds_count)), q_s SUM(RATE(metrics.vllm:request_queue_time_seconds_sum)), pf_s SUM(RATE(metrics.vllm:request_prefill_time_seconds_sum)), dc_s SUM(RATE(metrics.vllm:request_decode_time_seconds_sum)) BY minute BUCKET(timestamp, 1 minute) | EVAL queue_ms ROUND(q_s / reqs * 1000, 2), prefill_ms ROUND(pf_s / reqs * 1000, 1), decode_ms ROUND(dc_s / reqs * 1000, 1) | KEEP minute, queue_ms, prefill_ms, decode_ms | SORT minute ASC在 A10G 上运行 8 个并发请求时minute | queue_ms | prefill_ms | decode_ms | e2e_ms | ttft_ms | inter_token_ms 22:55:00 | 0.01 | 37.98 | 1664.69 | 1715.18 | 50.81 | 16.23 22:56:00 | 0.01 | 40.99 | 1670.44 | 1723.78 | 53.50 | 16.20根据目标来解读TTFT 是 51 ms而目标是 300 ms。达标并且还有接近 6 倍的余量。感知响应速度并不是问题不管会议上当时怎么说。Token 间延迟是 16 ms即约 62 tokens/sec目标是 50 ms / 20 tok/s。文本到达的速度大约是人类阅读速度的 3 倍。队列时间是 0.01 ms。没有请求在等待准入在这个并发量下推理引擎还有充足的容量。Decode 是 1,665 ms而 Prefill 只有 38 ms——97% 的时间都花在 Decode 上。最后这一点才是真正的发现。针对 Prefill 的任何优化对这个工作负载都没有价值。Chunked Prefill、Prompt 压缩、针对长上下文更快的 Attention Kernel——这些都是实际存在的优化技术但对于这个工作负载都无关紧要因为 Prefill 只占 2% 的时间。Decode 受内存带宽限制因此真正能够带来改善的手段是量化、跨两张卡进行 Tensor Parallelism或者使用更小的模型。一张图表就排除了错误的优化方案。重点关注vllm:request_queue_time_seconds。它是整个 vLLM 指标集合中最早出现的饱和信号——队列时间会在vllm:num_requests_waiting明显出现非零之前就开始上升因为请求可能只等待几毫秒就获得准入在 Prometheus 抓取时刻甚至不会被记录为处于队列中。如果这一部分只设置一个告警就应该设置为当队列时间超过一个较小的绝对阈值时触发。第 2 步vLLM 是否在高效地使用 GPUKV Cache、Prefix Caching 和 Batch Occupancy四个指标可以回答这个问题Prefix Cache 命中率、每次迭代处理的 Token 数量、KV Cache 占用率以及运行中的请求与等待中的请求数量。延迟告诉你用户体验是否良好这些指标则告诉你为此付出的成本是否过高。TS metrics-vllm.prometheus-inference | WHERE timestamp NOW() - 30 minutes | STATS ptok SUM(RATE(metrics.vllm:prompt_tokens_total)), cached SUM(RATE(metrics.vllm:prompt_tokens_cached_total)), gen SUM(RATE(metrics.vllm:generation_tokens_total)), it_s SUM(RATE(metrics.vllm:iteration_tokens_total_sum)), it_c SUM(RATE(metrics.vllm:iteration_tokens_total_count)), running MAX(metrics.vllm:num_requests_running), waiting MAX(metrics.vllm:num_requests_waiting), kv MAX(metrics.vllm:kv_cache_usage_perc) BY minute BUCKET(timestamp, 1 minute) | EVAL prefix_cache_hit_pct ROUND(cached / ptok * 100, 1), tokens_per_iteration ROUND(it_s / it_c, 2), gen_tokens_per_sec ROUND(gen, 1), kv_cache_pct ROUND(kv * 100, 3) | KEEP minute, prefix_cache_hit_pct, tokens_per_iteration, gen_tokens_per_sec, running, waiting, kv_cache_pct | SORT minute ASCminute | prefix_cache_hit_pct | tokens_per_iteration | gen_tok/s | running | waiting | kv_cache_pct 22:55:00 | 32.5 | 10.36 | 479.1 | 8.0 | 0.0 | 0.255 22:59:00 | 32.3 | 10.34 | 480.0 | 8.0 | 0.0 | 0.12132% 的 Prefix Cache 命中率意味着所有 Prefill 工作中有大约三分之一根本不需要执行。Claims 团队的请求共享 System Prompt 和 Policy Boilerplate而 vLLM 的 Automatic Prefix Caching 能够识别这一点。这直接说明应该进一步提高Prompt 标准化程度应用在一致的前置位置放置更多共享上下文命中率就会越高每个请求的成本也就越低。如果在两个 Replica 前面放置一个简单的 Round-Robin Load Balancer这个数字很可能会大幅下降——而这恰恰是需要 llm-d 的 Cache-Aware Routing 的典型场景。tokens_per_iteration ≈ 10.3在 8 个并发请求下说明 Continuous Batching 正常工作。每次 Forward Pass 经过模型时大约同时推进 10 个 Sequence。如果在有多个请求同时运行时这个值接近 1.0就说明 Batching 出了问题你将不得不为每个用户的每个 Token 分别支付一次完整的 Model Forward 成本。这个指标证明你正在获得 vLLM 的核心价值。vllm:kv_cache_usage_perc实际测量的是什么vllm:kv_cache_usage_perc报告的是 vLLM 预分配的 KV Block Pool 的占用率而不是物理 GPU 内存的使用率。vLLM 启动时会预留一部分 VRAM由--gpu-memory-utilization控制默认值为 0.9然后从这部分预留空间中划分出一个 KV Block Pool。这个 Gauge 报告的就是这个 Pool 有多满。这就是为什么这里的数值只有0.25%。8 个并发请求每个请求大约持有 150 个 Token只占用 A10G Block Budget 中非常小的一部分。这个 Pool 很大而且这样设计是正确的。如果把同一个 Server 推到 32 个并发请求同时每个请求生成 512–1,024 个 Token这个数值就会上升到大约2.8%。但仍然很低。直觉上可能会认为这么低的数字意味着“Cache 出问题了”或者“我严重过度配置了”。这两种判断都是错误的。把这个 Gauge 当作 VRAM 使用率的代理指标是我见过的 Self-Hosted vLLM 配置中最常见的错误之一。第 6 步会准确展示这两者之间到底有多大的差距。第 3 步你的 vLLM 推理工作负载是什么形态**在进行任何调优之前先确认实际工作负载是否与你的预期一致。**这个面板可以帮助你解释某些并非由你的配置导致的延迟“回归”。minute | requests_per_min | avg_prompt_tokens | avg_generated_tokens | avg_max_tokens | gen_to_prompt_ratio 22:55:00 | 282.2 | 49.6 | 103.6 | 103.6 | 2.09 23:01:00 | 276.0 | 50.6 | 103.0 | 103.0 | 2.04生成 Token 与 Prompt Token 的比率是 2.04 —— 这个工作负载产生的内容是读取内容的两倍。这个单一比率就是第 1 步中 97% 的时间花在 Decode 上这一发现的解释而且对于任何 “总结并起草” 类工作负载都成立。如果团队之后增加一个长文档 RAG 功能Prompt 会增加到数千个 Token这个比率就会反转工作负载会变成 Prefill-Bound而正确的调优方式也会完全不同。关注这个比率可以让你在有人提交工单之前就发现工作负载的特征已经发生了变化。现在来看一个应该让你警觉的列avg_generated_tokens恰好等于avg_max_tokens。每个请求都是因为达到 Token 上限而停止的而不是因为模型已经完成了它想表达的内容。截图显示当生成长度增加到约 685 个 Token、而上限约为 777 个 Token 时同样的模式依然存在。在负载测试中这是测试生成器造成的结果。但在生产环境中这意味着用户会在句子中途被截断—— 而你现有的任何延迟指标都看不到这个问题。接下来就轮到能够捕获这个问题的指标了。第 4 步vLLM 请求是否真的成功检查finished_reason按照finished_reason标签拆分vllm:request_success_total。这是 Self-Hosted Inference 最接近应用层 SLI 的指标而且能够捕获任何延迟图表都无法发现的一种故障模式。TS metrics-vllm.prometheus-inference | WHERE timestamp NOW() - 30 minutes | STATS completions_per_min ROUND(SUM(RATE(metrics.vllm:request_success_total)) * 60, 2) BY minute BUCKET(timestamp, 1 minute), finish_reason labels.finished_reason | SORT minute ASC, finish_reason五种结果每一种在运维层面都意味着不同的问题finished_reason含义应该怎么处理stop模型自然完成生成这是你希望占比尽可能高的结果length达到max_tokens后被截断占比较高意味着用户的回答被截断——提高上限或者缩短请求abort客户端先断开连接用户放弃等待或者 Proxy 的超时时间短于生成所需时间error推理引擎发生故障你的硬 SLO 信号。应该始终保持为 0repetition输出出现退化循环这是 Sampling Parameter 问题而不是基础设施问题在小负载生成器下结果是100%length—— 这是预期的因为它请求了一个固定的上限。在截图中在混合负载下stop和length并列出现大约分别为每分钟 51 次和 32 次完成。这种比例是健康的大多数请求自行完成少数请求达到上限。这个经验具有普遍意义。error和abort是你应该触发告警的情况。但stop与length的比例是你应该每周检查的指标因为向length偏移意味着答案正在被截断而任何延迟仪表板都无法告诉你这一点。还有一个需要补上的盲点vllm:*指标只统计被引擎接受的请求。格式错误的 JSON、4xx、身份验证失败以及连接被丢弃的请求都不会进入这些指标。它们会出现在带有status和handler标签的http_requests_total中 —— 值得在这个面板旁边增加一个面板因为 “模型坏了” 的报告很多时候最终发现其实是模型前面的网关出了问题。第 5 步NVIDIA DCGM 指标显示 GPU 实际在做什么使用 NVIDIA DCGM 作为 vLLM 自身数据的独立验证依据。到目前为止所有指标都是引擎对自身运行情况的描述而 DCGM 描述的是 GPU 硬件本身的实际运行状态。FROM metrics-vllm.prometheus-inference | WHERE timestamp NOW() - 30 minutes | STATS gpu_util_pct MAX(metrics.DCGM_FI_DEV_GPU_UTIL), mem_bw_util_pct MAX(metrics.DCGM_FI_DEV_MEM_COPY_UTIL), vram_used_mib MAX(metrics.DCGM_FI_DEV_FB_USED), vram_free_mib MIN(metrics.DCGM_FI_DEV_FB_FREE), power_w ROUND(MAX(metrics.DCGM_FI_DEV_POWER_USAGE), 1), temp_c MAX(metrics.DCGM_FI_DEV_GPU_TEMP) BY minute BUCKET(timestamp, 1 minute) | SORT minute ASC→100% GPU 利用率 · 已使用 21,483 MiB / 剩余 1,352 MiB · 240 W · 73 °C100% 利用率和 94% VRAM 使用率。按照传统的解读方式这张卡已经满负荷运行是时候申请更多硬件了。但这种解读是错误的。为什么 GPU 利用率是一个容易误导 LLM 推理的指标DCGM_FI_DEV_GPU_UTIL表示的是 “GPU 上有一个 kernel 常驻运行”而不是“GPU 正在执行有用的工作”。一个调优良好的服务器和一个调优糟糕的服务器都可能显示 100%因此这个指标无法区分两者。DCGM 的 profiling counters 则可以做到这一点gr_engine_active 99.8% · tensor_active 16.6% · dram_active 80.4%把这三个指标放在一起看。GPU 的计算引擎几乎一直处于忙碌状态 —— 但真正执行矩阵计算的tensor cores 仅有 16.6% 的时间处于活跃状态而DRAM 有 80.4% 的时间处于活跃状态。这张卡并不是在进行计算而是在等待内存。这是对第 1 步仅通过延迟推断出的结论进行的独立、硬件层面的验证decode 受内存带宽限制。两个完全不同的观测工具、来自技术栈的两个不同层次却得出了同一个结论——这就是假设与发现之间的区别。这也意味着对于 LLM 推理来说GPU 利用率不再适合作为容量指标。如果你的 GPU 容量规划依赖DCGM_FI_DEV_GPU_UTIL——而大多数容量规划确实如此 —— 那么这种规划实际上没有可靠依据。第 6 步vLLM KV cache 与 GPU VRAM以及为什么两者不一致将vllm:kv_cache_usage_perc和物理 VRAM 使用率放在同一个 0–100% 的坐标轴上。它们描述的是同一块 GPU 内存但处于图表的两个极端位置而且两者都是正确的。minute | running | gen_tok_s | kv_cache_pct | vram_used_pct | gpu_util | dram_active_pct | tensor_active_pct | tokens_per_watt 00:05:00 | 31 | 1627.5 | 2.82 | 94.1 | 100.0 | 80.4 | 16.6 | 6.80KV cache 为 2.8%。VRAM 为 94.1%。vLLM 在启动时会预分配很大一部分 VRAM —— 由--gpu-memory-utilization控制默认值为 0.9 —— 然后从这部分预留空间中划分出 KV block pool。vllm:kv_cache_usage_perc报告的是pool 的占用率。DCGM 报告的是驱动程序看到的情况也就是整个预留空间无论其中是否实际存放了数据。这些指标带来的运维影响非常明确这也是整个分析过程中最实际的价值所在根据vllm:kv_cache_usage_perc和vllm:num_requests_waiting进行自动扩缩容。这两个指标描述的是接纳容量——也就是引擎当前是否还能接收另一个请求。根据 VRAM 进行容量规划。它描述的是物理空间 —— 也就是这张卡上是否还有可能再容纳第二个模型。不可能。只剩 1.3 GB。永远不要针对 VRAM 设置告警。否则即使服务器运行正常、基本处于空闲状态它也会每天凌晨 3 点把你叫醒而且会永远如此。还有tokens_per_watt—— 生成的 token 数除以功耗这里是 6.8——是真正的成本效率指标。与延迟或利用率不同它可以在不同 GPU 型号、batch 设置和量化级别之间进行比较。当你再次去找 Finance 申请第二张卡时这就是最有说服力的数字在 32 个并发请求下我们以 240 瓦的功耗持续达到 1,627 tokens/sec而如果换成 L40S这个数字会变成……vLLM 调优决策SRE 如何利用这些 Prometheus 指标六个步骤30 分钟一台服务器。根据之前设定的目标最终结论如下目标测量结果结论TTFT p95 300 ms51 ms通过拥有 6× 余量Inter-token 50 ms16 ms≈62 tok/s通过12 个并发请求下零排队8 个并发时queue_ms0.01、waiting032 个并发时仍为 0通过余量很大Error abort 0.5%0%通过对于这个部门而言当前配置是正确的而且该部门是过度配置而不是配置不足。这就是对 “感觉很慢” 这一反馈一个有数据依据、站得住脚的回答 —— 同时也会把调查方向转向应用、网关或者 prompt因为真正的问题实际上出在那里。接下来需要采取的具体措施每一项都由指标提供依据而不是凭感觉停止优化 prefill。—— 比较vllm:request_decode_time_seconds和vllm:request_prefill_time_seconds。Decode 占 97% 的时间而 prefill 只占 2%这一点已经得到两次验证。对于这个工作负载chunked prefill 和 prompt compression 都可以排除。如果需要更高的吞吐量先进行量化再考虑购买硬件。——DCGM_FI_PROF_PIPE_TENSOR_ACTIVE16.6%相比DCGM_FI_PROF_DRAM_ACTIVE80.4%。瓶颈是内存带宽而不是计算能力因此对相同模型采用 FP8 或 AWQ 版本是最值得优先尝试的单项改动它每个 token 需要移动更少的数据而这恰恰是当前受限制的资源。提高客户端的max_tokens上限。—— 查看vllm:request_success_total{finished_reason}。每一个以length而不是stop结束的请求都意味着用户的回答在中途被截断。这是目前唯一一个直接影响用户体验的实际问题。你需要提高 prompt 的max_token限制。标准化 prompt 前缀。—— 查看vllm:prompt_tokens_cached_total与vllm:prompt_tokens_total的比例目前为 32%。但这个比例应该更高更接近 70%。让更多共享的 boilerplate 以一致的前缀位置出现可以提高这个比例而且不需要额外成本。现在就设置好自动扩缩容触发条件在真正需要之前完成准备。—— 使用vllm:kv_cache_usage_perc和vllm:num_requests_waiting。当前者超过约 60%或者后者持续大于 0 时进行扩容。不要根据DCGM_FI_DEV_GPU_UTIL进行扩容——它无论如何都会保持在 100%。针对 queue time 设置告警而不是针对 VRAM。——vllm:request_queue_time_seconds是最早出现的真正饱和信号DCGM_FI_DEV_FB_USED则是一个看起来像紧急情况、实际上却可能一直保持不变的指标。当工作负载形态发生变化时重新评估。—— 查看vllm:request_generation_tokens与vllm:request_prompt_tokens的比例目前为 2.04。当 RAG 功能上线后这个比例可能反转工作负载变成 prefill-bound那么这次分析的一半都需要重新进行。这个图表会告诉你这种变化发生的那一天。值得注意的是大多数用于优化的指标其实都来自vLLM 指标而不是 GPU 指标。目前这些指标仍然处于单个服务的范围内但当vllm:num_requests_waiting持续大于 0 时就需要开始考虑使用 KServe或者使用 KEDA 和 HPA 进行自动扩缩容。而这些 vLLM 指标可以帮助你确定什么时候应该扩容以及应该让 KServe 根据什么条件进行扩容。因此可以看到理解这些指标对于调优 inference service 至关重要。Elastic Observability 可以为你提供这些能力。为什么自托管 LLM 调优是 SRE 的问题而不是 ML 的问题自托管一个开放权重模型主要不是一个 ML 问题而是一个容量和饱和度问题—— 这是 SRE 二十年来一直非常擅长的事情。真正的阻碍从来都不是技能而是遥测数据一直躺在一个没人采集的/metrics端点上并且使用了一种没有映射到实际问题的 schema。一旦把这些数据采集起来推理过程其实就是熟悉的工作只不过换了一身不熟悉的外衣在优化任何东西之前先按照阶段分解延迟队列 / 预填充 / 解码。区分逻辑资源和物理资源KV block pool ≠ VRAM并明确每个指标描述的是哪一个。永远不要相信单一来源的利用率数据——用硬件层面的数据对引擎层面的数据进行交叉验证。将每一个配置项都与一个指标关联起来再将每一个指标与明确的目标关联起来这样调优才能逐步收敛而不是不断试错。对于一个不被允许将数据发送到任何外部服务的团队来说这种差异——也就是从运行一个模型到真正运营一个模型之间的差异——就是全部的关键所在。这个部门获得了一项原本无法使用的能力而 SRE 也终于可以用数字来回答有关这个模型的问题。常见问题vllm:kv_cache_usage_perc衡量什么它衡量的是 vLLM 预分配的 KV block pool 的占用率而不是物理 GPU 内存。vLLM 在启动时会预留一定比例的 VRAM--gpu-memory-utilization默认值为 0.9然后从这部分预留内存中划分出 KV pool。在本次部署中该指标为 2.8%而同一时刻同一块 GPU 上 DCGM 报告的 VRAM 使用率为 94.1%。将它用作自动扩缩容信号将 VRAM 用于容量规划。为什么我的 vLLM 部署是解码受限decode-bound的因为工作负载生成的 token 比读取的 token 更多。比较vllm:request_decode_time_seconds和vllm:request_prefill_time_seconds并检查生成 token 与提示词 token 的比例。在本次部署中这个比例为 2.04——输出 token 是输入 token 的两倍——因此解码耗时为 1,665 ms而预填充耗时仅为 38 ms。解码受内存带宽限制因此量化、张量并行或使用更小的模型会有所帮助预填充优化则没有帮助。我应该根据 GPU 利用率对 vLLM 进行自动扩缩容吗不应该。DCGM_FI_DEV_GPU_UTIL表示 GPU 上有 kernel 常驻而不是 GPU 正在执行有用的工作——一个调优良好的服务器和一个调优糟糕的服务器都可能显示 100%。应该根据vllm:kv_cache_usage_perc大约 60%或者持续高于零的vllm:num_requests_waiting进行自动扩缩容因为这些指标描述的是引擎当前是否还能接纳新的请求。为什么我的 GPU 显示 100% 利用率但实际上并没有被充分使用因为 GPU 利用率只报告 kernel 的常驻情况。应该改为检查 DCGM 的 profiling counters在本次部署中DCGM_FI_PROF_GR_ENGINE_ACTIVE为 99.8%而DCGM_FI_PROF_PIPE_TENSOR_ACTIVE仅为 16.6%DCGM_FI_PROF_DRAM_ACTIVE为 80.4%。这种组合意味着 GPU 正在等待内存带宽而不是进行计算。如何将 vLLM 指标获取到 PrometheusvLLM 已经在其服务端口的/metrics上以 Prometheus exposition format 暴露指标——不需要 adapter 或额外的 instrumentation。让 Prometheus 的一个 scrape job 指向 vLLM Service再添加第二个 job 抓取:9400上的dcgm-exporter然后使用remote_write将数据发送到长期存储。通过 OpenTelemetry Collector 发送也可以但它会对指标名称进行标准化这使得它们更难与 vLLM 文档中的名称对应。对于交互式 LLM 应用应该将 TTFT 设定在什么水平对于流式聊天界面p95 time-to-first-token 低于 300 ms 会让用户感觉响应非常即时而低于 50 ms 的 inter-token latency约 20 tokens/sec已经快于人类阅读速度。本次部署在单块 NVIDIA A10G 上运行 3B 模型时测得 TTFT 为 51 msinter-token latency 为 16 ms留下了大约 6 倍的余量。为什么我的所有 vLLM 请求最终都是length因为它们达到了max_tokens上限而不是模型自行决定停止。按照finished_reason标签拆分vllm:request_success_total较高的length占比意味着回答在中途被截断。这一点在任何延迟指标中都看不出来因此应该定期检查stop与length的比例如果length占比不断上升就提高客户端的 token 上限。什么时候应该从普通的 vLLM Deployment 迁移到 KServe 或 llm-d当vllm:num_requests_waiting在峰值负载期间持续不为零并且你需要在没有人工干预的情况下自动增加副本时可以迁移到 KServe。当跨副本的 prefix-cache 命中率下降时可以考虑迁移到 llm-d——这通常意味着负载均衡将共享相同前缀的会话分散到了不同副本或者当预填充耗时开始明显挤占解码耗时时也可以考虑使用 llm-d。原文vLLM Prometheus metrics for self-hosted LLM tuning | Elastic Observability Labs