Prhub

#45715 [LoRA] Gate all_gather on fully_sharded_loras inside _mcp_apply; rewrite regression test

原始 PR 作者 lcheng321 合并时间 2026-06-23 22:08 文件变更 1 提交数 6 评论 11 代码增减 +5 / -5

执行摘要

修复非全分片 LoRA 在 TP>1 时的 all_gather 错误

Issue #45691 报告了在 TP>1 且 fully_sharded_loras=False 时,MergedColumnParallelLinearWithLoRA_mcp_apply 中的无条件 all_gather 导致 shape mismatch,LoRA 输出静默错误。该 Bug 影响所有使用 TP>1 并加载 LoRA 适配器(如 gate_proj/up_proj)的用户。

值得精读。此 PR 展示了 LoRA 层中 TP 通信与分片策略的精细交互,是一个典型的数据竞争 bug 的修复案例。_mcp_apply 的条件化 all_gather 设计和 apply 方法的路由调整值得关注。

讨论亮点

主要讨论围绕两个设计决策:

  1. jeejeelee 提醒直接改用 _apply_sync 会丢失 dual-stream 并行收益,作者随后改为在 _mcp_apply 内条件化 all_gather,保留 dual-stream 路径。
  2. anshulkulhari7 确认诊断正确,建议增加对称断言验证 sharded 子类仍走 _mcp_apply,作者已采纳并在魔改的单元测试中覆盖。

实现拆解

  1. 修改 _mcp_apply 函数:添加 assert 确保只有 fully_sharded 路径调用;将 all_gather 置于条件 fully_sharded_loras and tp_size > 1 下,非全分片时跳过通信步骤。
  2. 修改 MergedColumnParallelLinearWithLoRA.apply 方法:将末尾的 return _mcp_apply(x, bias, self) 改为 return super().apply(x, bias),使非 fully_sharded 情况走父类的 apply 方法,父类方法最终通过 _apply_sync 路径处理,避免进入 _mcp_apply
  3. 更新 docstring:明确说明 _mcp_apply 仅用于 fully_sharded 路径。
  4. 配套测试:作者在 commit 中添加了单元测试(未体现在 PR 文件变更中),通过 mocked all_gather 验证非 sharded 路径不调用 all_gather,shape 正确。
文件 模块 状态 重要度
vllm/lora/layers/column_parallel_linear.py LoRA 层 modified 5.57

关键符号

_mcp_apply MergedColumnParallelLinearWithLoRA.apply

关键源码片段

vllm/lora/layers/column_parallel_linear.py core-logic

包含核心修复:_mcp_apply 内 all_gather 条件化和 MergedColumnParallelLinearWithLoRA.apply 路由调整。

def _mcp_apply(x, bias, layer: "ColumnParallelLinearWithLoRA"):
    """Fully-sharded (S-LoRA) apply path for column-parallel LoRA layers."""
    assert layer.lora_config.fully_sharded_loras, (
        "_mcp_apply is only used for fully sharded LoRA"
    )
    # ... (assert slices and compute quant output) ...
​
    # Shrink buffer: each rank produces [n_slices, num_tokens, local_lora_rank]
    buffers = layer.punica_wrapper.add_shrink(
        torch.empty_strided(...), x, layer.lora_a_stacked, 1.0
    )
​
    # Only all-gather when fully sharded; otherwise each rank already
    # holds the full rank dimension (max_lora_rank).
    if layer.lora_config.fully_sharded_loras and layer.tp_size > 1:
        buffers = tensor_model_parallel_all_gather(buffers)
​
    # Non-sharded: rank dim = local_lora_rank, matches lora_b_stacked.
    # Sharded: after all-gather, rank dim = local_lora_rank * tp_size.
    lora_output = layer.punica_wrapper.add_expand(...)
    return lora_outputclass MergedColumnParallelLinearWithLoRA(ColumnParallelLinearWithLoRA):
    def apply(self, x, bias=None):
        # ... condition for base forward ...
        # Previously: return _mcp_apply(x, bias, self)
        # Now: route through super().apply which will call _mcp_apply
        # only when fully_sharded or tp_size == 1
        return super().apply(x, bias)

评论区精华

直接改 _apply_sync 会丢失 dual-stream 设计

jeejeelee 指出直接改为 _apply_sync 会丢失 dual-stream 并行收益。

结论:作者改为在 _mcp_apply 内部条件化 all_gather,保留 dual-stream 路径。 · 已解决

增加对称断言确保 sharded 子类仍走 _mcp_apply 正确性

anshulkulhari7 建议在测试中验证 sharded 子类也路由到 _mcp_apply。

结论:作者采纳并在单元测试中添加断言。 · 已解决

风险与影响

风险较低:新增 assert 确保 fully_sharded 路径不会误用;all_gather 条件化不影响现有 fully_sharded 路径行为。主要风险是如果其他代码路径意外调用 _mcp_apply(非 fully_sharded),assert 会直接暴露问题。测试覆盖上,PR 未包含正式的测试文件,但作者确认有单元验证。

影响范围:使用 LoRA、TP>1、且 fully_sharded_loras=False 的用户,尤其是使用合并列并行模块(如 gate_proj/up_proj)的用户。这些用户的 LoRA 输出在此之前可能静默错误,修复后输出正确。性能上,non-sharded 路径不再执行 all_gather,可能有轻微性能提升。

核心路径变更 测试覆盖不完全

关联 Issue

#45691 [Bug]: MergedColumnParallelLinearWithLoRA fails under TP when fully_sharded_loras=False due to incorrect all_gather in apply

完整报告

参与讨论