指标#
确保 v1 LLM 引擎公开 v0 中可用指标的超集。
目标#
实现 v0 和 v1 之间指标的对等性。
首要用例是通过 Prometheus 访问这些指标,因为这是我们期望在生产环境中使用的。
提供日志记录支持 - 即将指标打印到信息日志 - 以用于更临时的测试、调试、开发和探索性用例。
背景#
vLLM 中的指标可以分为以下几类
服务器级别指标:这些是跟踪 LLM 引擎状态和性能的全局指标。这些通常在 Prometheus 中作为 Gauge 或 Counter 公开。
请求级别指标:这些是跟踪单个请求特征(例如大小和时间)的指标。这些通常在 Prometheus 中作为 Histogram 公开,并且通常是 SRE 监控 vLLM 将跟踪的 SLO。
心智模型是“服务器级别指标”解释了为什么“请求级别指标”是这样的。
v0 指标#
在 v0 中,以下指标通过 Prometheus 兼容的 /metrics
端点使用 vllm:
前缀公开
vllm:num_requests_running
(Gauge)vllm:num_requests_swapped
(Gauge)vllm:num_requests_waiting
(Gauge)vllm:gpu_cache_usage_perc
(Gauge)vllm:cpu_cache_usage_perc
(Gauge)vllm:gpu_prefix_cache_hit_rate
(Gauge)vllm:cpu_prefix_cache_hit_rate
(Gauge)vllm:prompt_tokens_total
(Counter)vllm:generation_tokens_total
(Counter)vllm:request_success_total
(Counter)vllm:request_prompt_tokens
(Histogram)vllm:request_generation_tokens
(Histogram)vllm:time_to_first_token_seconds
(Histogram)vllm:time_per_output_token_seconds
(Histogram)vllm:e2e_request_latency_seconds
(Histogram)vllm:request_queue_time_seconds
(Histogram)vllm:request_inference_time_seconds
(Histogram)vllm:request_prefill_time_seconds
(Histogram)vllm:request_decode_time_seconds
(Histogram)vllm:request_max_num_generation_tokens
(Histogram)vllm:num_preemptions_total
(Counter)vllm:cache_config_info
(Gauge)vllm:lora_requests_info
(Gauge)vllm:tokens_total
(Counter)vllm:iteration_tokens_total
(Histogram)vllm:time_in_queue_requests
(Histogram)vllm:model_forward_time_milliseconds
(Histogram)vllm:model_execute_time_milliseconds
(Histogram)vllm:request_params_n
(Histogram)vllm:request_params_max_tokens
(Histogram)vllm:spec_decode_draft_acceptance_rate
(Gauge)vllm:spec_decode_efficiency
(Gauge)vllm:spec_decode_num_accepted_tokens_total
(Counter)vllm:spec_decode_num_draft_tokens_total
(Counter)vllm:spec_decode_num_emitted_tokens_total
(Counter)
这些文档记录在 推理和服务 -> 生产指标 下。
Grafana 仪表板#
vLLM 还提供 一个参考示例,说明如何使用 Prometheus 收集和存储这些指标,并使用 Grafana 仪表板将其可视化。
Grafana 仪表板中公开的指标子集让我们了解哪些指标尤为重要
vllm:e2e_request_latency_seconds_bucket
- 以秒为单位测量的端到端请求延迟vllm:prompt_tokens_total
- Prompt Tokens/秒vllm:generation_tokens_total
- Generation Tokens/秒vllm:time_per_output_token_seconds
- 每输出令牌延迟 (TPOT),以秒为单位。vllm:time_to_first_token_seconds
- 首个令牌时间 (TTFT) 延迟,以秒为单位。vllm:num_requests_running
(以及_swapped
和_waiting
) - 处于 RUNNING、WAITING 和 SWAPPED 状态的请求数vllm:gpu_cache_usage_perc
- vLLM 使用的缓存块百分比。vllm:request_prompt_tokens
- 请求 Prompt 长度vllm:request_generation_tokens
- 请求生成长度vllm:request_success_total
- 按完成原因(生成 EOS 令牌或达到最大序列长度)统计的已完成请求数vllm:request_queue_time_seconds
- 队列时间vllm:request_prefill_time_seconds
- 请求预填充时间vllm:request_decode_time_seconds
- 请求解码时间vllm:request_max_num_generation_tokens
- 序列组中的最大生成令牌数
请参阅 添加此仪表板的 PR,了解关于此处所做选择的有趣且有用的背景信息。
Prometheus 客户端库#
Prometheus 支持最初是 使用 aioprometheus 库 添加的,但很快切换到 prometheus_client。相关原理在两个链接的 PR 中进行了讨论。
多进程模式#
在 v0 中,指标在引擎核心进程中收集,我们使用多进程模式使它们在 API 服务器进程中可用。请参阅 Pull Request #7279。
内置 Python/进程指标#
以下指标默认情况下由 prometheus_client
支持,但当使用多进程模式时,它们不会公开
python_gc_objects_collected_total
python_gc_objects_uncollectable_total
python_gc_collections_total
python_info
process_virtual_memory_bytes
process_resident_memory_bytes
process_start_time_seconds
process_cpu_seconds_total
process_open_fds
process_max_fds
这很重要,因为如果我们在 v1 中放弃多进程模式,我们将重新获得这些指标。但是,如果它们不聚合构成 vLLM 实例的所有进程的这些统计信息,那么它们的相关性就值得怀疑。
v0 PR 和问题#
作为背景,以下是一些添加 v0 指标的相关 PR
另请注意 “甚至更好的可观测性” 功能,例如 详细的路线图。
v1 设计#
v1 PR#
作为背景,以下是与 v1 指标问题 Issue #10582 相关的 v1 PR
指标收集#
在 v1 中,我们希望将计算和开销移出引擎核心进程,以最大限度地减少每次前向传递之间的时间。
V1 EngineCore 设计的总体思路是
EngineCore 是内循环。性能在这里最为关键
AsyncLLM 是外循环。这与 GPU 执行(理想情况下)重叠,因此如果可能,任何“开销”都应该放在这里。因此,如果可能,AsyncLLM.output_handler_loop 是指标簿记的理想位置。
我们将通过在前端 API 服务器中收集指标来实现这一点,并将这些指标基于我们可以从引擎核心进程返回到前端的 EngineCoreOutputs
中获取的信息。
间隔计算#
我们的许多指标都是请求处理中各种事件之间的时间间隔。最佳实践是使用基于“单调时间”(time.monotonic()
) 而不是“挂钟时间”(time.time()
) 的时间戳来计算间隔,因为前者不受系统时钟更改(例如来自 NTP)的影响。
同样重要的是要注意,单调时钟在进程之间有所不同 - 每个进程都有自己的参考点。因此,比较来自不同进程的单调时间戳是没有意义的。
因此,为了计算间隔,我们必须比较来自同一进程的两个单调时间戳。
调度器统计#
引擎核心进程将从调度器收集一些关键统计信息 - 例如,在上次调度器传递后已调度或等待的请求数 - 并将这些统计信息包含在 EngineCoreOutputs
中。
引擎核心事件#
引擎核心还将记录某些每个请求事件的时间戳,以便前端可以计算这些事件之间的间隔。
事件包括
QUEUED
- 请求被引擎核心接收并添加到调度器队列时。SCHEDULED
- 请求首次被调度执行时。PREEMPTED
- 请求已被放回等待队列中,以便为其他请求完成腾出空间。它将在未来重新调度并重新开始其预填充阶段。NEW_TOKENS
- 当EngineCoreOutput
中包含的输出被生成时。由于这对于给定迭代中的所有请求都是通用的,因此我们在EngineCoreOutputs
上使用单个时间戳来记录此事件。
计算的间隔包括
队列间隔 - 在
QUEUED
和最近的SCHEDULED
之间。预填充间隔 - 在最近的
SCHEDULED
和随后的第一个NEW_TOKENS
之间。解码间隔 - 在第一个(在最近的
SCHEDULED
之后)和最后一个NEW_TOKENS
之间。推理间隔 - 在最近的
SCHEDULED
和最后一个NEW_TOKENS
之间。令牌间间隔 - 在连续的
NEW_TOKENS
之间。
换句话说

我们探索了让前端使用前端可见的事件时序来计算这些间隔的可能性。但是,前端无法看到 QUEUED
和 SCHEDULED
事件的时序,并且由于我们需要根据来自同一进程的单调时间戳计算间隔……我们需要引擎核心记录所有这些事件的时间戳。
间隔计算与抢占#
当解码期间发生抢占时,由于任何已生成的令牌都将被重用,因此我们将抢占视为影响令牌间、解码和推理间隔。

当预填充期间发生抢占时(假设这种事件是可能的),我们将抢占视为影响首个令牌时间和预填充间隔。

前端统计收集#
当前端处理单个 EngineCoreOutputs
时 - 即来自单个引擎核心迭代的输出 - 它会收集与该迭代相关的各种统计信息
在此迭代中生成的新令牌总数。
在此迭代中完成的预填充处理的 Prompt 令牌总数。
在此迭代中调度的任何请求的队列间隔。
在此迭代中完成预填充的任何请求的预填充间隔。
对于此迭代中包含的所有请求,令牌间间隔(每输出令牌时间,TPOT)。
对于在此迭代中完成预填充的任何请求的首个令牌时间 (TTFT)。但是,我们根据请求首次被前端接收的时间 (
arrival_time
) 计算此间隔,以便考虑输入处理时间。
对于在给定迭代中完成的任何请求,我们还会记录
推理和解码间隔 - 相对于如上所述的调度和首个令牌事件。
端到端延迟 - 前端
arrival_time
与前端接收到最终令牌之间的间隔。
指标发布 - 日志记录#
LoggingStatLogger
指标发布器每 5 秒输出一条日志 INFO
消息,其中包含一些关键指标
当前正在运行/等待的请求数
当前 GPU 缓存使用率
过去 5 秒内每秒处理的 Prompt 令牌数
过去 5 秒内每秒生成的新令牌数
最近 1k kv-cache 块查询的前缀缓存命中率
指标发布 - Prometheus#
PrometheusStatLogger
指标发布器通过 /metrics
HTTP 端点以 Prometheus 兼容的格式提供指标。然后可以配置 Prometheus 实例轮询此端点(例如,每秒一次)并将值记录在其时序数据库中。Prometheus 通常通过 Grafana 使用,从而可以将这些指标随时间推移绘制成图表。
Prometheus 支持以下指标类型
Counter:随时间增加的值,永远不会减少,并且通常在 vLLM 实例重新启动时重置为零。例如,实例生命周期内生成的令牌数。
Gauge:上下波动的值,例如当前计划执行的请求数。
Histogram:记录在 buckets 中的指标样本计数。例如,TTFT 小于 1 毫秒、小于 5 毫秒、小于 10 毫秒、小于 20 毫秒等等的请求数。
Prometheus 指标也可以被标记,从而可以根据匹配的标签组合指标。在 vLLM 中,我们为每个指标添加一个 model_name
标签,其中包括该实例服务的模型名称。
示例输出
$ curl http://0.0.0.0:8000/metrics
# HELP vllm:num_requests_running Number of requests in model execution batches.
# TYPE vllm:num_requests_running gauge
vllm:num_requests_running{model_name="meta-llama/Llama-3.1-8B-Instruct"} 8.0
...
# HELP vllm:generation_tokens_total Number of generation tokens processed.
# TYPE vllm:generation_tokens_total counter
vllm:generation_tokens_total{model_name="meta-llama/Llama-3.1-8B-Instruct"} 27453.0
...
# HELP vllm:request_success_total Count of successfully processed requests.
# TYPE vllm:request_success_total counter
vllm:request_success_total{finished_reason="stop",model_name="meta-llama/Llama-3.1-8B-Instruct"} 1.0
vllm:request_success_total{finished_reason="length",model_name="meta-llama/Llama-3.1-8B-Instruct"} 131.0
vllm:request_success_total{finished_reason="abort",model_name="meta-llama/Llama-3.1-8B-Instruct"} 0.0
...
# HELP vllm:time_to_first_token_seconds Histogram of time to first token in seconds.
# TYPE vllm:time_to_first_token_seconds histogram
vllm:time_to_first_token_seconds_bucket{le="0.001",model_name="meta-llama/Llama-3.1-8B-Instruct"} 0.0
vllm:time_to_first_token_seconds_bucket{le="0.005",model_name="meta-llama/Llama-3.1-8B-Instruct"} 0.0
vllm:time_to_first_token_seconds_bucket{le="0.01",model_name="meta-llama/Llama-3.1-8B-Instruct"} 0.0
vllm:time_to_first_token_seconds_bucket{le="0.02",model_name="meta-llama/Llama-3.1-8B-Instruct"} 13.0
vllm:time_to_first_token_seconds_bucket{le="0.04",model_name="meta-llama/Llama-3.1-8B-Instruct"} 97.0
vllm:time_to_first_token_seconds_bucket{le="0.06",model_name="meta-llama/Llama-3.1-8B-Instruct"} 123.0
vllm:time_to_first_token_seconds_bucket{le="0.08",model_name="meta-llama/Llama-3.1-8B-Instruct"} 138.0
vllm:time_to_first_token_seconds_bucket{le="0.1",model_name="meta-llama/Llama-3.1-8B-Instruct"} 140.0
vllm:time_to_first_token_seconds_count{model_name="meta-llama/Llama-3.1-8B-Instruct"} 140.0
注意 - 选择对于广泛用例的用户最有用的直方图 buckets 并非易事,并且需要随着时间的推移进行改进。
缓存配置信息#
prometheus_client
支持 Info 指标,它等效于 Gauge
,其值永久设置为 1,但通过标签公开有趣的键/值对信息。这用于有关实例的信息,这些信息不会更改 - 因此只需要在启动时观察 - 并允许在 Prometheus 中跨实例进行比较。
我们将此概念用于 vllm:cache_config_info
指标
# HELP vllm:cache_config_info Information of the LLMEngine CacheConfig
# TYPE vllm:cache_config_info gauge
vllm:cache_config_info{block_size="16",cache_dtype="auto",calculate_kv_scales="False",cpu_offload_gb="0",enable_prefix_caching="False",gpu_memory_utilization="0.9",...} 1.0
但是,prometheus_client
从未在多进程模式下支持 Info 指标 - 原因不明。我们只是使用设置为 1 的 Gauge
指标和 multiprocess_mode="mostrecent"
代替。
LoRA 指标#
vllm:lora_requests_info
Gauge
有些类似,只是该值是当前的挂钟时间,并且每次迭代都会更新。
使用的标签名称是
running_lora_adapters
:每个适配器正在使用该适配器运行的请求数计数,格式为逗号分隔的字符串。waiting_lora_adapters
:类似,只是计数等待调度的请求。max_lora
- 静态的“单个批次中的最大 LoRA 数”配置。
在逗号分隔的字符串中编码多个适配器的正在运行/等待计数似乎相当不明智 - 我们可以使用标签来区分每个适配器的计数。这应该重新审视。
请注意,使用了 multiprocess_mode="livemostrecent"
- 使用最新的指标,但仅来自当前正在运行的进程。
这是在 Pull Request #9477 中添加的,并且 至少有一位已知用户。如果我们重新审视此设计并弃用旧指标,我们应该通过也在 v0 中进行更改并要求此项目迁移到新指标来减少重大弃用期的需要。
前缀缓存指标#
关于 Issue #10582 中添加前缀缓存指标的讨论产生了一些有趣的观点,这些观点可能与我们未来处理指标的方式有关。
每次查询前缀缓存时,我们都会记录查询的块数以及缓存中存在的查询块数(即命中数)。
然而,我们感兴趣的指标是命中率 - 即每次查询的命中数。
在日志记录的情况下,我们期望通过计算最近 1k 次查询的固定间隔内的命中率,能够最好地为用户服务(目前间隔固定为最近 1k 次查询)。
但在 Prometheus 的情况下,我们应该利用 Prometheus 的时间序列特性,并允许用户计算他们选择的时间间隔内的命中率。例如,一个 PromQL 查询可以计算过去 5 分钟的命中率
rate(cache_query_hit[5m]) / rate(cache_query_total[5m])
为了实现这一点,我们应该将查询和命中数记录为 Prometheus 中的计数器,而不是将命中率记录为仪表盘。
已弃用的指标#
如何弃用#
不应轻率对待指标的弃用。用户可能不会注意到某个指标已被弃用,并且当该指标突然(从他们的角度来看)被移除时可能会感到非常不便,即使有等效的指标可供他们使用。
例如,请参阅 vllm:avg_prompt_throughput_toks_per_s
是如何被 弃用(在代码中添加了注释),移除,然后被 用户注意到 的。
总的来说
我们应该谨慎对待指标的弃用,尤其因为很难预测用户的影响。
我们应该在 `/metrics` 输出中包含的帮助字符串中加入显眼的弃用通知。
我们应该在面向用户的文档和发行说明中列出已弃用的指标。
我们应该考虑将已弃用的指标隐藏在 CLI 参数之后,以便在删除它们之前,为管理员提供一段时间的 紧急出口。
未实现 - vllm:tokens_total
#
由 Pull Request #4464 添加,但显然从未实现。可以直接移除。
重复 - 队列时间#
vllm:time_in_queue_requests
Histogram 指标由 Pull Request #9659 添加,其计算方式为
self.metrics.first_scheduled_time = now
self.metrics.time_in_queue = now - self.metrics.arrival_time
两周后,Pull Request #4464 添加了 vllm:request_queue_time_seconds
,导致我们有了
if seq_group.is_finished():
if (seq_group.metrics.first_scheduled_time is not None and
seq_group.metrics.first_token_time is not None):
time_queue_requests.append(
seq_group.metrics.first_scheduled_time -
seq_group.metrics.arrival_time)
...
if seq_group.metrics.time_in_queue is not None:
time_in_queue_requests.append(
seq_group.metrics.time_in_queue)
这似乎是重复的,应该移除其中一个。后者被 Grafana 仪表板使用,因此我们应该弃用或从 v0 中移除前者。
前缀缓存命中率#
见上文 - 我们现在公开“queries”和“hits”计数器,而不是“hit rate”仪表盘。
KV 缓存卸载#
两个 v0 指标与 v1 中不再相关的“swapped”抢占模式有关
vllm:num_requests_swapped
vllm:cpu_cache_usage_perc
在这种模式下,当请求被抢占时(例如,为了在 KV 缓存中为完成其他请求腾出空间),我们会将 kv 缓存块换出到 CPU 内存。这也称为“KV 缓存卸载”,并通过 --swap-space
和 --preemption-mode
进行配置。
在 v0 中,vLLM 长期以来一直支持束搜索。SequenceGroup 封装了 N 个序列的概念,这些序列都共享相同的 prompt kv 块。这实现了请求之间的 KV 缓存块共享,以及用于分支的写时复制。CPU 交换旨在用于这些类似束搜索的情况。
后来,引入了前缀缓存的概念,它允许隐式共享 KV 缓存块。事实证明,这比 CPU 交换更好,因为块可以按需缓慢地逐出,并且可以重新计算被逐出的 prompt 部分。
SequenceGroup 在 V1 中被移除,尽管“并行采样” (n>1
) 仍需要一个替代方案。束搜索已移出核心 (在 V0 中)。对于一个非常不常见的功能,存在很多复杂的代码。
在 V1 中,由于前缀缓存更好(零开销)并且默认开启,抢占和重新计算策略应该会更好地工作。
未来工作#
并行采样#
一些 v0 指标仅在“并行采样”的上下文中相关。这是指请求中的 n
参数用于请求来自同一 prompt 的多个完成。
作为在 Pull Request #10980 中添加并行采样支持的一部分,我们还应该添加这些指标。
vllm:request_params_n
(Histogram)
观察每个已完成请求的 ‘n’ 参数的值。
vllm:request_max_num_generation_tokens
(Histogram)
观察每个已完成序列组中所有序列的最大输出长度。在没有并行采样的情况下,这等同于 vllm:request_generation_tokens
。
推测解码#
一些 v0 指标特定于“推测解码”。这是指我们使用更快、近似的方法或模型生成候选 tokens,然后使用更大的模型验证这些 tokens。
vllm:spec_decode_draft_acceptance_rate
(Gauge)vllm:spec_decode_efficiency
(Gauge)vllm:spec_decode_num_accepted_tokens_total
(Counter)vllm:spec_decode_num_draft_tokens_total
(Counter)vllm:spec_decode_num_emitted_tokens_total
(Counter)
有一个 PR 正在审查中 (Pull Request #12193) 以将 “prompt lookup (ngram)” 推测解码添加到 v1。其他技术将随之而来。我们应该在此上下文中重新审视 v0 指标。
注意 - 我们可能应该像处理前缀缓存命中率一样,将接受率作为单独的接受和草稿计数器公开。效率可能也需要类似的处理。
自动伸缩和负载均衡#
我们的指标的一个常见用例是支持 vLLM 实例的自动伸缩。
有关来自 Kubernetes Serving Working Group 的相关讨论,请参阅
这是一个非同小可的话题。考虑 Rob 的这条评论
我认为这个指标应该专注于尝试估计最大并发量,该并发量将导致平均请求长度 > 每秒查询次数……因为这才是真正会使服务器“饱和”的原因。
一个明确的目标是,我们应该公开检测此饱和点所需的指标,以便管理员可以基于这些指标实现自动伸缩规则。但是,为了做到这一点,我们需要清楚地了解管理员(和自动化监控系统)应该如何判断实例正在接近饱和
为了有效进行自动伸缩,如何识别模型服务器计算的饱和点(在更高的请求速率下我们无法获得更高的吞吐量,但开始产生额外延迟的拐点)?
指标命名#
我们命名指标的方法可能值得重新审视
在指标名称中使用冒号似乎与 “冒号保留给用户定义的记录规则” 相悖
我们的大多数指标都遵循以单位结尾的约定,但并非所有指标都这样做。
我们的一些指标名称以
_total
结尾
If there is a suffix of `_total` on the metric name, it will be removed. When
exposing the time series for counter, a `_total` suffix will be added. This is
for compatibility between OpenMetrics and the Prometheus text format, as OpenMetrics
requires the `_total` suffix.
添加更多指标#
关于新指标的想法层出不穷
来自其他项目的示例,例如 TGI
从特定用例中提出的建议,例如上面的 Kubernetes 自动伸缩主题
可能从标准化工作中产生的建议,例如 OpenTelemetry 用于 Gen AI 的语义约定。
我们应该对添加新指标的方法持谨慎态度。虽然指标通常相对容易添加
但它们可能难以移除 - 请参阅上面关于弃用的部分。
启用它们可能会对性能产生有意义的影响。而且,除非指标可以默认启用并在生产环境中使用,否则它们通常用途非常有限。
它们会对项目的开发和维护产生影响。添加到 v0 的每个指标都使 v1 的工作更加耗时,并且可能并非所有指标都证明了对其维护的持续投入是合理的。
追踪 - OpenTelemetry#
指标提供了系统性能和健康状况随时间变化的聚合视图。另一方面,追踪跟踪单个请求在不同服务和组件之间的移动。两者都属于更广义的“可观测性”范畴。
v0 支持 OpenTelemetry 追踪
由 Pull Request #4687 添加
通过
--oltp-traces-endpoint
和--collect-detailed-traces
进行配置
OpenTelemetry 有一个 Gen AI 工作组。
由于指标本身就是一个足够大的话题,我们将单独处理 v1 中关于追踪的话题。
OpenTelemetry 模型前向传播与执行时间#
在 v0 中,我们有以下两个指标
vllm:model_forward_time_milliseconds
(Histogram) - 当此请求在批处理中时,在模型前向传播中花费的时间。vllm:model_execute_time_milliseconds
(Histogram) - 在模型执行函数中花费的时间。这将包括模型前向传播、跨 worker 的块/同步、cpu-gpu 同步时间和采样时间。
这些指标仅在启用 OpenTelemetry 追踪并且使用 --collect-detailed-traces=all/model/worker
时启用。此选项的文档说明
为指定的“模块”收集详细的追踪信息。这涉及到可能代价高昂和/或阻塞的操作,因此可能会对性能产生影响。
这些指标由 Pull Request #7089 添加,并在 OpenTelemetry 追踪中显示为
-> gen_ai.latency.time_in_scheduler: Double(0.017550230026245117)
-> gen_ai.latency.time_in_model_forward: Double(3.151565277099609)
-> gen_ai.latency.time_in_model_execute: Double(3.6468167304992676)
我们已经有了 inference_time
和 decode_time
指标,所以问题是更高分辨率的时序是否有足够常见的用例来证明开销是合理的。
由于我们将单独处理 OpenTelemetry 支持的问题,我们将把这些特定指标包含在该主题下。