Prhub

#48281 [KV Offload] Add optional tier locality to FS/OBJ KV events

原始 PR 作者 Change72 合并时间 2026-07-17 15:31 文件变更 11 提交数 5 评论 11 代码增减 +326 / -10

执行摘要

为 KV 卸载事件添加可选的地域性元数据

PR 描述指出需要可选的地域性元数据来描述 KV 副本的存储位置相对于发布实例的关系,以便后续消费者(如 Dynamo 路由)能够根据 locality 做出决策。LOCAL 表示存储本地化,REMOTE 表示非本地,但不定义访问路径或延迟。

值得精读,尤其是事件元数据的向后兼容设计(omit_defaults)和 Locality 枚举的使用方式。对于需要构建 KV 缓存全局视图的团队尤其有参考价值。

讨论亮点

1. OBJ tier 是否应可配置 locality

  • orozery 提议 OBJ 硬编码为 remote,仅 FS 可配置。
  • Change72 坚持两者都配置,因为 OBJ 也可以部署在本地(如 MinIO)。最终保留两者均可配置,且都使用 Locality 枚举。

2. 移除 parse_locality 工具函数

  • orozery 建议直接在各 tier manager 中 Locality(locality) if locality is not None else None,而非独立函数。
  • Change72 接受并在 commit #4 中内联。

3. Locality 枚举放置位置

  • orozery 提议放在 vllm/v1/kv_offload/base.pyMedium 同级,Change72 采纳。

实现拆解

  1. 定义 Locality 枚举与事件扩展:在 vllm/v1/kv_offload/base.py 中新增 Locality 枚举(LOCALREMOTE),并在 OffloadingEvent 数据类中添加可选 locality: Locality | None 字段。

  2. 更新 Wire Schema:在 vllm/distributed/kv_events.pyBlockStoredBlockRemoved 结构体中添加可选的 locality: str | None = None 字段,利用 msgspecomit_defaults 确保旧负载不变。

  3. Tier Manager 接收配置:在 FileSystemTierManagerObjectStoreTierManager 的构造函数中新增 locality 参数,直接通过 Locality(locality) 验证并存储。配置来源于 SecondaryTierFactory.create_secondary_tier 的字典参数。

  4. 事件传递管道:在 OffloadingEventsTracker._take_stored_event_take_removed_event 中,从 OffloadingEvent.locality 提取字符串值(event.locality.value if event.locality is not None else None),并传递给构造的 BlockStored/BlockRemoved

  5. 测试与文档:新增多项测试验证 locality 的哈希区分、wire 兼容性(新旧两端解码一致)、配置验证(非法 locality 抛出异常)、以及 factory 配置传递。更新 docs/features/kv_offloading_usage.mdexamples/features/kv_events/kv_events_subscriber.py 示例。

此 PR 不涉及 CPU、GPU、P2P tier,不添加消费者端索引或路由功能。

文件 模块 状态 重要度
vllm/v1/kv_offload/base.py 事件模型 modified 6.35
vllm/distributed/kv_events.py 事件 Schema modified 5.87
vllm/distributed/kv_transfer/kv_connector/v1/offloading/events.py 事件追踪器 modified 6.41
vllm/v1/kv_offload/tiering/fs/manager.py 文件 Tier modified 5.97
vllm/v1/kv_offload/tiering/obj/manager.py 对象 Tier modified 5.97
tests/distributed/test_kv_cache_events.py 测试 modified 6.82
tests/v1/kv_connector/unit/offloading_connector/test_events.py 测试 modified 6.45
tests/v1/kv_offload/tiering/test_fs_tier.py 测试 modified 6.14
tests/v1/kv_offload/tiering/test_obj_tier.py 测试 modified 5.67
docs/features/kv_offloading_usage.md 文档 modified 2.02
examples/features/kv_events/kv_events_subscriber.py 示例 modified 4.24

关键符号

OffloadingEventsTracker._take_stored_event OffloadingEventsTracker._take_removed_event OffloadingEventsTracker._placeholder_stored FileSystemTierManager.__init__ ObjectStoreTierManager.__init__

关键源码片段

vllm/v1/kv_offload/base.py core-logic

定义了核心 `Locality` 枚举和 `OffloadingEvent.locality` 字段,是所有 locality 事件的源头。

# vllm/v1/kv_offload/base.py
from enum import Enum
from dataclasses import dataclass, field# 定义存储地域性,LOCAL 表示本地,REMOTE 表示远程
class Locality(Enum):
    LOCAL = "LOCAL"
    REMOTE = "REMOTE"
​
​
@dataclass
class OffloadingEvent:
    keys: list[OffloadKey]
    medium: str # 介质类型,如 "FS", "OBJ", "CPU"
    removed: bool # True 表示移除事件,False 表示存储事件
    locality: Locality | None = None # 可选地域性,由 tier 配置决定
vllm/distributed/kv_transfer/kv_connector/v1/offloading/events.py core-logic

实现 locality 从 `OffloadingEvent` 到 KV 事件的传递逻辑,包括 `_take_stored_event` 和 `_take_removed_event`。

# vllm/distributed/kv_transfer/kv_connector/v1/offloading/events.pyclass OffloadingEventsTracker:
    def _take_stored_event(self, event: OffloadingEvent) -> Iterable[KVCacheEvent]:
        # 从 OffloadingEvent 中提取 locality 字符串,若为 None 则保持 None
        locality = event.locality.value if event.locality is not None else None
        for key in event.keys:
            meta = self._pending_event_metadata.get(key)
            if meta is None:
                # 没有元数据时,仍然使用 locality 构造占位事件
                yield self._placeholder_stored(key, event.medium, locality)
                continue
            yield BlockStored(
                block_hashes=...,
                ...
                locality=locality, # 传递 locality
            )
​
    def _take_removed_event(self, event: OffloadingEvent) -> Iterable[KVCacheEvent]:
        locality = event.locality.value if event.locality is not None else None
        # ... 类似逻辑
        yield BlockRemoved(
            ...
            locality=locality,
        )

评论区精华

OBJ tier 是否应当可配置 locality 设计

orozery 建议 OBJ 硬编码为 remote,仅 FS 可配置;Change72 坚持两者都可配置,因为 OBJ 也可以本地部署。

结论:最终保留两者均可配置,且使用相同的 Locality 枚举。 · 已解决

是否移除 parse_locality 工具函数 设计

orozery 认为在各 tier manager 中直接使用 Locality(locality) 即可,无需额外函数。Change72 同意并删除。

结论:代码简化:直接在各 manager 中内联解析。 · 已解决

Locality 枚举放置位置 设计

orozery 提议放在 base.py 与 Medium 同级,Change72 接受。

结论:在 base.py 中定义 Locality。 · 已解决

风险与影响

  • 向后兼容风险:事件负载使用 omit_defaults=True,未配置 locality 时不会序列化该字段,与旧消费者兼容。验证通过 test_block_stored_locality_is_wire_compatible 等测试。
  • 配置错误风险:非法 locality 字符串(如 "local""")会在构造时抛出 ValueError,不会静默忽略。
  • 缺少消费者索引:此 PR 仅暴露元数据,并未实现基于 locality 的路由或过滤,消费者需要自行处理,可能存在不完整风险。
  • 非核心路径变更:事件管道不影响模型执行,无需精度验证。

对用户:FS 和 OBJ tier 配置中现在可以添加可选的 locality 字段,事件消费者将收到 locality 信息。未配置的用户无行为变化。
对系统:事件负载增加可选字段,网络传输量略有增加(仅在配置时)。
对团队:为未来的地域感知路由(如 #48123)奠定基础,后续还需要消费者端索引和路由逻辑。

向后兼容依赖 omit_defaults 只在 FS/OBJ 生效 缺少消费者端 routing 非法配置即时报错

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论