Prhub

#44956 [KV Connector][Mooncake] Add store group semantics

原始 PR 作者 bitborne 合并时间 2026-08-05 16:25 文件变更 2 提交数 7 评论 18 代码增减 +531 / -3

执行摘要

Mooncake 连接器新增可选组语义,按逻辑前缀归组对象

Mooncake Store 以对象粒度管理生命周期(见 RFC kvcache-ai/Mooncake#2127):同一逻辑 KV cache 条目的 K 与 V、TP/PP 各分片都是独立对象,对象级驱逐会把原本完整的组拆成 partial group,上层 KV cache 无法安全复用。Mooncake 在 kvcache-ai/Mooncake#2180 中实现了 opt-in 的 ReplicateConfig::group_ids,按 hash(group_id) 路由元数据,并在租约刷新与驱逐时联动同组成员。PR body 说明:vLLM 当前即使多个物理对象属于同一个逻辑 KV cache 条目,也独立写入;本 PR 的目标是不改通用 KV connector 接口、不改默认写路径,把相关 Mooncake 对象归入同一逻辑组,语义对齐 SGLang 侧的 sgl-project/sglang#26574。

值得精读。它展示了上游协议演进时下游框架无侵入接入的完整套路:默认关闭、运行时能力探测、旧版本回退、双重写路径覆盖。组 id 边界的设计讨论(逻辑生命周期单元 vs 物理分片、chunk_hash 保留与否)对同类分布式缓存系统有借鉴意义。建议重点阅读 worker.py 中的 _make_mooncake_group_id 与两条写路径的 group_ids 组装逻辑,以及测试中对 existing-object 过滤后对齐的回归用例。

讨论亮点
  • yokinoshitayoki(enable_group_semantics 解析):bool(extra_config.get("enable_group_semantics", False)) 会把非空字符串(如 "false")当作 True,经字符串配置路径传入时用户可能意外开启组语义,建议显式解析。作者已改为只认字符串 "true" 并补测试。
  • Dao007forever(组 id 边界):chunk_hash 是前缀哈希,单卡场景下会精确映射到该前缀的 KV,起不到归组作用;按 hash(group_id) 路由是 Redis 语义,同组对象应一起存取与驱逐;若把 vLLM group_id 放入组 id,某个 group 未命中会拖累整体。
  • yokinoshitayoki(澄清):物理对象 key 仍是权威身份,组 id 是辅助元数据;chunk_hash 必须保留,否则同模型所有 chunk 会被归为一个超大组,驱逐与租约粒度过粗;tp_rank/pp_rank 是物理分片维度,放入会让同一逻辑 chunk 的分片分散到不同组。
  • bitborne(综合结论):把 vLLM group_id、pcp_rank、dcp_rank 全部移出组 id,收敛为 vllm-mooncake-store:{model}@{chunk_hash};Dao007forever 同意。
  • bitborne(rebase 到 #45767 后):不同 cache_prefix 部署共享同一组 id 会造成跨部署租约/驱逐耦合,应在组 id 中保留前缀命名空间;Dao007forever 同意。
  • ivanium(CHANGES_REQUESTED):整体 LGTM,但还需覆盖 _maybe_offload_partial_tail() 路径,附上最小修复分支。作者 cherry-pick 并补回归测试,复审通过。

实现拆解

  1. 能力探测与配置解析(vllm/distributed/kv_transfer/kv_connector/v1/mooncake/store/worker.py):新增 _replicate_config_supports_group_ids(replicate_config_cls, replicate_config),用类属性与实例属性双重 hasattr 判断新版 Mooncake 是否暴露 group_ids;MooncakeStoreWorker.__init__ 从 extra_config 读取 enable_group_semantics,显式只把字符串 "true" 视为开启(避免 bool("false") 误判),不支持时打 warning 并沿用原写路径。
  2. 组 id 构造:新增 _make_mooncake_group_id(metadata, chunk_hash),生成 vllm-mooncake-store:{cache_prefix@}{model_name}@{chunk_hash}。组 id 只保留逻辑前缀 chunk 身份,把 tp_rank/pp_rank/pcp_rank/dcp_rank 以及 vLLM KV cache group_id 全部排除(它们仍在物理对象 key 中),保证同一逻辑 chunk 的物理分片落在同一组;chunk_hash 必须保留,否则同模型所有 chunk 会塌缩成一个大组;cache_prefix 非空时进入命名空间,隔离共享同一 Mooncake master 的不同部署。
  3. 两条写路径接入:KVCacheStoreSendingThread.__init__ 接收 enable_group_semantics 与 supports_group_ids;常规 _handle_request 与 partial-tail 专用 _maybe_offload_partial_tail 都在收集 keys 时同步构造 group_ids,在 batch_is_exist 过滤后按相同 missing 索引压缩,断言 len(group_ids) == len(keys) 后写入 self.replicate_config.group_ids 再提交;未启用时完全不动 replicate_config。
  4. 测试配套:tests/v1/kv_connector/unit/test_mooncake_store_worker.py 新增多组请求工厂 _make_multi_group_store_req、线程工厂参数化(replicate_config/enable_group_semantics/supports_group_ids)、能力探测三种形态(类属性/实例属性/均无)、组 id 内容断言(不含物理分片维度)、cache_prefix 命名空间隔离、过滤后替换 stale group_ids 的回归测试,以及 partial-tail 路径修复测试。
  5. 协作与演进:review 中 ivanium 发现 partial-tail 路径遗漏并提出修复分支,作者 cherry-pick 09e4654a5 后补测;rebase 到 #45767(cache_prefix)后同步把前缀纳入组 id 命名空间。
文件 模块 状态 重要度
vllm/distributed/kv_transfer/kv_connector/v1/mooncake/store/worker.py KV 连接器 modified 7.46
tests/v1/kv_connector/unit/test_mooncake_store_worker.py 单元测试 modified 7.24

关键符号

_replicate_config_supports_group_ids _make_mooncake_group_id KVCacheStoreSendingThread.__init__ KVCacheStoreSendingThread._handle_request KVCacheStoreSendingThread._maybe_offload_partial_tail MooncakeStoreWorker.__init__

关键源码片段

vllm/distributed/kv_transfer/kv_connector/v1/mooncake/store/worker.py core-logic

核心实现文件:新增 `_replicate_config_supports_group_ids` 能力探测与 `_make_mooncake_group_id` 组 id 构造,并把可选组语义接入常规写入与 partial-tail 两条路径;`MooncakeStoreWorker.__init__` 负责配置解析、能力探测与旧包回退。

def _replicate_config_supports_group_ids(
    replicate_config_cls: type[Any],
    replicate_config: Any,
) -> bool:
    # 新版 Mooncake 把 `group_ids` 暴露为 `ReplicateConfig` 的类属性,
    # 旧版可能只存在于实例上;两类都探测,避免把新版误判为不支持。
    if hasattr(replicate_config_cls, "group_ids"):
        return True
    return hasattr(replicate_config, "group_ids")
​
​
def _make_mooncake_group_id(metadata: KeyMetadata, chunk_hash: str) -> str:
    # Mooncake 的 `group_ids` 描述生命周期单元:一个逻辑前缀 chunk 的
    # K/V 对象与 TP/PP 分片属于同一组,因此物理分片维度
    # (`tp_rank`/`pp_rank`/`pcp_rank`/`dcp_rank` 与 vLLM 的 `group_id`)
    # 只保留在物理对象 key 中,不能进入组 id,否则同一逻辑 chunk 的
    # 各分片会被拆到不同组,失去组级租约刷新与驱逐的意义。
    # 非空 `cache_prefix` 进入命名空间,避免共享同一 Mooncake master
    # 的不同部署之间发生跨部署的租约与驱逐耦合;空前缀时格式不变。
    prefix = f"{metadata.cache_prefix}@" if metadata.cache_prefix else ""
    return f"vllm-mooncake-store:{prefix}{metadata.model_name}@{chunk_hash}"

写入路径中的 group_ids 组装与过滤对齐(KVCacheStoreSendingThread._handle_request 及 _maybe_offload_partial_tail 等价逻辑):

        # 启用组语义且 Mooncake 支持时,为每个待写入对象生成组 id;
        # 组 id 只依赖逻辑前缀 chunk,与对象 key 的物理分片无关。
        group_ids = (
            [
                _make_mooncake_group_id(
                    self.token_databases[g_idx].metadata,
                    key.rsplit("@", 1)[-1],
                )
                for key, g_idx in zip(keys, group_indices, strict=True)
            ]
            if self.enable_group_semantics and self.supports_group_ids
            else None
        )
​
        # `batch_is_exist` 过滤已存在对象后,`keys`/`sizes`/`addrs` 按
        # `missing` 索引同步压缩;`group_ids` 必须走同样的过滤,否则会把
        # 已存在对象的 stale 组 id 错误地套到新对象上,导致组语义错乱。
        if group_ids is not None:
            group_ids = [group_ids[i] for i in missing]
        # 断言保证 Mooncake 批量接口要求的 `len(group_ids) == len(keys)`。
        if group_ids is not None:
            assert len(group_ids) == len(keys)
            self.replicate_config.group_ids = group_ids
tests/v1/kv_connector/unit/test_mooncake_store_worker.py test-coverage

测试覆盖能力探测三种形态、组 id 内容与命名空间、existing-object 过滤后 group_ids 对齐、partial-tail 路径回归,是关键行为的可验证保障。

def test_partial_tail_offload_replaces_stale_group_ids_after_filtering():
    # 回归场景:`batch_is_exist` 返回 [1, 0, 0],第一个对象已存在;
    # 过滤后 keys 只剩 2 个,如果 `group_ids` 没有同步过滤,
    # 旧的 stale 组 id 会被错误保留,导致组语义错乱。
    store = MagicMock()
    store.batch_is_exist.return_value = [1, 0, 0]
    store.batch_put_from_multi_buffers.return_value = [256, 256]
    replicate_config = SimpleNamespace(group_ids=["stale"])
    thread = _make_partial_tail_send_thread(
        store,
        replicate_config=replicate_config,
        enable_group_semantics=True,
        supports_group_ids=True,
    )
​
    assert thread._maybe_offload_partial_tail(_make_partial_tail_req([1, 2, 3]))
​
    keys, _addrs, _sizes, config = store.batch_put_from_multi_buffers.call_args.args
    assert keys == [
        "test-model@tp_rank:0@pcp0@dcp0@pp_rank:0@group:0@6131",
        "test-model@tp_rank:0@pcp0@dcp0@pp_rank:0@group:0@6132",
    ]
    assert config is replicate_config
    assert config.group_ids == [
        "vllm-mooncake-store:test-model@6131",
        "vllm-mooncake-store:test-model@6132",
    ]

评论区精华

enable_group_semantics 布尔解析陷阱 设计

yokinoshitayoki 指出 `bool(extra_config.get("enable_group_semantics", False))` 会把非空字符串 `"false"` 当成 `True`,经字符串配置路径传入时用户可能意外开启组语义。

结论:改为显式解析:`str(...).strip().lower() == "true"` 才视为开启,并补充字符串 `"false"`/`"true"` 的测试。 · 已解决

group id 边界:chunk_hash 与物理分片维度 设计

Dao007forever 质疑为何包含 `chunk_hash`,并提议考虑 `tp_rank`/`pp_rank`/`pcp_rank`;yokinoshitayoki 澄清物理 key 是权威身份、组 id 是辅助元数据,`chunk_hash` 防止同模型所有 chunk 塌缩成一个大组,`tp_rank`/`pp_rank` 属于物理分片应排除;bitborne 进一步提出把 vLLM `group_id`、`pcp_rank`、`dcp_rank` 也移出,收敛为 `vllm-mooncake-store:{model}@{chunk_hash}`。

结论:采纳:组 id 只保留模型名与 chunk_hash,物理 / 结构维度全部留在对象 key。 · 已解决

cache_prefix 是否进入组 id 设计

rebase 到 #45767 后,bitborne 发现不同 `cache_prefix` 部署共享同一组 id 会造成跨部署租约刷新与驱逐耦合,提议在组 id 中保留前缀命名空间。

结论:Dao007forever 同意:非空 `cache_prefix` 时组 id 变为 `vllm-mooncake-store:{cache_prefix@}{model}@{chunk_hash}`,空前缀行为不变。 · 已解决

partial-tail 写路径遗漏 正确性

ivanium 在 CHANGES_REQUESTED 中提示 rebase 后还需覆盖 `_maybe_offload_partial_tail()` 路径,并提供了最小修复分支与提交供 cherry-pick。

结论:作者 cherry-pick `09e4654a5` 后补回归测试(`test_partial_tail_offload_replaces_stale_group_ids_after_filtering`),worker 测试 104 通过,复审批准。 · 已解决

风险与影响

  • 写路径双重改动:_handle_request 与 _maybe_offload_partial_tail 都要保持 group_ids/keys 过滤对齐,未来若重构遗漏 missing 索引压缩会静默错配;已有断言与回归测试兜底。
  • 能力探测启发式:hasattr 探测依赖 Mooncake 绑定形态,若未来 group_ids 语义变化,vLLM 会静默沿用新语义,升级 Mooncake 时需关注其兼容性说明。
  • 组级生命周期取舍:启用后任一组成员被访问会刷新整组租约、驱逐整组,可能降低部分工作负载的 L3 有效容量,这是组语义的固有设计取舍;默认关闭可规避。
  • 配置解析:只认字符串 "true",布尔 True 或 "TRUE" 会被视为关闭,文档需明确写法。
  • 兼容性:未启用用户零变化;启用后仅多一次短字符串构造,性能开销可忽略。
  • 用户侧:需要在 kv_connector_extra_config 显式开启 enable_group_semantics 并升级 Mooncake wheel;启用后减少 partial group 造成的缓存碎片(#2127 实验显示在高水位工作负载下碎片比例为 1% 量级)。
  • 系统侧:不改变物理对象 key 与寻址方式,batch_put_from_multi_buffers 调用签名不变;组 id 仅作为元数据附加到 ReplicateConfig。
  • 团队侧:vLLM 与 Mooncake、SGLang 三方协同演进,本 PR 是跨仓库协议集成的参考实现,后续可复用其能力探测与回退模式。
写路径双重改动 能力探测启发式 跨库协议耦合 opt-in 默认关闭

关联 Issue

#2127 [RFC]: Lightweight Best-Effort Group Semantics for Mooncake Store Objects
#2180 [Store] Add opt-in grouped object routing semantics
#26574 feat: add Mooncake group semantics

完整报告

参与讨论