Prhub

#48866 [Metrics] Consolidate Prometheus histogram bucket defaults into a single module

原始 PR 作者 GuyStone 合并时间 2026-09-11 22:12 文件变更 3 提交数 7 评论 7 代码增减 +524 / -147

执行摘要

Prometheus 直方图桶默认值收敛到单一模块,零行为变更

issue #34370 的核心诉求是:直方图桶目前静态写死在源码里,「在高吞吐、低延迟的生产环境中,运维方需要更细的亚 300 ms 粒度、随负载画像自定义的桶分布、与既有 SLO 对齐,以及在多个服务间保持一致的桶定义」,而改桶只能打补丁并重新编译 vLLM。markmc 在该 issue 的评论中给出了分步落地方案,其中「PR 1 — Consolidate Bucket Types」正是本 PR 要实现的步骤。改造前的直接痛点是:vllm/v1/metrics/loggers.py 里 request_latency 的 21 个边界在 e2e、queue、inference、prefill、decode 五个指标上逐字重复,inter_token_latency 列表也在两个指标上重复;想做配置化就必须先有单一数据源,否则配置项无处挂载。作者也刻意划清了与既有 PR 的边界:#35952 提出同一合并方案但自 5 月起停摆且缺少测试计划;#48175 改动 request_latency 默认值而本 PR 不改任何值;#34564/#34720 走的是「每个指标一个 CLI flag」的路线,已被 changes requested 否决。

值得精读,但目标读者是 metrics/observability 维护者和准备做桶配置化的开发者,普通模型开发者可以只看结论。建议重点看三处设计决策:一是用 Literal + get_args 派生 BUCKET_FAMILY_KEYS,让「家族集合」在类型层和运行时各有一份但同源,便于测试做集合等值断言;二是快照测试的双层策略(访问器级 + 通过 PrometheusStatLogger 与 Prometheus 注册表的端到端回读),其中第二层捕捉到的正是「main 合进来一个新指标」这类跨分支漂移,非常值得在其他「单一数据源」重构中复用;三是 histogram_buckets 明确返回新副本的契约,为将来「默认值 + 用户覆盖」的合并逻辑留出了安全边界。审阅时建议继续追问两点:五个指标共享同一 list 实例是否应改为各自取副本,以及未知家族键是否应统一抛 ValueError——这两点都会在 #48867 落地时被放大。

讨论亮点

本 PR 的评审痕迹非常薄:review_comments_count 为 0,唯一的实质性人类动作是维护者 robertgshaw2-redhat 的 APPROVED(无正文),claude[bot] 只留下一句「该 PR 来自 fork——自动 review 被禁用」,其余 7 条评论全是 /ci run 与 Buildkite 触发回执。因此真正的技术讨论集中在 PR body 与 commit message 里:

PR body 划界:「零行为变更:默认桶边界与当前 main 完全一致。issue 计划中唯一会改变行为的一项(把 request_prefill_time 重新分桶到 prefill 尺度的家族)被刻意推迟,以保持本 PR 是纯重构。」——这说明作者把「可评审性」放在「一次做完」之上。

关于重复实现:「#35952 提出同一合并方案,但自 5 月起停摆且测试计划未填;本 PR 的差异在于按家族给出理由文档(这正是计划里明确要求的)。如果 @npache 更倾向,我很乐意把两者收敛。」

commit 4638a10 关于文档准确性:「该指标对每个结束的请求都会记录,因此第一个桶同时包含从未被抢占和只被抢占一次的请求;此前的措辞声称了边界并不提供的单位分辨率。」

commit 6f14d1f 关于测试护栏:「快照测试断言注册的直方图集合与家族映射精确一致,所以 main 一合并进来它就捕获了这个新指标。」——这是本 PR 少见的「测试先于代码发现问题」的实证。

未解决的疑虑:没有人类 reviewer 对「快照测试硬编码是否值得维护成本」给出明确表态,也没有人评估过把同一个 bucket 列表对象共享给五个 Histogram 的隐式契约。

实现拆解

1. 新增单一数据源模块 vllm/v1/metrics/buckets.py(+228)

  • 用 BucketFamilyKey = Literal[...] 声明八个家族键,再用 BUCKET_FAMILY_KEYS: frozenset[str] = frozenset(get_args(BucketFamilyKey)) 派生出可做成员检查的集合,使「有哪些家族」只有一处定义,测试可直接对其做等值断言。
  • 七个静态家族以模块级 tuple[float, ...] 常量承载(REQUEST_LATENCY_BUCKETS、TIME_TO_FIRST_TOKEN_BUCKETS、INTER_TOKEN_LATENCY_BUCKETS、ITERATION_TOKENS_BUCKETS、REQUEST_PARAMS_N_BUCKETS、REQUEST_NUM_PREEMPTIONS_BUCKETS、KV_CACHE_RESIDENCY_BUCKETS),每个常量配一段 docstring 说明数值范围与用途,这是 issue 计划里明确要求的「per-family rationale」。这些常量不进入 _STATIC_FAMILY_DEFAULTS 字典的那个家族是 request_tokens,因为它的边界随 max_model_len 动态生成。
  • build_buckets() 与 build_1_2_5_buckets() 从 loggers.py 迁移进来,并在迁移时把返回类型从 list[int] 修正为 list[float]——原先 [1, 2, 5] 的整型推导与 10**exponent 产生的浮点值混用,类型标注是不准确的。
  • histogram_buckets(family, max_model_len=None) 是唯一访问器:request_tokens 走 build_1_2_5_buckets(max_model_len) 且缺参时抛 ValueError,其余家族返回 list(_STATIC_FAMILY_DEFAULTS[family]) 的新副本,保证调用方可以自由修改返回值而不污染模块级常量。

2. 改造 vllm/v1/metrics/loggers.py(+23/-147)

  • 导入从 build_1_2_5_buckets 改为 histogram_buckets,并在 PrometheusStatLogger.__init__ 中、创建任何直方图之前集中解析七个局部列表变量,随后各指标直接引用变量,取代原先散落的内联字面量与重复的 build_1_2_5_buckets(max_model_len) 调用。
  • request_tokens_buckets 一次解析后被五个 token 计数类指标共享(prompt tokens、generation tokens、max_num_generation_tokens、params_max_tokens、prefill_kv_computed tokens),这正是原先重复最严重的部分。
  • kv_cache_residency_buckets 仍留在 if self.kv_cache_metrics_enabled: 分支内按需取,避免为未启用的指标做无谓计算。
  • 文件末尾重复定义的 build_buckets / build_1_2_5_buckets 被删除,loggers.py 的职责收敛为「注册并更新指标」,不再持有边界知识。

指标名与家族键的映射关系(由测试中的 METRIC_FAMILIES 固化):

家族键 覆盖的 vllm:* 指标
request_latency e2e / queue / inference / prefill / decode 五个请求阶段耗时
time_to_first_token time_to_first_token_seconds
inter_token_latency inter_token_latency_seconds、request_time_per_output_token_seconds
iteration_tokens iteration_tokens_total
request_params_n request_params_n
request_num_preemptions request_num_preemptions
request_tokens prompt / generation / max_num_generation / params_max_tokens / prefill_kv_computed tokens
kv_cache_residency kv_block_lifetime / idle_before_evict / reuse_gap

3. 新增双层快照测试 tests/v1/metrics/test_histogram_buckets.py(+273)

  • 第一层是访问器级:test_default_bucket_snapshots 参数化遍历 DEFAULT_BUCKET_SNAPSHOTS,test_request_tokens_bucket_snapshot 校验 max_model_len=32768 下的 1-2-5 序列,test_request_tokens_requires_max_model_len 校验缺参报错,test_buckets_return_fresh_copies 校验返回值是可变的新副本。
  • 第二层是端到端:test_prometheus_logger_default_buckets 用 distilbert/distilgpt2(max_model_len=1024)构造 VllmConfig + ObservabilityConfig(kv_cache_metrics=True),实例化 PrometheusStatLogger 后通过 prometheus_client.REGISTRY.collect() 按 le 标签回读每个 vllm:* 直方图的有限桶边界,与 METRIC_FAMILIES 逐项比对;finally 中调用 unregister_vllm_metrics() 清理进程级全局注册表。
  • 期望值刻意写成硬编码字面量而不是 import 常量,文件 docstring 说明「任何默认桶边界的改动都必须表现为这里的一个失败测试」,这是刻意的变更探测器(change detector)而不是普通的等价性测试。

4. 演进中的两次修正(无测试/配置/部署之外的配套改动)

  • 合并 main 后,vllm:request_num_preemptions 这个新指标带着内联边界落地,快照测试立刻以「注册集合与家族映射不一致」失败;commit 6f14d1f 因此把 REQUEST_NUM_PREEMPTIONS_BUCKETS 作为第八个家族补进来,边界保持不变。
  • commit 4638a10 收紧文档:把 single-source-of-truth 的说法限定到核心引擎直方图,说明配置了 KV connector 时 KVConnectorProm 会注册自己的直方图并保有自有边界;同时改写 PREEMPTION 家族的 docstring,如实描述第一个桶同时容纳未被抢占和被抢占一次的请求。
  • 本 PR 不含任何配置 schema、CLI flag 或部署变更——这正是它被定义为「纯重构」的前提,配置化留给 #48867。
文件 模块 状态 重要度
vllm/v1/metrics/buckets.py 指标桶定义 added 8.63
vllm/v1/metrics/loggers.py 指标导出器 modified 7.78
tests/v1/metrics/test_histogram_buckets.py 指标测试 added 7.62

关键符号

histogram_buckets build_buckets build_1_2_5_buckets PrometheusStatLogger.__init__ collect_histogram_buckets test_prometheus_logger_default_buckets test_bucket_family_keys test_buckets_return_fresh_copies

关键源码片段

vllm/v1/metrics/buckets.py dependency-wiring

新增的单一数据源模块,定义八个桶家族、家族键类型与唯一访问器 histogram_buckets(),是整个改动与后续可配置化 PR 的支点。

vllm/v1/metrics/buckets.py:家族声明、静态默认值与唯一访问器

# vllm/v1/metrics/buckets.py(新增,228 行)
from typing import Literal, get_args
​
# 家族键先用 Literal 声明,再用 get_args() 派生为可做成员检查的 frozenset:
# 「有哪些家族」因此只有一处定义,测试可以直接对 BUCKET_FAMILY_KEYS 做集合等值断言,
# 后续可配置化也能复用它校验用户输入。
BucketFamilyKey = Literal[
    "request_latency",
    "time_to_first_token",
    "inter_token_latency",
    "iteration_tokens",
    "request_params_n",
    "request_num_preemptions",
    "request_tokens",
    "kv_cache_residency",
]
BUCKET_FAMILY_KEYS: frozenset[str] = frozenset(get_args(BucketFamilyKey))
​
# 静态家族用模块级 tuple 常量承载默认边界,并配 docstring 说明覆盖范围;
# 下面只完整展示 request_latency,其余六个静态家族结构一致、仅数值不同。
# 注意 request_tokens 不在这里:它的边界随 max_model_len 动态生成。
REQUEST_LATENCY_BUCKETS: tuple[float, ...] = (
    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, 240.0, 480.0, 960.0, 1920.0, 7680.0,
)
"""亚秒级调度延迟到多小时批量请求;被 e2e、queue、inference、prefill、decode
五个请求阶段耗时直方图共享。"""
​
_STATIC_FAMILY_DEFAULTS: dict[str, tuple[float, ...]] = {
    "request_latency": REQUEST_LATENCY_BUCKETS,
    "time_to_first_token": TIME_TO_FIRST_TOKEN_BUCKETS,
    "inter_token_latency": INTER_TOKEN_LATENCY_BUCKETS,
    "iteration_tokens": ITERATION_TOKENS_BUCKETS,
    "request_params_n": REQUEST_PARAMS_N_BUCKETS,
    "request_num_preemptions": REQUEST_NUM_PREEMPTIONS_BUCKETS,
    "kv_cache_residency": KV_CACHE_RESIDENCY_BUCKETS,
}
​
​
def build_buckets(mantissa_lst: list[int], max_value: int) -> list[float]:
    """按 10 的递增幂缩放尾数,直到超出 max_value 为止。"""
    exponent = 0
    buckets: list[float] = []
    while True:
        for m in mantissa_lst:
            value = m * 10**exponent
            if value <= max_value:
                buckets.append(value)
            else:
                # 任一尾数越界即整体返回,保证结果严格递增且不超过上限
                return buckets
        exponent += 1
​
​
def build_1_2_5_buckets(max_value: int) -> list[float]:
    """1-2-5 序列:build_1_2_5_buckets(100) 得到 [1, 2, 5, 10, 20, 50, 100]。"""
    return build_buckets([1, 2, 5], max_value)
​
​
def histogram_buckets(
    family: BucketFamilyKey,
    max_model_len: int | None = None,
) -> list[float]:
    """唯一访问器:返回可被调用方自由修改的桶上界新列表。"""
    if family == "request_tokens":
        if max_model_len is None:
            # request_tokens 是唯一的动态家族,缺参属于调用方错误,显式报错而非静默降级
            raise ValueError(
                "max_model_len is required for the 'request_tokens' bucket family"
            )
        return build_1_2_5_buckets(max_model_len)
    # list(...) 做浅拷贝,避免调用方原地修改污染模块级共享常量
    return list(_STATIC_FAMILY_DEFAULTS[family])
vllm/v1/metrics/loggers.py core-logic

移除 147 行内联桶定义与重复的 build_buckets/build_1_2_5_buckets,改为一处解析、多处复用,是本次重构的落地面。

vllm/v1/metrics/loggers.py:一次解析、多处复用

# vllm/v1/metrics/loggers.py(PrometheusStatLogger.__init__ 内)
from vllm.v1.metrics.buckets import histogram_buckets
​
# 在创建任何直方图之前集中解析边界,取代原先散落在各处的内联列表:
# request_tokens 需要 max_model_len,其余家族忽略该参数。
request_tokens_buckets = histogram_buckets(
    "request_tokens", max_model_len=max_model_len
)
iteration_tokens_buckets = histogram_buckets("iteration_tokens")
request_params_n_buckets = histogram_buckets("request_params_n")
time_to_first_token_buckets = histogram_buckets("time_to_first_token")
inter_token_latency_buckets = histogram_buckets("inter_token_latency")
request_latency_buckets = histogram_buckets("request_latency")
request_num_preemptions_buckets = histogram_buckets("request_num_preemptions")
​
# 原先这里是重复五次的内联 build_1_2_5_buckets(max_model_len) 调用,
# 现在五个 token 计数指标共享同一个列表对象,这是本次重构最需要留意的形态变化。
histogram_num_prompt_tokens_request = self._histogram_cls(
    name="vllm:request_prompt_tokens",
    documentation="Number of prefill tokens processed.",
    buckets=request_tokens_buckets,
    labelnames=labelnames,
)
self.histogram_num_prompt_tokens_request = create_metric_per_engine(
    histogram_num_prompt_tokens_request, per_engine_labelvalues
)
​
# 其余指标沿用同一模式:
# vllm:iteration_tokens_total -> iteration_tokens_buckets
# vllm:request_params_n -> request_params_n_buckets
# vllm:time_to_first_token_seconds -> time_to_first_token_buckets
# vllm:inter_token_latency_seconds -> inter_token_latency_buckets
# vllm:request_time_per_output_token_seconds -> inter_token_latency_buckets
# vllm:request_num_preemptions -> request_num_preemptions_buckets
# vllm:e2e_request_latency_seconds 等 5 个 -> request_latency_buckets
​
# KV 缓存驻留相关指标仍只在开启开关时取桶,避免无谓计算:
if self.kv_cache_metrics_enabled:
    kv_cache_residency_buckets = histogram_buckets("kv_cache_residency")
​
# 文件末尾原先重复定义的 build_buckets / build_1_2_5_buckets 已删除,
# 迁入 buckets.py 时顺带把返回类型从 list[int] 修正为 list[float]。
tests/v1/metrics/test_histogram_buckets.py test-coverage

新增的双层快照测试,把默认边界与指标到家族的映射同时锁死,是防止后续无意改动桶定义的主要护栏。

tests/v1/metrics/test_histogram_buckets.py:从 Prometheus 注册表回读的端到端快照

# tests/v1/metrics/test_histogram_buckets.py(新增,273 行)
# 期望值刻意写成硬编码字面量而不是 import 常量:
# 任何默认桶边界的改动都必须表现为这里的一个失败测试。
DEFAULT_BUCKET_SNAPSHOTS: dict[str, list[float]] = {
    "request_latency": [0.3, 0.5, 0.8, ..., 7680.0],
    "time_to_first_token": [0.001, 0.005, ..., 2560.0],
    "inter_token_latency": [0.01, 0.025, ..., 80.0],
    "iteration_tokens": [1, 8, 16, ..., 16384],
    "request_params_n": [1, 2, 5, 10, 20],
    "request_num_preemptions": [1, 2, 3, 4, 5, 10, 20],
    "kv_cache_residency": [0.001, 0.002, ..., 1800],
}
​
# 指标名 -> 家族键,是「每个直方图都必须登记家族」的强制清单
METRIC_FAMILIES: dict[str, str] = {
    "vllm:e2e_request_latency_seconds": "request_latency",
    # ... 另外 4 个 request_latency 指标、2 个 inter_token_latency 指标 ...
    "vllm:request_prefill_kv_computed_tokens": "request_tokens",
    "vllm:kv_block_reuse_gap_seconds": "kv_cache_residency",
}
​
​
def collect_histogram_buckets() -> dict[str, list[float]]:
    """按指标名回读每个 vllm:* 直方图的有限桶上界。"""
    found: dict[str, list[float]] = {}
    for metric in prometheus_client.REGISTRY.collect():
        if metric.name.startswith("vllm:") and metric.type == "histogram":
            found[metric.name] = sorted(
                {
                    float(sample.labels["le"])
                    for sample in metric.samples
                    # 排除 +Inf,只保留显式配置的上界
                    if sample.name.endswith("_bucket")
                    and sample.labels["le"] != "+Inf"
                }
            )
    return found
​
​
def test_prometheus_logger_default_buckets():
    """端到端:每个核心引擎直方图都必须落在自己家族的默认边界上。
​
    配置中刻意不设置 kv_transfer_config:KV connector 会注册自己的直方图,
    它们保有自有边界,不受 METRIC_FAMILIES 覆盖。
    """
    config = build_logger_config(ObservabilityConfig(kv_cache_metrics=True))
    assert config.model_config.max_model_len == TEST_MODEL_MAX_LEN # 1024
    try:
        PrometheusStatLogger(config)
        found = collect_histogram_buckets()
        # 注册集合必须与家族映射精确相等:新增指标却忘记登记就会失败
        assert set(found) == set(METRIC_FAMILIES)
        for metric_name, family in METRIC_FAMILIES.items():
            expected = (
                REQUEST_TOKENS_SNAPSHOT_FOR_TEST_MODEL
                if family == "request_tokens"
                else DEFAULT_BUCKET_SNAPSHOTS[family]
            )
            assert found[metric_name] == [float(b) for b in expected], metric_name
    finally:
        # Prometheus 注册表是进程级全局状态,必须清理以免污染其他测试
        unregister_vllm_metrics()

评论区精华

与既有合并 PR #35952 及改值 PR #48175 的边界划分 设计

PR body 逐条说明本 PR 不重复既有工作:#35952 是同一合并方案但自 5 月起停摆、测试计划未填;#48175 改动 request_latency 默认值而本 PR 不改任何值;#34564/#34720 的「每指标一个 CLI flag」路线已被 changes requested。作者补充「如果 @npache 更倾向,我很乐意把两者收敛」,但 @npache 未回应。

结论:维护者接受本 PR 的定位(纯重构 + 按家族文档),以 APPROVED 结束;收敛 #35952 的可能性被搁置。 · 已解决

是否在本 PR 中一并重新分桶 request_prefill_time 设计

issue #34370 的分步计划里含一项会改变行为的改动:把 request_prefill_time 从 request_latency 家族换到 prefill 尺度的家族。作者选择推迟,理由是要保持本 PR 是「零行为变更」的纯重构,便于评审与回滚。

结论:行为变更被显式延后,request_prefill_time 暂时与 e2e/queue/inference/decode 共用同一套桶;这一决策被测试中的 METRIC_FAMILIES 映射如实固化。 · 已决策(延后)

快照测试为何使用硬编码字面量而非 import 常量 测试

测试文件 docstring 明确说明期望值必须是字面量,「任何默认桶边界的改动都必须表现为这里的一个失败测试」;同时补充端到端用例,通过 PrometheusStatLogger 注册后从全局注册表回读 le 标签,验证真实注册结果而非只验证访问器。

结论:采用访问器级 + 注册表级双层快照;后者在合并 main 后立刻捕获了新增的 vllm:request_num_preemptions 指标,实证了该设计的价值。 · 已解决

histogram_buckets 返回新副本的契约 正确性

因为 prometheus_client 的 Histogram 需要 bucket 上界列表,而访问器返回的是模块级共享常量,若直接返回 tuple 或共享对象,调用方一次原地修改就会污染所有后续使用。实现改为对静态家族返回 list(...) 拷贝、对 request_tokens 每次重新构造,并新增 test_buckets_return_fresh_copies 断言 second is not first。

结论:契约被测试锁定;但「多个 Histogram 共享同一返回列表」这一新形态未被测试覆盖。 · 已解决(存在残留疑虑)

single-source-of-truth 的适用范围与 KV connector 自有边界 documentation

commit 4638a10 把模块 docstring 的说法收紧到「核心引擎直方图」,说明配置了 KV connector 时会由 KVConnectorProm 注册自有直方图、保留自有边界;测试也刻意不设置 kv_transfer_config,并在 docstring 中写明这一取舍。同一提交还改写了 preemption 家族的描述,承认第一个桶同时容纳未被抢占与被抢占一次的请求,而非此前声称的单位分辨率。

结论:文档与测试的范围声明对齐,避免了「全部指标边界都在此集中」的过度承诺。 · 已解决

合并 main 后暴露的新指标未纳入家族 测试

vllm:request_num_preemptions 在分支打开后才进入 main 并内联定义边界,合入 main 后快照测试因「注册集合 != 家族映射」立即失败,作者据此新增第八个家族 REQUEST_NUM_PREEMPTIONS_BUCKETS,边界保持原样。

结论:新增家族后测试恢复通过;该事件被写进 commit message,作为「注册集合精确匹配」断言有效性的证据。 · 已解决

自动评审不可用,缺少独立技术 review other

claude[bot] 留言说明该 PR 来自 fork,「自动 review 被禁用」,需要维护者评论 @claude review 才能触发一次性评审;实际并未触发,最终只有维护者 robertgshaw2-redhat 的 APPROVED(无正文),成对符号与共享列表实例等问题没有被第三方审视。

结论:以维护者批准合并收尾;技术层面的独立复核缺位。 · 未解决

风险与影响

  1. 共享列表实例的隐式契约(低概率、影响面大):request_tokens_buckets 是同一个 list 对象,被 vllm:request_prompt_tokens、request_generation_tokens、request_max_num_generation_tokens、request_params_max_tokens、request_prefill_kv_computed_tokens 五个指标同时引用。这依赖 prometheus_client 在 Histogram 构造时把传入的 buckets 复制成新的浮点列表;一旦该行为变化或后续有人在 __init__ 之后原地 append/sort 这个列表,五个指标的桶边界会一起被改写。test_buckets_return_fresh_copies 只锁定了访问器返回新副本,没有覆盖「多个 Histogram 共享一个实例」这个新引入的形态。
  2. 错误类型不一致:histogram_buckets 对 request_tokens 缺参抛 ValueError,但传入未知家族键时会走 _STATIC_FAMILY_DEFAULTS[family] 抛 KeyError。当前只有内部调用点,风险很低;但 #48867 引入用户可配置的家族名时,必须显式用 BUCKET_FAMILY_KEYS 做校验并统一成 ValueError,否则用户配置错误会以 KeyError 的形式冒出。
  3. 快照测试的维护与冲突成本:273 行测试里大部分是硬编码数值,任何有意的桶边界调整都必须同步改测试。例如 #48175 想改 request_latency 默认值时会直接红在这份快照上——这是设计意图,但会让 metrics 相关的多 PR 并行变得更容易冲突;本 PR 自己就因为 main 上新增的 request_num_preemptions 反复 merge 并返工。
  4. 测试对全局状态与具体模型的依赖:端到端用例依赖 prometheus_client 的进程级 REGISTRY 和 distilbert/distilgpt2 的默认 max_model_len == 1024。注册表若被其他测试污染、或模型注册表默认值变化,会以难以归因的方式失败;finally: unregister_vllm_metrics() 只降低了污染别人的风险,没有隔离被别人污染的路径。
  5. 回归风险本身很低:所有边界逐值与 main 一致(PR body 明确声明,并被快照测试固化),对 Prometheus 抓取端、histogram_quantile 查询和既有 SLO 面板是零影响;不改配置 schema、不改 CLI、不改序列化格式。
  • 对运维方/监控侧:零影响。发布后所有 vllm:* 直方图的 bucket 边界与 main 完全相同,已配置的 Grafana 面板、告警规则和 SLO 查询不需要任何调整。
  • 对 vLLM 开发者:vllm/v1/metrics/loggers.py 减少 147 行内联数据,新增指标时只需在 buckets.py 选择一个家族键,边界知识集中在一处;代价是任何默认边界的调整都必须同步更新 tests/v1/metrics/test_histogram_buckets.py 的硬编码快照,属于「摩擦换安全」的显式取舍。
  • 对后续功能:本 PR 是 #48867「桶可配置化」的直接前置。BucketFamilyKey / BUCKET_FAMILY_KEYS / histogram_buckets() 三层接口已经把「家族集合」与「默认值解析」解耦,配置化只需替换 histogram_buckets 的数据来源,调用点无需再改。
  • 对团队协作:新增的 METRIC_FAMILIES 映射 + 「注册集合必须精确等于映射集合」的断言,会让任何新增 vllm:* 直方图却忘记登记家族的 PR 立刻失败——这是跨 PR 的强制登记机制,正负面作用并存。
共享桶列表实例的隐式契约 快照测试硬编码带来维护与冲突成本 未知家族键抛 KeyError 而非 ValueError 测试依赖进程级 Prometheus 注册表 仅有维护者批准,缺少独立技术 review

关联 Issue

#34370 [Feature]: Make Prometheus histogram buckets configurable

完整报告

参与讨论