Prhub

#51689 [KV Connector][Offloading] Certify attention-only hybrids in the canonical portability gate

原始 PR 作者 Etelis 合并时间 2026-08-31 20:53 文件变更 2 提交数 9 评论 8 代码增减 +91 / -91

执行摘要

KV offload 门禁逐组认证,解锁 SWA 混合模型跨 TP 复用

PR body 明确说明了动机链条:现有 canonical portability gate 只接受“单一 full-attention 组”,任何含 sliding-window 的混合注意力模型(gpt-oss-20b/120b、Gemma-class interleaved-SWA)都无法获得 TP-agnostic 的 KV offload 命名空间,跨拓扑复用被阻断。核心洞察是 Hybrid attention models (full + sliding-window groups) certify group by group: every layer's bytes are head-shard fragments regardless of its window, so the storage namespace can collapse across topologies——无论窗口类型,每层字节都是 head-shard 片段,命名空间可以跨拓扑折叠。安全性设计保持不变:认证仍在注册时逐层对照 live tensor strides 进行,_validate_canonical_refs 在静态 flag 声称可移植而层无法交付时 fail closed。目标是 Unlocks TP-agnostic offload for sliding-window hybrids。

值得精读。这是 KV offload canonical 布局功能线的关键收口 PR,有三个亮点:

  1. 设计模式可借鉴——“静态保守镜像 + 注册时逐层运行时认证 + fail closed”的双层保险,用静态判定决定 namespace、用运行时认证兜底正确性。
  2. 逐组认证(group-wise certification)是对混合拓扑的简洁泛化,比旧门禁“全模型单组”覆盖广得多,且新增的 per-group head 精确匹配避免了假阳性。
  3. 建议结合 vllm/v1/kv_offload 下的 _layer_mapping 与 _validate_canonical_refs 对照阅读,理解静态门禁与运行时认证的对应关系;做多副本或 PD 分离部署时可借鉴 body 中三臂对照的 E2E 验证方法(命名空间比对 + completion 字节一致性)。
讨论亮点

该 PR 没有 inline 代码 review 评论,评审通过来自 bnellnm 与 orozery 的 approve。值得提炼的讨论与迭代痕迹分布在 commit 演进、issue 评论与 E2E 验证中:

  • bnellnm:LGTM Do you mind adding ready? —— 作者随后补上 ready 标签并请求 mgoin 协助 review。
  • commit 10515616("Tighten the canonical gate's per-group head check and dedupe gate tests"):将门禁从模型级整除收紧为每个组 num_kv_heads 恰好等于 max(1, total_kv_heads // tp_size),堵住 head 分片与实际 group 不匹配的假阳性。
  • commit 271061736("Keep the gate's original branch shape to minimize stacked-PR churn"):为减少与堆叠 PR #51690 / #51691 的合并冲突,刻意保留原 gate 分支形状——这是堆叠开发中降低冲突面的明确设计取舍。
  • commit da28b36 / a9e4b11:连续两次把散落的 gate 测试折叠为 pytest.param 参数化表,并引入 _groups 构造器统一构造 KVCacheGroupSpec 列表。
  • body 中的 E2E 三臂对照表:TP1 写入的命名空间 …_583489c9fd7b 被 TP2 进程命中 3840/3901 token,且 completion 与 main 分支 TP2 全新计算结果字节一致,验证的是“正确的复用”而非“仅仅是存在”。

实现拆解

  1. 变更入口:vllm/distributed/kv_transfer/kv_connector/v1/offloading/config.py 的 build_offloading_config。直接布局(非 canonical)下的保守判定 is_parallelism_agnostic 保持不变;只有当 canonical_layout=True 且直接判定不通过时,才进入放宽分支。

  2. 门禁重构为逐组认证:base 版本只对 single_group_spec 做单一分支判定,head 版本把判定拆成独立的 spec_certifiable(spec: KVCacheSpec) 闭包,并改为遍历 kv_cache_config.kv_cache_groups 逐组执行 all(...)。规则有四层:非 AttentionSpec 组直接拒绝(Mamba 等只能推导 exact-topology 的 opaque 映射);per-token-head 量化拒绝;严格类型命中的 MLAAttentionSpec 需满足 tokens_per_state == 1 且 real_page_size_bytes % block_size == 0(canonical 布局把 TP 复制的 latent 去重为一份,所以可认证);SlidingWindowMLASpec 拒绝,唯有 FullAttentionSpec 与 SlidingWindowSpec 进入最终检查——组内 num_kv_heads 必须恰好等于 max(1, total_kv_heads // tp_size),比 base 的模型级整除检查更严格,可同时表达 head sharding 与 GQA 复制两种方向。

  3. 聚合判定与并行约束:all(spec_certifiable(...)) 之外仍沿用原 gate 的并行约束——decode_context_parallel_size == 1、prefill_context_parallel_size == 1、world_size == tp_size。commit 271061736 特意保留原 gate 的分支形状,以减少与堆叠 PR #51690 / #51691 的合并冲突。

  4. 测试配套:tests/v1/kv_connector/unit/offloading_connector/test_config.py 中 _parallelism_agnostic 辅助函数新增 canonical 与 v2 参数;抽出 _SWA_SPEC、_SWA_MLA_SPEC 常量与 _groups 构造器;原 test_canonical_layout_widens_parallelism_agnostic_to_mla 折叠为参数化 test_canonical_layout_gate,覆盖四象限——mla-latent(可认证)、full + SWA 混合(可认证)、SWA 版 MLA(拒绝)、Mamba 混合(拒绝);test_canonical_layout_certifies_v2_model_runner 同步简化。单测 57 项全部通过,无需 GPU。

  5. 附带修复:首个 commit(981bcf9)同时将 canonical format id 的解析提前到 config build 阶段一次性完成,因为 scheduler 侧 FileMapper 在 multiproc executor 下运行于 vLLM 配置上下文之外,原先动态解析 KV cache layout 会崩溃;具体改动位置不在展示窗口内,依据 commit message 确认。

文件 模块 状态 重要度
vllm/distributed/kv_transfer/kv_connector/v1/offloading/config.py 卸载配置 modified 6.9
tests/v1/kv_connector/unit/offloading_connector/test_config.py 卸载配置 modified 6.91

关键符号

build_offloading_config spec_certifiable test_canonical_layout_gate _parallelism_agnostic

关键源码片段

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

核心源码文件:canonical 可移植性门禁从单一 full-attention 组放宽为逐组认证的关键逻辑所在,新增 spec_certifiable 闭包作为 _layer_mapping 运行时检查的静态镜像。

# 直接布局(非 canonical)的可移植判定保持原有保守规则;
# 仅当请求 canonical_layout 且直接判定不通过时,才进入逐组认证分支。
if canonical_layout and not is_parallelism_agnostic:
    tp_size = parallel_config.tensor_parallel_size
    total_kv_heads = vllm_config.model_config.get_total_num_kv_heads()
​
    def spec_certifiable(spec: KVCacheSpec) -> bool:
        """保守的静态镜像,与 _layer_mapping 在注册时的逐层检查保持一致。"""
        # 非 Attention 组(如 Mamba 隐藏态缓存)只能推导出 exact-topology 的 opaque 映射。
        if not isinstance(spec, AttentionSpec):
            return False
        # 按 token 头量化的缩放因子会破坏字节布局,任何拓扑下都不可移植。
        if spec.kv_quant_mode.is_per_token_head:
            return False
        # 严格类型命中纯 MLA latent:canonical 布局把 TP 副本去重为一份,可认证。
        if type(spec) is MLAAttentionSpec:
            return (
                spec.tokens_per_state == 1
                and spec.real_page_size_bytes % spec.block_size == 0
            )
        # 上一步的严格 type 检查已放行纯 MLA,这里实际只拦截窗口变体 SWA 版 MLA。
        if isinstance(spec, (SlidingWindowMLASpec, MLAAttentionSpec)):
            return False
        # 仅 full-attention 与 sliding-window 这类 head-shard 页布局可能可移植。
        if not isinstance(spec, (FullAttentionSpec, SlidingWindowSpec)):
            return False
        # 每个组的 KV head 数必须恰好等于本 rank 的 shard 数:既处理 head sharding
        #(total_kv_heads % tp_size == 0),也处理 GQA 复制(tp_size % total_kv_heads == 0)。
        return (
            total_kv_heads % tp_size == 0 or tp_size % total_kv_heads == 0
        ) and spec.num_kv_heads == max(1, total_kv_heads // tp_size)
​
    # 逐组认证:全部组的 spec 均通过,整体才可跨拓扑折叠存储命名空间;
    # 并行约束仍沿用原 gate:无 CP 轴且 world_size 恰好等于 TP 规模。
    is_parallelism_agnostic = (
        len(kv_cache_config.kv_cache_groups) > 0
        and all(
            spec_certifiable(group.kv_cache_spec)
            for group in kv_cache_config.kv_cache_groups
        )
        and parallel_config.decode_context_parallel_size == 1
        and parallel_config.prefill_context_parallel_size == 1
        and parallel_config.world_size == tp_size
    )
tests/v1/kv_connector/unit/offloading_connector/test_config.py test-coverage

测试配套:`_parallelism_agnostic` 辅助函数扩展 canonical/v2 参数,gate 测试折叠为四象限参数化表,新增 _SWA_SPEC/_SWA_MLA_SPEC 常量与 _groups 构造器,是验证新门禁行为的主要依据。

# 四象限参数化:这些形状在直接布局下均不可移植;
# canonical 下只有纯 MLA latent 与 full + sliding-window 混合组通过认证,
# SWA 版 MLA 与 Mamba 混合仍被拒绝(前者无法静态认证,后者是 opaque 映射)。
@pytest.mark.parametrize(
    ("kv_cache_groups", "certified"),
    [
        pytest.param(_groups(_mla_spec(head_size=576)), True, id="mla-latent"),
        pytest.param(
            _groups(_full_attention_spec(), _SWA_SPEC), True, id="attention-hybrid"
        ),
        pytest.param(_groups(_SWA_MLA_SPEC), False, id="swa-mla"),
        pytest.param(
            list(_make_mamba_hybrid_kv_cache_config().kv_cache_groups),
            False,
            id="mamba-hybrid",
        ),
    ],
)
def test_canonical_layout_gate(kv_cache_groups, certified):
    """Canonical 布局逐组认证可移植性;这些形状在直接布局下都不可移植。"""
    assert not _parallelism_agnostic(kv_cache_groups)
    assert _parallelism_agnostic(kv_cache_groups, canonical=True) is certified

评论区精华

合并冲突提示与 rebase other

mergify[bot] 提示 PR 存在 merge conflicts 需要 rebase;作者随后在 commit 1afff1b 与 80e642e 中两次 merge main 解决冲突。

结论:冲突已解决并合入 main。 · 已解决

LGTM 与 ready 标记 other

评审人 bnellnm 评论 "LGTM Do you mind adding ready?",作者随后在标签中加入 ready 并请求 mgoin 协助 review。

结论:作者添加 ready 标签,PR 通过两位评审人 approve。 · 已解决

per-group head 检查收紧 正确性

commit 10515616 将模型级整除检查细化为每个组的 spec.num_kv_heads == max(1, total_kv_heads // tp_size),避免模型级整除但单组 head 数与 rank 分片不匹配的假阳性。

结论:采用 per-group 精确匹配,同时兼容 head sharding 与 GQA 复制两种方向。 · 已解决

gate 分支形状与堆叠 PR 冲突 设计

commit 271061736 特意保留 gate 的原始分支形状,以减少与堆叠 PR #51690 / #51691 的合并冲突面,属于堆叠开发中的显式取舍。

结论:保持原 gate 结构,后续堆叠 PR 独立扩展各自轴。 · 已解决

门禁测试折叠为参数化表 测试

连续两个 commit(da28b36、a9e4b11)把分散的 gate 测试重构为 pytest.param 参数化表格,并引入 _groups 构造器统一生成 KVCacheGroupSpec 列表,消除重复样板。

结论:参数化表覆盖 mla-latent、attention-hybrid、swa-mla、mamba-hybrid 四象限。 · 已解决

风险与影响

  1. 静态镜像漂移风险:spec_certifiable 是 _layer_mapping 注册时逐层检查的静态复制品。_layer_mapping 后续若新增 spec 类型或布局规则(新量化模式、新窗口式 latent 布局),静态镜像可能滞后,出现“门禁放行、运行时认证失败”。缓解机制是 _validate_canonical_refs 在层注册时 fail closed,最坏情况是启动失败而非错误复用,但会带来静态/动态不一致的排查成本。
  2. head 数检查的保守性:spec.num_kv_heads == max(1, total_kv_heads // tp_size) 会拒绝像 6 heads 配 tp=4 这类互不整除的组合,即使实际布局可移植也会被保守拒绝——功能范围受限但安全性优先。
  3. namespace 语义变化:启用 canonical 后存储命名空间不再编码并行拓扑,升级后旧 namespace 缓存不再命中,属一次性缓存失效;跨拓扑复用依赖所有参与拓扑的字节布局完全同构,若未来 SWA 层改变页组织方式会破坏该假设。
  4. 测试盲区:test_canonical_layout_gate 未覆盖多组 full-attention(full+full)在 canonical 下的行为,也未覆盖 per-token-head 量化在 canonical 下的拒绝路径(后者正是 #51691 的扩展轴,属于有意留白)。单测无 GPU,依赖提交者在 2x H100 上的人工 E2E 验证。
  5. 堆叠 PR 合并顺序:本 PR 是 #51690 / #51691 的基础,PR 生命周期内已出现一次 mergify 冲突并 rebase 解决,后续两个堆叠 PR 合入时仍需注意冲突面。
  • 用户与部署:启用 canonical_layout 的 KV offload 用户,其 gpt-oss-20b/120b、Gemma 类 interleaved-SWA 模型可在不同 TP 配置的进程间复用同一份 KV 缓存。E2E 显示 TP2 冷进程命中 3840/3901 token,显著缩短首 token 延迟与回退重算;共享 root_dir 的多副本部署形态可获得更好缓存命中率。
  • 系统:门禁逻辑从单组判定变为逐组判定,影响所有 offloading 配置构建路径;非 canonical 模式行为不变,回归面小。存储命名空间语义从“含并行拓扑”变为“不含拓扑”(canonical 模式下),直接影响 KV offload 的键空间分布。
  • 团队:本 PR 为同一 gate 的后续扩展(#51690 的 UniformTypeKVCacheSpecs 展开、#51691 的 per-token-head KV 量化)奠定基础,KV offload 功能线从 full-attention 专用逐步走向混合架构通用。
静态门禁与运行时认证一致性 堆叠 PR 合并顺序敏感 namespace 语义变更影响存量缓存

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论