执行摘要
- 一句话:单全注意力组KV缓存跨并行尺寸复用
- 推荐动作:本PR设计值得精读:将并行无关判断集中于
FileMapper,通过布尔参数让各tier透明受益,是一种清晰的关注点分离。特别是对子类关系的处理(排除 MLA)体现了防御式编程。建议在引入新的注意力类型时同步更新排除条件。
功能与动机
KV offload 缓存目前按并行配置(tp_size、rank 等)命名路径,导致相同内容在不同并行尺寸下无法复用。对于单个全注意力组,KV 块分布与并行度无关,因此可跨并行配置共享缓存,提升缓存命中率并减少I/O。讨论中 orozery 要求将逻辑移到 FileMapper 以便复用。
实现拆解
- 集中判断逻辑至FileMapper:在
vllm/v1/kv_offload/file_mapper.py 的 from_offloading_spec 方法中,从 kv_cache_groups 提取 spec,若唯一 group 且 spec 为 FullAttentionSpec 且非 MLAAttentionSpec,则保留 parallel_agnostic 传入值,否则强制设为 False。同步导入 FullAttentionSpec、MLAAttentionSpec。
- 启用文件系统tier:在
vllm/v1/kv_offload/tiering/fs/manager.py 中调用 FileMapper.from_offloading_spec 时传入 parallel_agnostic=True,将实际裁决交给 FileMapper。
- 启用对象存储tier:在
vllm/v1/kv_offload/tiering/obj/manager.py 中做相同修改,使对象存储层也受益于并行无关共享。
- 新增单元测试:在
tests/v1/kv_offload/test_file_mapper.py 中添加辅助构造方法(_full_attention_group、_sliding_window_group)及四个测试函数,分别验证:单全注意力组启用(tp_size→1, rank→0)、多组禁用、非全注意力(滑动窗口)禁用、MLA 排除。
关键文件:
tests/v1/kv_offload/test_file_mapper.py(模块 测试;类别 test;类型 test-coverage;符号 _full_attention_group, _sliding_window_group, test_parallel_agnostic_enabled_for_single_full_attention, test_parallel_agnostic_disabled_for_multiple_groups): 新增 parallel_agnostic 完整测试,覆盖启用、禁用、MLA排除等场景。
vllm/v1/kv_offload/file_mapper.py(模块 文件映射;类别 source;类型 dependency-wiring;符号 from_offloading_spec): 核心判断逻辑所在,新增 parallel_agnostic 条件推导。
vllm/v1/kv_offload/tiering/fs/manager.py(模块 文件系统层;类别 source;类型 core-logic;符号 init): 文件系统 tier 入口,传入 parallel_agnostic=True 以触发共享。
vllm/v1/kv_offload/tiering/obj/manager.py(模块 对象存储层;类别 source;类型 core-logic;符号 init): 对象存储 tier 入口,同样传入 parallel_agnostic=True。
关键符号:FileMapper.from_offloading_spec
关键源码片段
tests/v1/kv_offload/test_file_mapper.py
新增 parallel_agnostic 完整测试,覆盖启用、禁用、MLA排除等场景。
def _full_attention_group() -> KVCacheGroupSpec:
# 构建一个全注意力组的 spec
return KVCacheGroupSpec(
layer_names=["layer0"],
kv_cache_spec=FullAttentionSpec(
block_size=16, num_kv_heads=4, head_size=128, dtype=torch.float32
),
)
def _sliding_window_group() -> KVCacheGroupSpec:
# 构建一个滑动窗口注意力组的 spec
return KVCacheGroupSpec(
layer_names=["layer0"],
kv_cache_spec=SlidingWindowSpec(
block_size=16, num_kv_heads=4, head_size=128, dtype=torch.float32,
sliding_window=128,
),
)
# 只有单个全注意力组时,tp_size 被强制为 1,rank 被置为 0
# 从而产生与并行度无关的缓存路径
def test_parallel_agnostic_enabled_for_single_full_attention():
fm = make_mapper_from_offloading_spec(
tp_size=2, rank=1,
kv_cache_groups=[_full_attention_group()],
parallel_agnostic=True,
)
assert fm.fields["tp_size"] == 1
assert fm.rank == 0
# 多个 group 时,即使 parallel_agnostic 传入 True 也应保持原有 tp_size
def test_parallel_agnostic_disabled_for_multiple_groups():
fm = make_mapper_from_offloading_spec(
tp_size=2,
kv_cache_groups=[_full_attention_group(), _full_attention_group()],
parallel_agnostic=True,
)
assert fm.fields["tp_size"] == 2
# 非全注意力(滑动窗口)即使只有一个 group 也不启用并行无关
def test_parallel_agnostic_disabled_for_non_full_attention():
fm = make_mapper_from_offloading_spec(
tp_size=2,
kv_cache_groups=[_sliding_window_group()],
parallel_agnostic=True,
)
assert fm.fields["tp_size"] == 2
vllm/v1/kv_offload/file_mapper.py
核心判断逻辑所在,新增 parallel_agnostic 条件推导。
@classmethod
def from_offloading_spec(
cls,
root_dir: str,
offloading_spec: OffloadingSpec,
gpu_blocks_per_file: int = 1,
parallel_agnostic: bool = False,
) -> "FileMapper":
vllm_config = offloading_spec.vllm_config
kv_cache_config = offloading_spec.kv_cache_config
parallel_config = vllm_config.parallel_config
dtype = str(vllm_config.cache_config.cache_dtype).replace("torch.", "")
kv_cache_groups = [
{
"block_size": group.kv_cache_spec.block_size,
"layer_names": list(group.layer_names),
}
for group in kv_cache_config.kv_cache_groups
]
# 只有当只有一个 group 且该 group 是 FullAttentionSpec(且不是 MLA)时
# 才允许启用 parallel_agnostic
groups = kv_cache_config.kv_cache_groups
spec = groups[0].kv_cache_spec if len(groups) == 1 else None
parallel_agnostic = (
parallel_agnostic
and isinstance(spec, FullAttentionSpec)
and not isinstance(spec, MLAAttentionSpec)
)
return cls(
root_dir=root_dir,
model_name=vllm_config.model_config.model,
hash_block_size=vllm_config.cache_config.block_size,
gpu_blocks_per_file=gpu_blocks_per_file,
tp_size=parallel_config.tensor_parallel_size,
pp_size=parallel_config.pipeline_parallel_size,
pcp_size=parallel_config.prefill_context_parallel_size,
dcp_size=parallel_config.decode_context_parallel_size,
rank=parallel_config.rank,
dtype=dtype,
kv_cache_groups=kv_cache_groups,
parallel_agnostic=parallel_agnostic,
)
评论区精华
核心讨论由 reviewer orozery 驱动:
风险与影响
- 风险:
- MLA 排除遗漏风险:
MLAAttentionSpec 继承自 FullAttentionSpec,若未来新增注意力类型未显式排除,可能被错误启用。当前代码明确排除 MLA,相对安全。
- 缓存一致性风险:跨并行尺寸共享缓存时,若 GPU 数量变化导致显存分配不一致,load 端有
Short read 检查,失败时回退重计算,不会返回错误数据。
- 无性能风险:仅在文件映射时增加少量条件判断,无运行时开销。
- 影响:
- 用户:同一模型在不同并行配置间切换时可能命中已有缓存,减少 offload I/O,降低延迟。
- 系统:向前兼容,现有缓存路径不受影响;新增
parallel_agnostic 参数,扩展新 tier 时需显式传入。
- 团队:测试覆盖完整,降低回归风险;设计将判断逻辑集中,后续维护清晰。
- 风险标记:MLA 排除依赖类继承关系, 跨并行尺寸缓存一致性检查, 核心路径变更需评审注意力类型扩展
关联脉络
参与讨论