Prhub

#45958 [KV Offloading] Add basic offloading metrics

原始 PR 作者 Srinivasoo7 合并时间 2026-07-07 14:26 文件变更 10 提交数 14 评论 29 代码增减 +320 / -16

执行摘要

KV 卸载添加基础监控指标:延迟、分配失败、分配大小

为 KV offloading 提供可观测性,使运维人员能够监控 offload 查找延迟、分配失败和分配大小,以便调优容量和性能。

建议精读该 PR,特别是 scheduler.py 中异步延迟的埋点模式和 metrics.py 中 bucket 的设计。设计模式可复用于 vllm 其他子系统的指标添加。测试代码也值得参考,尤其是使用 request_runner fixture 对 scheduler 行为进行集成验证的方式。

讨论亮点
  • 审核者 orozery 建议将单一的 lookup delay 拆分为同步/异步两个独立指标,并分别设定不同的 bucket 范围(sync: 0.00001~1s,async: 0.0001~10s)。
  • RequestOffloadState 中新增 deferred_lookup_start_time 字段代替全局 dict,用于跟踪异步延迟起始点。
  • orozery 要求将 increase_counter 的默认增量值设为 1,简化调用。
  • 对于 CPU 分配大小,orozery 指出不应观察空分配(len(keys_to_store)==0),并应在 eviction 之前记录分配大小,以便捕获 eviction 失败时的值。
  • 代码中 self._connector_stats 的 lazy 初始化被改为始终非空,并通过 is_empty() 判断是否丢弃空 stats。

实现拆解

  1. 指标定义:在 vllm/distributed/.../offloading/metrics.py 中新增 _ConnectorMetricName 类,定义三个 connector 侧指标名;在 get_connector_metric_definitions() 中添加对应的 OffloadingHistogramMetadataOffloadingCounterMetadata,并设定合适的 bucket 分桶。
  2. scheduler 侧埋点:在 scheduler.py 中引入 time.monotonic(),在 get_num_new_matched_tokens()_lookup 调用前后记录同步延迟并 observe 到 _connector_stats;在 _build_store_jobs 中当 prepare_store 返回 None 时增加分配失败计数器;新增 _maybe_observe_lookup_async_delay 方法,在异步 lookup 第一次返回 None 时记录起始时间,在后续 resolve 或请求结束时观察异步延迟。
  3. CPU manager 侧埋点:在 cpu/manager.pyprepare_store 中记录每次分配的 block 数量到 allocation_sizes_in_current_batch,并在 get_stats() 中 observe 到 CPU_ALLOCATION_SIZE 直方图。
  4. 默认 counter 值:将 increase_counter 的默认增量参数设为 1,简化调用。
  5. 测试配套:在 test_scheduler.py 新增 3 个测试用例验证 allocation_failure、lookup_sync_delay、lookup_async_delay 的计数与求和;在 test_manager.py 新增 3 个测试用例验证 CPU allocation size 直方图,包括正常、分配失败和驱逐失败路径;在 test_factory.py 新增验证 CPU_ALLOCATION_SIZE 直方图定义;在 test_metrics.py 新增验证 connector metric 的 bucket 值。
  6. 测试工具更新:在 offloading_connector/utils.py 中新增 _record_kv_connector_stats 辅助函数,确保测试 runner 能正确收集 stats 输出。
文件 模块 状态 重要度
vllm/distributed/kv_transfer/kv_connector/v1/offloading/scheduler.py 调度器 modified 7.59
vllm/distributed/kv_transfer/kv_connector/v1/offloading/metrics.py 指标定义 modified 6.98
tests/v1/kv_connector/unit/offloading_connector/test_scheduler.py 测试 modified 7.3
tests/v1/kv_offload/cpu/test_manager.py 测试 modified 6.73
tests/v1/kv_offload/test_factory.py 测试 modified 5.91
tests/v1/kv_connector/unit/offloading_connector/test_metrics.py 测试 modified 5.5
vllm/v1/kv_offload/cpu/manager.py CPU 管理 modified 5.71
vllm/v1/kv_offload/cpu/spec.py 配置 modified 5.34
vllm/v1/kv_offload/cpu/common.py 常量 modified 4.35
tests/v1/kv_connector/unit/offloading_connector/utils.py 测试 modified 4.88

关键符号

_maybe_observe_lookup_async_delay get_num_new_matched_tokens _build_store_jobs get_connector_metric_definitions prepare_store get_stats increase_counter

关键源码片段

vllm/distributed/kv_transfer/kv_connector/v1/offloading/scheduler.py core-logic

核心变更文件:新增同步 / 异步 lookup 延迟埋点、分配失败计数器、`_maybe_observe_lookup_async_delay` 方法,以及 `deferred_lookup_start_time` 字段。

# vllm/distributed/kv_transfer/kv_connector/v1/offloading/scheduler.py# 在 RequestOffloadState 中新增字段,用于跟踪异步延迟起始时间
@dataclass(slots=True)
class RequestOffloadState:
    # ... 其他字段 ...
    # time.monotonic() of this request's first deferred offload lookup;
    # None once consumed (observed) or while no lookup is pending.
    deferred_lookup_start_time: float | None = None
​
​
def _maybe_observe_lookup_async_delay(
    self, req_status: RequestOffloadState
) -> None:
    start_time = req_status.deferred_lookup_start_time
    if start_time is None:
        return
    req_status.deferred_lookup_start_time = None
    self._connector_stats.observe_histogram(
        _ConnectorMetricName.LOOKUP_ASYNC_DELAY,
        time.monotonic() - start_time,
    )# 在 get_num_new_matched_tokens 中埋点同步延迟和异步延迟触发
lookup_start = time.monotonic()
num_hit_tokens = self._lookup(req_status)
self._connector_stats.observe_histogram(
    _ConnectorMetricName.LOOKUP_SYNC_DELAY,
    time.monotonic() - lookup_start,
)
if num_hit_tokens is None:
    if req_status.deferred_lookup_start_time is None:
        req_status.deferred_lookup_start_time = lookup_start
else:
    self._maybe_observe_lookup_async_delay(req_status)# 在 _build_store_jobs 中增加分配失败计数器
if store_output is None:
    self._connector_stats.increase_counter(
        _ConnectorMetricName.ALLOCATION_FAILURE
    )
vllm/distributed/kv_transfer/kv_connector/v1/offloading/metrics.py core-logic

新增 `_ConnectorMetricName` 类定义指标名,并添加到 `get_connector_metric_definitions()` 中,包含详细 bucket 配置。同时修改 `increase_counter` 默认参数。

# vllm/distributed/kv_transfer/kv_connector/v1/offloading/metrics.pyclass _ConnectorMetricName:
    """Connector-side metrics emitted by scheduler-side offloading code."""
    LOOKUP_SYNC_DELAY = "vllm:kv_offload_lookup_sync_delay_seconds"
    LOOKUP_ASYNC_DELAY = "vllm:kv_offload_lookup_async_delay_seconds"
    ALLOCATION_FAILURE = "vllm:kv_offload_allocation_failure"# 在 get_connector_metric_definitions 中添加:
def get_connector_metric_definitions() -> dict[str, OffloadingMetricMetadata]:
    definitions = {
        # 已有 transfer 指标 ...
        _ConnectorMetricName.LOOKUP_SYNC_DELAY: OffloadingHistogramMetadata(
            documentation=(
                "Histogram of the time spent in a single offload lookup call, "
                "in seconds."
            ),
            buckets=(
                0.00001, 0.00005, 0.0001, 0.0005, 0.001, 0.005,
                0.01, 0.05, 0.1, 0.5, 1,
            ),
        ),
        _ConnectorMetricName.LOOKUP_ASYNC_DELAY: OffloadingHistogramMetadata(
            documentation=(
                "Histogram of time between a request's offload lookup first "
                "deferring and the following lookup resolving, or request "
                "finish, in seconds."
            ),
            buckets=(
                0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05,
                0.1, 0.5, 1, 5, 10,
            ),
        ),
        _ConnectorMetricName.ALLOCATION_FAILURE: OffloadingCounterMetadata(
            documentation=(
                "Number of KV offload store allocation attempts that failed."
            ),
        ),
    }
    return definitions
tests/v1/kv_connector/unit/offloading_connector/test_scheduler.py test-coverage

新增 3 个测试用例,验证 scheduler 侧三个指标的记录和 reduce 正确性,引入 `_reduce_kv_connector_stats` 辅助函数。

# tests/v1/kv_connector/unit/offloading_connector/test_scheduler.pydef _reduce_kv_connector_stats(runner):
    reduced: dict[str, int | float] = {}
    for payload in runner.kv_connector_stats:
        stats = (
            payload
            if hasattr(payload, "reduce")
            else OffloadingConnectorStats(data=payload)
        )
        for key, value in stats.reduce().items():
            reduced[key] = reduced.get(key, 0) + value
    return reduceddef test_scheduler_reports_allocation_failure(request_runner):
    runner = request_runner(block_size=4, num_gpu_blocks=10, async_scheduling=False)
    runner.new_request(token_ids=[0] * 4)
    # 让 prepare_store 返回 None 模拟分配失败
    runner.manager.prepare_store.side_effect = lambda keys, req_context: None
    runner.run(decoded_tokens=[EOS_TOKEN_ID])
    reduced = _reduce_kv_connector_stats(runner)
    assert reduced[_ConnectorMetricName.ALLOCATION_FAILURE] == 1def test_scheduler_reports_lookup_sync_delay(request_runner):
    runner = request_runner(block_size=4, num_gpu_blocks=10, async_scheduling=False)
    runner.new_request(token_ids=[1] * 4)
    runner.manager.prepare_store.side_effect = lambda keys, req_context: (
        generate_store_output([])
    )
    runner.run(decoded_tokens=[EOS_TOKEN_ID])
    reduced = _reduce_kv_connector_stats(runner)
    assert reduced[f"{_ConnectorMetricName.LOOKUP_SYNC_DELAY}_count"] == 1
    assert reduced[f"{_ConnectorMetricName.LOOKUP_SYNC_DELAY}_sum"] > 0

评论区精华

将 lookup delay 拆分为同步 / 异步两个指标 设计

orozery 提议将一个单一的 lookup delay 指标拆分为同步(`_lookup` 调用时间)和异步(从第一次 defer 到 resolve 的时间)两个 histogram,并分别设置不同的 bucket 值。

结论:接受建议,实现两个 metrics:`LOOKUP_SYNC_DELAY` 和 `LOOKUP_ASYNC_DELAY`。 · 已解决

使用 RequestOffloadState 字段代替全局 dict 跟踪异步延迟 设计

orozery 建议在 `RequestOffloadState` 中添加 `deferred_lookup_start_time` 字段代替 `self._lookup_started_at: dict[ReqId, float]`,避免全局 dict 管理。

结论:采用该方案,新增 `deferred_lookup_start_time` 字段,并在 consume 后置 None。 · 已解决

defualt increase_counter 增量值设为 1 设计

orozery 指出 `increase_counter` 调用时频繁传入 1,建议改为默认参数 `counter_increase_value: int | float = 1`。

结论:修改方法签名,默认增量为 1。 · 已解决

CPU allocation size 应在 eviction 之前记录 设计

orozery 指出分配大小记录的位置应该放在 eviction 尝试之前,以便捕获 eviction 失败时的分配大小。

结论:将 `allocation_sizes_in_current_batch.append(len(keys_to_store))` 移到 eviction 逻辑之前。 · 已解决

历史 bucket 值微调 设计

orozery 两次建议在同步延迟和异步延迟的 bucket 中添加更小的边界值(0.00001、0.00005 等),以捕捉微秒级延迟。

结论:接受建议,最终 bucket 列表包含 0.00001、0.00005 等。 · 已解决

风险与影响

主要风险在 scheduler 的 hot path 中新增 time.monotonic() 调用,但该调用开销极低(纳秒级),对性能影响可忽略。stats 对象的生命周期通过 is_empty() 守卫,不会在无观测时产生不必要的传输。CPU manager 中 allocation_sizes_in_current_batch 列表在每次 get_stats() 后清空,但没有保护防止并发访问(当前为单线程模型,安全)。测试充分覆盖了正常/失败/驱逐路径,降低回归风险。

用户/运维:新 metric 默认启用,通过 Prometheus 或 stats logger 暴露,运维可直接监控 KV offloading 延迟和分配失败,无需额外配置。系统:增加少量内存用于观测数据,但每次 get_stats() 后即释放,内存占用可控。团队:该 PR 为后续更精细的 offload 监控(如按 tier 细分)打下基础。影响范围限定在 kv-offloading 子系统,不影响其他核心路径。

hot-path 增加 time.monotonic() 调用 stats 对象空值处理 测试覆盖充分

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论