Prhub

#57033 [Rust Frontend][Metrics] Add per-request preemption histogram

原始 PR 作者 reidliu41 合并时间 2026-09-16 10:51 文件变更 3 提交数 2 评论 6 代码增减 +33 / -0

执行摘要

Rust 前端新增 per-request 抢占直方图,对齐 Python 指标

PR body 明确指出对齐目标:"The Rust frontend reports the total number of preemption events through vllm:num_preemptions, but does not report their per-request distribution. Align the Rust frontend with the Python frontend"。关联 issue #44772 进一步说明痛点:Request.num_preemptions 在调度器上递增但从未暴露到 per-request 指标,只有引擎级累计总量 "giving no visibility into how frequently individual requests are preempted",运维无法判断哪些请求频繁被抢占。

值得快速精读(约 30 分钟)。它是 Rust 前端移植 Python per-request 指标的标准样板,重点看 observe_events → record_finished 的收口模式、零值样本处理、以及与 Python 桶常量的同步维护方式。若团队正在推进 v1 Rust 前端迁移,建议将本 PR 的指标对齐模式纳入后续迁移 checklist。

讨论亮点

该 PR 没有实质性的 review 评论(review_comments_count 为 0),主要信号来自自动审核与最终批准:ChatGPT Codex 审核结论为 "Didn't find any major issues. More of your lovely PRs please.",维护者 BugenZhao 直接批准并回复 "Thanks @reidliu41"。最有价值的设计讨论沉淀在关联 issue #44772 中:为何用 histogram 而非 counter——per-request 的值有界且随请求重置,histogram 允许查询 p50/p99 并按严重程度分桶;实现沿用 Python 前端 #49984 的 [1, 2, 3, 4, 5, 10, 20] 桶。

实现拆解

  1. 指标定义与注册(rust/src/metrics/src/request.rs):新增常量 REQUEST_NUM_PREEMPTIONS_BUCKETS(7 个桶 [1, 2, 3, 4, 5, 10, 20],与 Python 前端 #49984 一致);新增构造函数 request_num_preemptions_histogram();RequestMetrics 结构体新增 request_num_preemptions: HistogramFamily 字段,并在 RequestMetrics::register 中注册 vllm:request_num_preemptions,描述为 "Histogram of the number of times a request was preempted.",最后加入 Self 返回体。

  2. 请求级状态跟踪(rust/src/llm/src/request_metrics.rs):RequestMetricsTracker 新增 num_preemptions: u64 字段(初始 0);RequestMetricHandles 新增 request_num_preemptions: HistogramMetric 句柄;observe_events 在 EngineCoreEventType::Preempted 分支中除了原有引擎级 counter 递增外,再对请求级计数执行 self.num_preemptions += 1;record_finished 在请求终止时 observe(self.num_preemptions as f64),保证零抢占请求也记录一次样本;resolve_request_metric_handles 通过 metrics.request_num_preemptions.get_or_create_owned(&labels) 解析句柄。

  3. 测试配套(rust/src/llm/tests/generate.rs):两个既有集成测试补断言。generate_records_request_metrics_in_prometheus_output(engine=4,发生 1 次抢占)断言 vllm:request_num_preemptions_sum 为 1.0、_count 为 1;dropping_stream_records_abort_terminal_request_metrics(abort 场景,0 次抢占)断言 _sum 为 0.0、_count 为 1,显式覆盖"零抢占请求也记录"的行为。

整个变更无配置、schema 或部署配套改动,是纯增量观测能力。

文件 模块 状态 重要度
rust/src/metrics/src/request.rs 指标定义 modified 6.27
rust/src/llm/src/request_metrics.rs 请求指标 modified 5.19
rust/src/llm/tests/generate.rs 生成测试 modified 4.33

关键符号

request_num_preemptions_histogram RequestMetrics::register RequestMetricsTracker::observe_events RequestMetricsTracker::record_finished resolve_request_metric_handles

关键源码片段

rust/src/metrics/src/request.rs core-logic

指标面核心改动:新增直方图桶常量、构造函数、结构体字段与注册逻辑,是 vllm:request_num_preemptions 指标的定义入口。

// 每请求抢占次数的直方图分桶,与 Python 前端 vllm:request_num_preemptions 保持一致。
//
// 与 token 数等采用 1-2-5 递增的桶不同,这里使用接近均匀的边界:
// 抢占次数是小整数,运维可以直观区分被抢占 1 次、2 次、3 次的请求分布,
// 便于按严重程度分级告警。
const REQUEST_NUM_PREEMPTIONS_BUCKETS: [f64; 7] = [1.0, 2.0, 3.0, 4.0, 5.0, 10.0, 20.0];
​
fn request_num_preemptions_histogram() -> Histogram {
    // 由 prometheus_client 的 Histogram::new 依据桶边界构造直方图。
    Histogram::new(REQUEST_NUM_PREEMPTIONS_BUCKETS.iter().copied())
}
​
// 在 RequestMetrics::register 中注册该指标族,与其它 request 生命周期指标并列。
// 使用 new_with_constructor 延迟构造,避免为每个标签组合预先创建直方图。
let request_num_preemptions =
    Family::new_with_constructor(request_num_preemptions_histogram as fn() -> Histogram);
registry.register(
    "vllm:request_num_preemptions",
    "Histogram of the number of times a request was preempted.",
    request_num_preemptions.clone(),
);
rust/src/llm/src/request_metrics.rs core-logic

请求生命周期跟踪核心:新增 num_preemptions 状态、指标句柄、事件累计与终止时观测逻辑,是数据流的主链路。

/// 从 engine-core 事件流中维护请求私有的抢占计数。
///
/// 引擎级 counter 展示的是累计总量,这里额外保存 per-request 计数,
/// 供请求结束时观测进直方图,从而支撑 p50/p99 级别的分布查询。
fn observe_events(&mut self, events: &[EngineCoreEvent]) {
    for event in events {
        match event.r#type {
            EngineCoreEventType::Queued => {
                self.queued_ts = event.timestamp;
            }
            EngineCoreEventType::Scheduled => {
                // 只记录首次调度时间,避免重复调度覆盖已修正的时间戳。
                if self.scheduled_ts == 0.0 {
                    self.scheduled_ts = event.timestamp;
                }
            }
            EngineCoreEventType::Preempted => {
                // per-request 计数与引擎级 counter 同步累加,
                // 保证 vllm:num_preemptions 与 vllm:request_num_preemptions 口径一致。
                self.num_preemptions += 1;
                self.handles.num_preemptions.inc();
            }
        }
    }
}
​
/// 请求终止(含 abort)时收口所有生命周期指标。
pub(crate) fn record_finished(&self, received_at: f64, finish_reason: FinishReason) {
    let e2e_latency_seconds = received_at - self.arrival_time;
​
    // 每个请求恰好观测一次,包括从未被抢占的请求(num_preemptions == 0),
    // 保证直方图 _count 等于已结束请求总数,分位数计算不缺分母。
    self.handles.request_num_preemptions.observe(self.num_preemptions as f64);
​
    // 其余过程指标(队列时间、prefill/decode 时间、e2e 延迟等)统一在此观测,
    // 与 Python 前端 FinishedRequestStats 的收口逻辑一一对应。
    self.handles.e2e_request_latency_seconds.observe(e2e_latency_seconds);
    self.record_request_success(finish_reason);
}

评论区精华

为什么 per-request 抢占用 histogram 而不是 counter 设计

关联 issue #44772 中说明:Request.num_preemptions 在调度器递增但从未暴露,vllm:num_preemptions 只有引擎级累计总量,无法观察单请求抢占分布。设计上选 histogram 而非 counter,是因为该值按请求维度有界、随请求重置,可支撑 p50/p99 分位数查询与严重程度分桶。

结论:采用 histogram,桶为 [1, 2, 3, 4, 5, 10, 20],并在请求终止(含 abort 与 0 次抢占)时记录一次样本。 · 已解决

风险与影响

  1. 事件流时序依赖:num_preemptions 完全依赖 EngineCoreEventType::Preempted 事件在 record_finished 之前到达;若事件乱序或丢失,直方图会漏计。不过该值与引擎级 counter 同源同路径,不会出现两指标不一致,属既有行为约束。
  2. 双前端口径同步:Rust 与 Python 各自维护同一组桶常量,未来 Python 侧若调整桶边界,Rust 侧需要手动跟进,存在口径漂移风险。
  3. 零值样本语义:abort 与 0 次抢占请求也计入 _count,直接拿 _sum / _count 算均值会被大量 0 稀释产生误导,应基于 _bucket 计算分位数——这是有意设计,但监控告警配置时需注意。
  4. 性能与兼容性:每请求多一次直方图 observe,开销可忽略;纯新增指标族,无 breaking change,不影响现有指标。
  • 用户/运维:可在 Prometheus 中查询 vllm:request_num_preemptions 的 p50/p99 每请求抢占分布,为容量规划与抢占调优提供依据;指标名与 Python 前端一致,现有 dashboard 可无缝复用。
  • 系统开销:每个 model/engine 标签组合新增一个 7 桶直方图,内存与 CPU 开销可忽略。
  • 团队/Rust 前端:Rust 与 Python 前端的指标面进一步对齐,降低双前端行为漂移;后续新增 per-request 指标可复用本 PR 确立的"事件流累计 + record_finished 收口"模板。
事件流时序依赖 Python/Rust 指标口径同步 零值样本易误读

关联 Issue

#44772 [Feature]: [Metrics] Add per-request preemption count to Prometheus histogram

完整报告

参与讨论