Prhub

#47725 [BugFix][LoRA] Refresh punica metadata when LoRA slots are reassigned under an unchanged mapping

原始 PR 作者 AmeenP 合并时间 2026-07-07 23:53 文件变更 2 提交数 2 评论 0 代码增减 +56 / -1

执行摘要

修复 LoRA 槽位重分配导致 punica 元数据过期

FIX #47724。运行时 add_lora() 可能 LRU 驱逐正在解码的适配器并旋转 lora_index_to_id,但 in-flight 请求集的 LoRAMapping 未变,memo 命中跳过 punica 刷新,导致 token_lora_indices 仍编码旧槽位,请求实际使用另一个适配器的权重。详见 Issue 附带的完整分析及自助复现脚本。

值得精读的设计决策:通过额外字段扩展现有 memo,而非完全移除优化。该模式可在类似缓存失效场景复用。

讨论亮点

无人工 review 讨论。仅 bot 评论和 maintainer 简单 approval。

实现拆解

  1. 新增 _last_slot_layout 字段vllm/lora/model_manager.py__init__ 中初始化)。
  2. 修改 set_adapter_mapping:在比较 _last_mapping 的基础上,增加 _last_slot_layout != slot_layout 条件(slot_layouttuple(self.lora_index_to_id) 即时获得);任一变时即执行 _set_adapter_mapping 并更新两个 memo。
  3. 修复后,每步无变更时(常见情况)仍命中 memo,保留性能。
  4. 新增回归测试tests/lora/test_lora_manager.pytest_set_adapter_mapping_refreshes_after_slot_reassignment):在 manager 层面驱动精确生产序列——两个适配器解码,外部激活 LRU 驱逐后重分配槽位,验证 token_lora_indices 反映新布局。
文件 模块 状态 重要度
vllm/lora/model_manager.py LoRA 管理 modified 6.05
tests/lora/test_lora_manager.py LoRA 管理 modified 5.59

关键符号

LoRAModelManager.set_adapter_mapping LoRAModelManager.__init__

关键源码片段

vllm/lora/model_manager.py data-contract

核心修复:新增 `_last_slot_layout` 字段,修改 `set_adapter_mapping` 使槽位布局变化时也能触发 punica 元数据刷新。

# vllm/lora/model_manager.py (head 版本 )def __init__(self, model, ...):
    # ... 原有初始化 ...
    self._last_mapping: LoRAMapping | None = None
    self._last_slot_layout: tuple[int | None, ...] | None = None # 新增:记录上一次的槽位布局
    # ...def set_adapter_mapping(self, mapping: LoRAMapping) -> None:
    # The punica metadata derives from the slot layout as well as the
    # mapping: an out-of-band add_lora() can LRU-evict and reassign slots
    # while the running batch, and thus the mapping, is unchanged.
    slot_layout = tuple(self.lora_index_to_id) # 从当前槽位映射构建快照
    if self._last_mapping != mapping or self._last_slot_layout != slot_layout:
        self._set_adapter_mapping(mapping)
        self._last_mapping = mapping
        self._last_slot_layout = slot_layout
    # 若 mapping 和 slot_layout 均未变,则跳过刷新,保持原有性能
tests/lora/test_lora_manager.py test-coverage

新增回归测试 `test_set_adapter_mapping_refreshes_after_slot_reassignment`,精确复现槽位重分配后元数据刷新的场景。

# tests/lora/test_lora_manager.py (head 版本 )@pytest.mark.parametrize("device", DEVICES)
def test_set_adapter_mapping_refreshes_after_slot_reassignment(
    default_vllm_config, dist_init, dummy_model, device
):
    # 场景:运行时 add_lora() 导致 LRU 驱逐并重分配槽位,同时运行的批次
    # 及其中 LoRAMapping 保持不变,此时 punica 元数据必须重新派生。
    # ... ( 创建 manager 和三个 LoRA 适配器,省略模型创建细节 )
    manager = LRUCacheLoRAModelManager(
        model, 2, 2, 2,
        LoRAConfig(max_lora_rank=8, max_cpu_loras=3, max_loras=2, lora_dtype=DEFAULT_DTYPE),
        device=device,
        vllm_config=default_vllm_config,
    )
    punica_wrapper = manager.punica_wrapper_mapping[DEFAULT_LANGUAGE_WRAPPER_KEY]
​
    # 先激活两个适配器,占据 slot 0 和 slot 1
    assert manager.add_adapter(model_lora1)
    assert manager.activate_adapter(1)
    assert manager.add_adapter(model_lora2)
    assert manager.activate_adapter(2)
    assert manager.lora_index_to_id == [1, 2]
​
    # 模拟两个 in-flight 请求各一个 token,分别使用适配器 1 和 2
    manager.set_adapter_mapping(LoRAMapping((1, 2), (1, 2)))
    assert punica_wrapper.token_lora_indices.tolist() == [0, 1] # slot 0-1 对应 adapter 1-2
​
    # out-of-band add_lora():激活适配器 3 会 LRU 驱逐适配器 1(被驱逐的槽位),
    # 然后再激活 1 和 2,它们落入了交换后的槽位,但批次不变
    assert manager.add_adapter(model_lora3)
    assert manager.activate_adapter(3) # 驱逐 slot 0 中的 adapter 1
    assert manager.activate_adapter(1) # 适配器 1 重新激活,可能占用 slot 0(若被驱逐后空闲)
    assert manager.activate_adapter(2) # 适配器 2 重新激活
    assert manager.lora_index_to_id == [2, 1] # 槽位已互换
​
    # 相同的 LoRAMapping,但 token_lora_indices 必须反映新槽位布局
    manager.set_adapter_mapping(LoRAMapping((1, 2), (1, 2)))
    assert punica_wrapper.token_lora_indices.tolist() == [1, 0] # 现在 slot 0 是 adapter 2,slot 1 是 adapter 1

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

本修复非常聚焦:仅修改 set_adapter_mapping 中的 memo 条件,新增一个字段 _last_slot_layout。风险极低,回归风险局限于 LoRA 槽位变更场景。已有端到端复现脚本和单元测试验证。

用户影响:修复生产环境中运行时添加 LoRA 适配器可能导致的静默错误解码,直接影响正确性。
系统影响:无性能退化(无变更时仍命中 memo)。
团队影响:极小的核心路径改动,易于审查。

核心路径变更 测试覆盖充分

关联 Issue

#47724 [Bug]: After a runtime `add_lora()`, one user's in-flight LoRA request is silently served with another user's adapter

完整报告

参与讨论