目录

vLLM Metrics 计算详解

系列 - VLLM Internals

本文档旨在深入剖析 vLLM V1 引擎中 Metrics(指标)的底层计算逻辑。我们将剥离上层 API 的封装,直接深入 vllm/v1/metrics/stats.pyloggers.pyscheduler.py 源码,解析从 Request 到达、调度、执行到最终完成的全生命周期中,关键性能指标是如何被精确捕获、计算并暴露给 Prometheus 的。

:本文分析基于 vLLM V1 架构。

在 vLLM V1 的 Core Engine 中,指标计算并非散落在各处,而是高度内聚在 IterationStats 类及其关联的数据结构中。这种设计保证了在 Continuous Batching 的高并发场景下,依然能以 Iteration(推理步)为单位,精确追踪每个 Request 的状态。

理解指标计算的前提是理解承载状态的容器。

  • RequestStateStats: 这是每个请求的私有状态仪表盘。它跨越多次调度(Iteration),持久记录该请求的时间戳锚点和累计计数。

    • arrival_time: 请求到达 API Server 的 Wall-clock 时间。
    • queued_ts: 进入调度队列的单调时间(Monotonic Clock)。
    • scheduled_ts: 首次被调度执行的时间。
    • first_token_ts & last_token_ts: 首个和最新 Token 生成的时间。
    • num_generation_tokens: 累计生成的 Token 数。
  • IterationStats: 这是**每个推理步(Step)**的统计快照。

    • 它聚合了当前 Step 中所有活跃请求产生的增量数据(如新生成的 Token 数)。
    • 它收集了当前 Step 发生的系统事件(如抢占 PREEMPTED)。
    • 它承载了刚完成请求的最终报告 FinishedRequestStats

为了宏观理解 vLLM 指标的触发与计算流程,我们梳理了从请求到达(Arrival)到请求完成(Finished)的全生命周期时序图。

此图展示了 IterationStats 类中的三个核心方法 —— update_from_eventsupdate_from_outputupdate_from_finished_request —— 是如何在不同的系统阶段被调用的。

sequenceDiagram
    autonumber
    participant Client
    participant APIServer as API Server
    participant Scheduler
    participant EngineCore as Engine Core
    participant Stats as IterationStats (vllm/v1/metrics/stats.py)
    
    Note over Client, APIServer: 1. Request Arrival
    Client->>APIServer: Send Request
    APIServer->>APIServer: Record req_stats.arrival_time
    
    Note over Scheduler, Stats: 2. Scheduling Phase (Event Driven)
    loop Every Iteration
        Scheduler->>EngineCore: Queue / Schedule / Preempt
        EngineCore->>Stats: update_from_events(events)
        
        alt Event: QUEUED
            Stats->>Stats: req_stats.queued_ts = event.timestamp
        else Event: SCHEDULED
            Stats->>Stats: req_stats.scheduled_ts = event.timestamp (First Time Only)
        else Event: PREEMPTED
            Stats->>Stats: num_preempted_reqs += 1
        end
        
        Note over EngineCore, Stats: 3. Execution Phase (Streaming)
        EngineCore->>EngineCore: Model Execution (Prefill / Decode)
        EngineCore->>Stats: update_from_output(output, is_prefilling)
        
        alt Phase: Prefill (First Token)
            Stats->>Stats: Record TTFT (now - arrival_time)
            Stats->>Stats: req_stats.first_token_ts = now
        else Phase: Decode (Subsequent Tokens)
            Stats->>Stats: Record ITL (now - last_token_ts)
        end
        Stats->>Stats: req_stats.last_token_ts = now
        Stats->>Stats: req_stats.num_generation_tokens += new_tokens
    end
    
    Note over EngineCore, Stats: 4. Completion Phase (Final Aggregation)
    EngineCore->>Stats: update_from_finished_request(finish_reason)
    Stats->>Stats: Calculate E2E Latency (now - arrival_time)
    Stats->>Stats: Calculate Queued Time (scheduled - queued)
    Stats->>Stats: Calculate Inference Time (last - scheduled)
    Stats->>Stats: Calculate TPOT (decode / generated-1)
    
    Stats-->>APIServer: Return Metrics
    APIServer-->>Client: Response

下表是 vLLM V1 暴露的完整 Prometheus 指标清单。包含数据类型、计算逻辑源头及对应的代码位置。

代码位置说明:

  • loggers.py: 指标定义与暴露文件 (vllm/v1/metrics/loggers.py)
  • stats.py: 核心计算逻辑文件 (vllm/v1/metrics/stats.py)
  • scheduler.py: 调度器状态文件 (vllm/v1/core/sched/scheduler.py)
  • spec_decode/metrics.py: 投机采样指标 (vllm/v1/spec_decode/metrics.py)

所有延迟指标均为 Histogram 类型,记录了操作耗时的分布。

Metric Name (Prometheus)TypeDescription & Calculation LogicInternal SourceCode Location
vllm:time_to_first_token_secondsHistogramTTFT。Prefill 结束时记录:iteration_ts - arrival_timeiteration_stats.time_to_first_tokens_iterstats.py:L270
vllm:inter_token_latency_secondsHistogramITL。Decode 阶段每个 Token 生成间隔:now - last_token_tsiteration_stats.inter_token_latencies_iterstats.py:L300
vllm:e2e_request_latency_secondsHistogramE2E Latency。请求结束时记录:now - arrival_timefinished_request.e2e_latencystats.py:L336
vllm:request_queue_time_secondsHistogramQueued Time。请求在队列中等待的时间:scheduled_ts - queued_tsfinished_request.queued_timestats.py:L339
vllm:request_inference_time_secondsHistogramInference Time。请求在 GPU 上执行的总时间(含被抢占时间):last_ts - scheduled_tsfinished_request.inference_timestats.py:L351
vllm:request_prefill_time_secondsHistogramPrefill Time。预填充阶段耗时:first_ts - scheduled_tsfinished_request.prefill_timestats.py:L343
vllm:request_decode_time_secondsHistogramDecode Time。解码阶段耗时:last_ts - first_tsfinished_request.decode_timestats.py:L347
vllm:request_time_per_output_token_secondsHistogramMean TPOT。请求级平均每 Token 生成时间:decode_time / (gen_tokens - 1)finished_request.mean_time_per_output_tokenstats.py:L354
Metric Name (Prometheus)TypeDescription & Calculation LogicInternal SourceCode Location
vllm:prompt_tokens_totalCounterTotal Prompt Tokens。累计处理的 Prompt Token 总数。iteration_stats.num_prompt_tokensstats.py:L267
vllm:generation_tokens_totalCounterTotal Gen Tokens。累计生成的 Token 总数。iteration_stats.num_generation_tokensstats.py:L265
vllm:iteration_tokens_totalHistogramTokens Per Step。每个推理步处理的总 Token 数 (Prompt + Gen)。num_prompt + num_genloggers.py:L628
vllm:request_prompt_tokensHistogramPrompt Length。每个完成请求的 Prompt 长度分布。finished_request.num_prompt_tokensstats.py:L363
vllm:request_generation_tokensHistogramOutput Length。每个完成请求的生成长度分布。finished_request.num_generation_tokensstats.py:L364
vllm:request_prefill_kv_computed_tokensHistogramComputed Prefill Tokens。实际计算的 KV Token 数(排除 Cache 命中)。prompt_tokens - cached_tokensloggers.py:L873
vllm:request_params_nHistogramBest of N。请求参数 n 的分布。finished_request.nloggers.py:L648
vllm:request_params_max_tokensHistogramMax Tokens。请求参数 max_tokens 的分布。finished_request.max_tokensloggers.py:L658
vllm:request_max_num_generation_tokensHistogramMax Gen Tokens。请求实际生成的最大 Token 数(与参数限制对比)。iteration_stats.max_num_generation_tokens_iterloggers.py:L638
Metric Name (Prometheus)TypeDescription & Calculation LogicInternal SourceCode Location
vllm:num_requests_runningGaugeRunning Requests。当前在 GPU 上执行的请求数。scheduler.running (len)scheduler.py:L1504
vllm:num_requests_waitingGaugeWaiting Requests。当前在队列中等待的请求数。scheduler.waiting (len)scheduler.py:L1505
vllm:engine_sleep_stateGaugeSleep State。引擎休眠状态 (0=Awake, 1=Offloaded, 2=Discard)。engine_core.is_sleepingloggers.py:L434
vllm:kv_cache_usage_percGaugeGPU Cache Usage。KV Cache 使用率 (0.0-1.0)。block_pool.get_usage()kv_cache_manager.py:L150
vllm:num_preemptionsCounterPreemption Count。请求因资源不足被抢占的次数。iteration_stats.num_preempted_reqsstats.py:L325
vllm:request_success_totalCounterFinished Requests。按 finished_reason 分类的完成请求数。iteration_stats.finished_requestsloggers.py:L588
vllm:corrupted_requestsCounterCorrupted Requests。出现 NaN Logits 的异常请求数。iteration_stats.num_corrupted_reqsstats.py:L378
Metric Name (Prometheus)TypeDescription & Calculation LogicInternal SourceCode Location
vllm:prefix_cache_hitsCounterPrefix Cache Hits。Prefix Cache 命中的 Token 总数。prefix_cache_stats.hitsscheduler.py:L1506
vllm:prefix_cache_queriesCounterPrefix Cache Queries。Prefix Cache 查询的 Token 总数。prefix_cache_stats.queriesscheduler.py:L1506
vllm:external_prefix_cache_hitsCounterExt Prefix Hits。跨实例 KV Connector 缓存命中数。connector_cache_stats.hitsloggers.py:L519
vllm:external_prefix_cache_queriesCounterExt Prefix Queries。跨实例 KV Connector 缓存查询数。connector_cache_stats.queriesloggers.py:L507
vllm:mm_cache_hitsCounterMulti-Modal Hits。多模态(图片/视频)缓存命中数。mm_cache_stats.hitsloggers.py:L546
vllm:mm_cache_queriesCounterMulti-Modal Queries。多模态缓存查询数。mm_cache_stats.queriesloggers.py:L535

仅当启用 Speculative Decoding 时可用。

Metric Name (Prometheus)TypeDescription
vllm:spec_decode_num_draftsCounter投机采样尝试次数 (Drafts)。
vllm:spec_decode_num_draft_tokensCounter生成的 Draft Token 总数。
vllm:spec_decode_num_accepted_tokensCounter被验证通过并接受的 Token 总数。
vllm:spec_decode_num_accepted_tokens_per_posCounter按位置(Position)统计的接受 Token 数。

计算 Acceptance Rate: rate(accepted_tokens) / rate(draft_tokens)

需开启 --kv-cache-metrics-sample 采样。

Metric Name (Prometheus)TypeDescription
vllm:kv_block_lifetime_secondsHistogramKV Block 从分配到被驱逐的存活时间。
vllm:kv_block_idle_before_evict_secondsHistogramKV Block 被驱逐前的空闲(未被访问)时间。
vllm:kv_block_reuse_gap_secondsHistogram连续两次访问同一 KV Block 的时间间隔(Reuse Gap)。
Metric Name (Prometheus)TypeDescriptionInternal SourceCode Location
vllm:lora_requests_infoGaugeLoRA Info。当前运行和等待的 LoRA Adapters 信息。Labels 包含 running_lora_adapters, waiting_lora_adaptersscheduler_stats.running_lora_adaptersloggers.py:L977

vLLM 使用 Prometheus Histogram 来记录延迟和分布数据。默认的 Bucket 配置决定了观测的粒度。以下是 loggers.py 中定义的典型配置参数:

  • Request Latency Buckets: 用于 E2E, Queue, Inference, Prefill, Decode Time 等。
    • Range: 0.3s ~ 7680.0s (覆盖从毫秒级到数小时的长请求)
    • Buckets: [0.3, 0.5, 0.8, 1.0, 1.5, 2.0, 2.5, 5.0, 10.0, 15.0, 20.0, 30.0, 40.0, 50.0, 60.0, 120.0, ...]
  • TTFT Buckets: 用于 Time to First Token。
    • Range: 0.001s ~ 2560.0s (重点关注 1s 以内的亚秒级响应)
    • Buckets: [0.001, 0.005, 0.01, 0.02, 0.04, 0.06, 0.08, 0.1, 0.25, 0.5, 0.75, 1.0, ...]
  • ITL Buckets: 用于 Inter-Token Latency。
    • Range: 0.01s ~ 80.0s (重点关注 10ms - 100ms 区间)
    • Buckets: [0.01, 0.025, 0.05, 0.075, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.75, 1.0, ...]
  • Token Buckets: 用于 Token 计数 (Prompt/Gen/Iteration)。
    • Range: 1 ~ 16384 (指数增长,覆盖短文本到长上下文)
    • Buckets: [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384]

vLLM 的 Metrics 系统采用精密的双层架构,配合事件驱动机制,确保了在 Continuous Batching 高并发场景下的低开销与高精度。

  • Request-level (微观): 由 stats.py 追踪单个请求的生命周期(到达、排队、调度、生成、结束),计算 TTFT, ITL 等延迟指标。
  • System-level (宏观): 由 scheduler.py 周期性快照系统状态,监控队列长度、KV Cache 利用率等资源指标。
  • Integration (输出): 由 loggers.py 将上述内部对象映射为 Prometheus 指标格式。

以下按代码文件结构进行深度剖析。

此文件定义了核心统计类 IterationStats,它是一个短暂存在的容器,仅存活于一个推理 Step 中,负责收集该 Step 内产生的所有增量数据。

Function: update_from_events

此函数是时间锚点 (Time Anchoring) 的核心。它监听调度器发出的状态变更事件,将抽象的系统状态转换为精确的时间戳。

  • Trigger: 当 Scheduler 决定将请求放入运行队列、从队列取出或发生抢占时。
  • State Transitions: 完整涵盖了请求在调度系统中的生命周期。
def update_from_events(
    self,
    req_id: str,
    events: list["EngineCoreEvent"],
    is_prefilling: bool,
    req_stats: RequestStateStats,
    ...
):
    for event in events:
        # 1. QUEUED: 请求到达并被放入等待队列
        if event.type == EngineCoreEventType.QUEUED:
            # 记录绝对时间,用于计算总排队耗时
            req_stats.queued_ts = event.timestamp
            
        # 2. SCHEDULED: 请求首次获得 GPU 资源
        elif event.type == EngineCoreEventType.SCHEDULED:
            # 关键逻辑:仅记录第一次被调度的时间
            # 如果请求被抢占后重新调度,不会重置此时间,从而保证 Queued Time 准确
            if req_stats.scheduled_ts == 0.0:
                req_stats.scheduled_ts = event.timestamp
                
        # 3. PREEMPTED: 请求因显存不足被强制换出 (Swap out)
        elif event.type == EngineCoreEventType.PREEMPTED:
            # 累加抢占计数,用于监控系统颠簸 (Thrashing)
            self.num_preempted_reqs += 1
            # 注意:此处不修改时间戳,Preemption 耗时会计入 Inference Time

Function: update_from_output

此函数在每个推理 Step 调用,处理模型输出,是流式指标 (Streaming Metrics) 的计算源头。

  • Trigger: 模型完成一次 Forward Pass 并生成 Token 后。
  • Logic: 严格区分 Prefill (首字) 和 Decode (后续) 阶段。
def update_from_output(
    self,
    output: "EngineCoreOutput",
    engine_core_timestamp: float,
    is_prefilling: bool,
    prompt_len: int,
    req_stats: RequestStateStats,
    ...
):
    # 1. 基础计数:累加生成 Token 数
    num_new_tokens = len(output.new_token_ids)
    self.num_generation_tokens += num_new_tokens
    req_stats.num_generation_tokens += num_new_tokens

    if is_prefilling:
        # === Prefill Phase (First Token) ===
        self.num_prompt_tokens += prompt_len
        
        # [Metric: TTFT] Time to First Token
        # 计算逻辑:当前时间 (Prefill结束) - 到达时间
        ttft = self._time_since(req_stats.arrival_time)
        self.time_to_first_tokens_iter.append(ttft)
        
        # 锚定首字时间,作为后续 ITL 计算的基准
        req_stats.first_token_ts = engine_core_timestamp
    else:
        # === Decode Phase (Subsequent Tokens) ===
        # [Metric: ITL] Inter-Token Latency
        # 计算逻辑:当前时间 - 上次 Token 生成时间
        itl = engine_core_timestamp - req_stats.last_token_ts
        self.inter_token_latencies_iter.append(itl)

    # 更新“上次 Token 时间”,为下一个 Step 的 ITL 计算做准备
    req_stats.last_token_ts = engine_core_timestamp

Function: update_from_finished_request

当请求生命周期结束时调用,负责最终聚合 (Final Aggregation)

  • Trigger: 请求生成了 EOS Token、达到 Max Tokens 或被用户取消。
  • Metrics: 计算所有跨越整个生命周期的长时指标。
def update_from_finished_request(
    self,
    finish_reason: "FinishReason",
    req_stats: RequestStateStats,
    ...
):
    # 1. [Metric: E2E Latency] 端到端总耗时
    e2e_latency = self._time_since(req_stats.arrival_time)

    # 2. [Metric: Queued Time] 纯排队耗时
    # 准确性保证:得益于 SCHEDULED 事件的幂等处理,即使被抢占也不会虚高
    queued_time = req_stats.scheduled_ts - req_stats.queued_ts

    # 3. [Metric: Inference Time] GPU 执行耗时
    # 包含:Prefill 计算 + Decode 计算 + 被抢占期间的等待
    inference_time = req_stats.last_token_ts - req_stats.scheduled_ts

    # 4. [Metric: Weighted TPOT] 加权平均每 Token 耗时
    # 仅计算 Decode 阶段,排除 Prefill 的干扰
    mean_tpot = 0
    if req_stats.num_generation_tokens > 1:
        decode_time = req_stats.last_token_ts - req_stats.first_token_ts
        mean_tpot = decode_time / (req_stats.num_generation_tokens - 1)
    
    # 构造最终统计对象
    finished_req = FinishedRequestStats(
        e2e_latency=e2e_latency,
        queued_time=queued_time,
        inference_time=inference_time,
        mean_time_per_output_token=mean_tpot,
        # ...
    )
    self.finished_requests.append(finished_req)

调度器不仅决定谁跑谁等,还负责生成系统级的状态快照。

Function: make_stats

此函数在每个调度周期结束时被调用,生成 SchedulerStats 对象。它不关注单个请求,而是关注资源池 (Resource Pool) 的整体水位。

  • Trigger: scheduler.schedule() 方法执行完毕后。
def make_stats(
    self,
    spec_decoding_stats: SpecDecodingStats | None = None,
    ...
) -> SchedulerStats | None:
    
    # 1. Prefix Cache 统计 (命中率)
    # 从 Block Manager 获取当前 Block 的复用情况
    prefix_cache_stats = self.kv_cache_manager.make_prefix_cache_stats()
    
    # 2. KV Cache 驱逐事件 (Residency Metrics)
    # 收集本周期内被强制驱逐的 Block 信息
    eviction_events = self.kv_metrics_collector.drain_events() 

    # 3. 构造系统快照
    return SchedulerStats(
        # [Metric: Running/Waiting Reqs] 队列深度
        num_running_reqs=len(self.running),
        num_waiting_reqs=len(self.waiting),
        
        # [Metric: GPU Cache Usage] 显存负载
        kv_cache_usage=self.kv_cache_manager.usage,
        
        # 缓存与推测解码统计
        prefix_cache_stats=prefix_cache_stats,
        kv_cache_eviction_events=eviction_events,
        spec_decode_stats=spec_decoding_stats,
    )

这是 Metrics 系统与外部世界的桥梁。它解耦了“统计逻辑”与“展示格式”。

Function: record

此函数将 Python 对象 (SchedulerStats, IterationStats) 转换为 Prometheus 的 Gauge, Counter, Histogram。

  • Trigger: 每个 Step 结束,Metrics Manager 收集完所有数据后。
  • Logic: 清晰地将数据分流为 System Metrics 和 Request Metrics。
def record(
    self,
    scheduler_stats: SchedulerStats | None,
    iteration_stats: IterationStats | None,
    ...
):
    # === Part 1: System Level Metrics (Gauge/Counter) ===
    # 特点:反映当前瞬时状态,通常使用 Gauge
    if scheduler_stats is not None:
        # 并发度监控
        self.gauge_scheduler_running.set(scheduler_stats.num_running_reqs)
        self.gauge_scheduler_waiting.set(scheduler_stats.num_waiting_reqs)
        
        # 资源监控
        self.gauge_kv_cache_usage.set(scheduler_stats.kv_cache_usage)
        
        # 缓存命中监控 (Counter 持续累加)
        self.counter_prefix_cache_queries.inc(scheduler_stats.prefix_cache_stats.queries)
        self.counter_prefix_cache_hits.inc(scheduler_stats.prefix_cache_stats.hits)

    # === Part 2: Request Level Metrics (Histogram/Counter) ===
    # 特点:反映分布情况,数据量大,使用 Histogram 观测
    if iteration_stats is not None:
        # 吞吐量 (Counter)
        self.counter_prompt_tokens.inc(iteration_stats.num_prompt_tokens)
        self.counter_generation_tokens.inc(iteration_stats.num_generation_tokens)

        # 延迟分布 (Histogram)
        # 遍历当前 Step 产生的所有 TTFT 数据点
        for ttft in iteration_stats.time_to_first_tokens_iter:
            self.histogram_time_to_first_token.observe(ttft)
            
        # 遍历当前 Step 产生的所有 ITL 数据点
        for itl in iteration_stats.inter_token_latencies_iter:
            self.histogram_inter_token_latency.observe(itl)

        # 完成请求的最终统计 (Histogram)
        for finished_req in iteration_stats.finished_requests:
            self.histogram_e2e_time_request.observe(finished_req.e2e_latency)
            self.histogram_queue_time_request.observe(finished_req.queued_time)
            # ...