Prhub

#54941 [Transformers backend] Find attention with a fuser and attach vLLM's layer to it

原始 PR 作者 bohnstingl 合并时间 2026-09-05 00:26 文件变更 14 提交数 22 评论 29 代码增减 +522 / -151

执行摘要

重构 Transformers 后端注意力发现与附加机制,解决模块树、缩放与索引的三个潜在 bug。

动机在于解决 Transformers 后端原有的三个潜在问题:

  1. create_attention_instances 将注意力实例存储在普通字典 attention_instances 中,由于 nn.Module.__setattr__ 不注册字典,这些层不在模块树中,导致 process_weights_after_loading、model.to(device) 等操作无法找到它们。MLA 路径已通过 _vllm_mla_attn 单独处理,但不通用。
  2. impl.scale 在每次前向传播时被修正,但当一个编译产物跨层复用时(例如使用 LayerName),只有第一层的 Python 帧会运行,导致其他层使用错误的默认缩放。
  3. 层索引 layer_idx 作为 int 被烘焙进计算图,限制了 Dynamo 编译复用单个工件的能力。本 PR 旨在系统性地解决这些问题,提升后端与 Transformers 模块树的兼容性、正确性和可编译性。

这是一个高价值的架构改进 PR,解决了 Transformers 后端的多个根本性问题。建议:

  1. 精读核心逻辑:重点关注 AttentionFuser 的实现、融合器列表的生成与应用逻辑,以及 create_attention_instances 中注意力附加和错误检查的部分。理解这些是理解整个变更的关键。
  2. 验证测试覆盖:检查新增的测试用例(test_attention_dispatch_is_matched 等)是否覆盖了关键场景,如 MLA、非标准缩放(Gemma, DeepSeek, OPT)、层索引自定义(LongCat Flash)。
  3. 关注后续依赖:此 PR 改变了融合器接口,需要确保所有调用点都已更新。
  4. 留意关联 PR:注意与 PR#52156(注意力 sinks)的重叠和解决方案的差异。本 PR 通过附加而非 nn.ModuleList 注册来解决模块树可见性,同时带来了额外好处。
讨论亮点
  1. 缩放表达式解析:coderabbitai[bot] 指出 _resolve 无法解析复杂表达式(如 self.head_dim**-0.5 * self.factor)可能导致静默回退到默认缩放,引发正确性问题。bohnstingl 随后添加了异常处理,在无法解析时抛出 ValueError,避免了静默错误。
  2. 注册条件放宽:hmellor 建议将支持后端的条件改为 or 以最大化兼容性(hmellor -> Could we change the supports backend condition to or both conditions so maximum permissibility)。这导致了 registry.py 中条件从 is_backend_compatible() 变为 is_backend_compatible() or _can_set_attn_implementation() 的修改。
  3. 递归的必要性:在后续提交中,hmellor 添加了递归回退,表示其必要性(hmellor -> I've added the recursion back because it's necessary),这可能与 recursive_replace 或模块遍历逻辑有关。

实现拆解

  1. 引入 AttentionFuser:在 vllm/model_executor/models/transformers/fusers/attention.py 中新增 AttentionFuser 类。它通过源码级 AST 分析(interface_call 函数)发现模块的 forward 方法是否调度到 ALL_ATTENTION_FUNCTIONS 接口。其 validate 方法检查模块的 config._attn_implementation 是否为 vLLM 后端。layer_index 和 scale 方法分别从模块属性中读取层索引和从 AST 解析出的缩放表达式中解析缩放值。
  2. 增强融合器系统:在 vllm/model_executor/models/transformers/fuser.py 中,将 get_fuser 函数重构为 get_fusers,返回一个融合器列表。系统现在支持多个非冲突的融合器:一个 RewriteFuser(如 QKVFuser)可以重写前向传播,而 AttentionFuser 等不重写前向的融合器可以共存。Fusers 类和 get_fuser 辅助函数相应更新以处理列表。
  3. 附加注意力实例并注册:在 vllm/model_executor/models/transformers/base.py 的 recursive_replace 方法中,当遍历模块树时,为每个匹配 AttentionFuser 的模块调用 register_fusion。该函数将融合器列表注册到 self.fusers,并为 AttentionFuser 专门维护一个 self.attention_fusers 字典(layer_idx -> (prefix, fuser))。在 create_attention_instances 中,通过 self.attention_fusers 直接获取每个层的注意力模块,并通过 setattr(module, VLLM_ATTN_ATTR, attn_instance) 将注意力实例附加为模块的 attn 属性。这确保了注意力层在模块树中可见。
  4. 更新注意力调度函数:在 vllm/model_executor/models/transformers/__init__.py 中,vllm_attention_forward 和 vllm_mla_attention_forward 函数不再接收 attention_instances 参数,而是通过 getattr(module, VLLM_ATTN_ATTR) 获取附加的注意力实例。这简化了接口。
  5. 放宽注册条件:在 vllm/model_executor/models/registry.py 中,将 Transformers 后端的注册条件从 is_backend_compatible() 放宽为 is_backend_compatible() or _can_set_attn_implementation(),使得更多模型可以使用此后端。
  6. 配套测试与文档:在 tests/models/transformers/test_backend.py 中新增了针对注意力调度、层索引、缩放值解析的测试。在 tests/models/transformers/fusers/test_linear.py 等测试中更新了融合器获取和缓存的调用方式。更新了 docs/models/supported_models.md 文档。
文件 模块 状态 重要度
vllm/model_executor/models/transformers/fusers/attention.py 注意力融合器 added 9.23
vllm/model_executor/models/transformers/fuser.py 融合器系统 modified 8.51
vllm/model_executor/models/transformers/base.py 后端基类 modified 7.94
tests/models/transformers/test_backend.py 后端测试 modified 7.85
vllm/model_executor/models/transformers/__init__.py Transformer 模块 modified 6.71
vllm/model_executor/models/registry.py 模型注册 modified 5.57

关键符号

AttentionFuser.match AttentionFuser.validate AttentionFuser.scale AttentionFuser.layer_index interface_call _resolve _is_interface_lookup get_fusers Base.register_fusion Base.create_attention_instances vllm_attention_forward vllm_mla_attention_forward

关键源码片段

vllm/model_executor/models/transformers/fusers/attention.py core-logic

新增核心组件 `AttentionFuser`,实现了基于 AST 源码分析的注意力模块发现、验证、缩放值解析等关键逻辑。

# vllm/model_executor/models/transformers/fusers/attention.py
@dataclass
class AttentionFuser(BaseFuser):
    """A module that dispatches through the Transformers attention interface."""
​
    redefines_forward: ClassVar[bool] = False
​
    source_cls: str
    """Class of the HF module that dispatches (for logging)."""
    scale_expr: ast.expr | None
    """Source of the `scaling=` the module hands the interface, if it hands one."""
​
    @classmethod
    def match(
        cls, graph: fx.Graph | None, module: nn.Module
    ) -> "AttentionFuser | None":
        # 通过调用 interface_call 分析模块的 forward 源码,查找唯一的接口调用
        if (call := interface_call(type(module))) is None:
            return None
        # 从 AST 调用节点中提取 `scaling` 关键字参数的值表达式
        scaling = [kw.value for kw in call.keywords if kw.arg == "scaling"]
        scale_expr = scaling[0] if len(scaling) == 1 else None
        return cls(source_cls=type(module).__name__, scale_expr=scale_expr)
​
    def validate(self, module: nn.Module, vllm_config: "VllmConfig") -> bool:
        """Whether `module` will actually dispatch to vLLM."""
        config = getattr(module, "config", None)
        # 只有被文本配置修补为 vLLM 实现时,才会通过验证
        # 这确保了注意力编码器模块不会被错误地附加
        vllm_attn_impls = {VLLM_ATTN_IMPL, VLLM_MLA_ATTN_IMPL}
        return getattr(config, "_attn_implementation", None) in vllm_attn_impls
​
    def scale(self, module: nn.Module) -> float | None:
        """The softmax scale `module` passes to the interface, or `None`."""
        if self.scale_expr is None:
            return None
        # 解析 AST 表达式为模块上的实际值(支持字面量和 self.attr)
        scale = _resolve(self.scale_expr, module)
        if not isinstance(scale, (int, float)) or isinstance(scale, bool):
            # 无法解析的表达式将抛出异常,避免静默回退到错误的默认值
            expression = ast.unparse(self.scale_expr)
            raise ValueError(
                f"Cannot resolve attention scaling expression {expression!r} in "
                f"{type(module).__name__}."
            )
        return float(scale)
vllm/model_executor/models/transformers/fuser.py core-logic

重构融合器检测核心逻辑,从返回单个融合器改为返回融合器列表,支持 `AttentionFuser` 等非冲突融合器的共存。

# vllm/model_executor/models/transformers/fuser.py
FUSERS: tuple[type[BaseFuser], ...] = (
    MLAFuser,
    GLUFuser,
    QKVFuser,
    PackedQKVFuser,
    RMSNormFuser,
    AttentionFuser,
)
"""Every fuser, in match order: those that redefine the forward first, then those
that leave it alone. A new fuser is added here."""
​
@cached(cache={}, key=key)
def get_fusers(module: nn.Module) -> list[BaseFuser]:
    """Every fuser that applies to `module`'s class and shape (cached).
​
    The one that redefines the forward comes first, so applying them in order
    fuses before the rest read or adjust the result."""
    n_linear = sum(isinstance(c, nn.Linear) for c in module.children())
    is_leaf = next(module.children(), None) is None
    # 图追踪是昂贵的,仅在可能匹配投影或 Norm 融合器时执行
    graph = trace(module) if n_linear >= 2 or is_leaf else None
​
    fusers: list[BaseFuser] = []
    for fuser_cls in FUSERS:
        # 如果是重写前向的融合器(如 QKVFuser),且已经有一个重写器,或没有图,则跳过
        if fuser_cls.redefines_forward and (
            graph is None or any(fuser.redefines_forward for fuser in fusers)
        ):
            continue
        if (fuser := fuser_cls.match(graph, module)) is None:
            continue
        if isinstance(fuser, RewriteFuser):
            try:
                fuser.update_forward(module)
            except Exception as exc:
                logger.debug(...)
                continue
        fusers.append(fuser)
    return fusers
vllm/model_executor/models/transformers/base.py core-logic

展示了注意力实例如何被附加到模块树,以及 `attention_fusers` 字典的注册逻辑,这是本次重构的核心目标实现。

# vllm/model_executor/models/transformers/base.py ( 节选 )
def register_fusion(fuser: BaseFuser, prefix: str, module: nn.Module):
    """Register a fused layer's mappings just before it is built."""
    self.fusers.setdefault(prefix, []).append(fuser)
    # 为 AttentionFuser 维护一个从层索引到模块前缀和融合器的映射
    if (
        isinstance(fuser, AttentionFuser)
        and (index := fuser.layer_index(module)) is not None
    ):
        if index in self.attention_fusers:
            other, _ = self.attention_fusers[index]
            raise ValueError(
                f"Layer {index} dispatches attention from more than one "
                f"module ({other} and {prefix}), which one vLLM attention "
                "layer cannot serve."
            )
        self.attention_fusers[index] = (prefix, fuser)
    # ... 更新映射器和打包模块映射
​
def create_attention_instances(self) -> dict[int, Attention]:
    """..."""
    attention_instances = {}
    # ... 获取注意力类和配置
    for i in range(start, end):
        if i not in self.attention_fusers:
            raise ValueError(
                f"Layer {i} does not dispatch through the Transformers "
                "attention interface..."
            )
        prefix, attn_fuser = self.attention_fusers[i]
        attn_module = self.get_submodule(prefix)
        # ... 从 attn_fuser 获取缩放值
        scale = attn_fuser.scale(attn_module)
        if scale is None:
            scale = head_size**-0.5 # 回退到 Llama 默认值
        # ... 构造注意力实例 kwargs
        attn_instance = attn_cls(**kwargs)
        # 将注意力实例附加为模块的 'attn' 属性,使其在模块树中可见
        setattr(attn_module, VLLM_ATTN_ATTR, attn_instance)
        attention_instances[i] = attn_instance
    return attention_instances

评论区精华

无法解析的缩放表达式处理 正确性

coderabbitai[bot] 指出 `_resolve` 对复杂表达式(如 `self.head_dim**-0.5 * self.factor`)的解析失败会导致静默回退到默认缩放,可能改变模型输出。bohnstingl 回应称已添加异常处理,在无法解析时抛出 `ValueError`,避免静默错误。

结论:已通过在 `AttentionFuser.scale` 方法中添加对解析结果的检查,并在无法解析时抛出异常来解决。这防止了不正确的默认缩放被静默应用。 · 已解决

注册条件放宽为 `_can_set_attn_implementation` 设计

hmellor 建议将注册条件改为 `or` 以最大化兼容性。这导致了在 `registry.py` 中将 `is_backend_compatible()` 改为 `is_backend_compatible() or _can_set_attn_implementation()`。coderabbitai[bot] 后来建议为该条件添加回归测试。

结论:合并时已应用条件放宽。关于回归测试的建议可能需要在后续 PR 中跟进。 · 已解决

融合器列表化与缓存更新 设计

本 PR 将 `get_fuser` 重构为 `get_fusers`,返回列表。这需要在所有调用点进行更新。例如,`test_linear.py` 中的缓存清理和调用方式均已更新为使用 `get_fusers` 或新的 `get_fuser(module, cls)` 辅助函数。

结论:融合器系统已成功重构为支持多个融合器。所有相关测试和调用点(如 `moe.py`)都已相应更新。 · 已解决

风险与影响

  1. 核心路径变更:此变更修改了所有使用 Transformers 后端的模型的注意力构建和发现机制(base.py, __init__.py)。如果 AttentionFuser.match 或 scale 解析逻辑存在缺陷,可能影响所有使用该后端模型的推理正确性。特别是 scale 的 AST 解析,如果遗漏了某些模型特有的缩放表达式,会导致错误的 softmax 缩放。
  2. 兼容性风险:融合器系统从返回单个 BaseFuser | None 变为返回 list[BaseFuser]。所有依赖 Fusers 和 get_fuser 的代码(如 moe.py)需要更新以使用新的 get_fuser(module, FuserCls) 接口。未能更新的调用将导致错误。
  3. 模块树变化:注意力实例现在通过 setattr 附加为模块属性,这改变了模型的状态字典结构。这可能会影响依赖于严格状态字典键名的工具或流程,但同时也使得 process_weights_after_loading 等操作能正确找到注意力层,这是一个正向的兼容性改进。
  4. 未测试的场景:PR 描述明确指出,需要加速器的测试(tensor parallel、模型评估)尚未在合并前运行,这些测试可能揭示在真实设备上的问题。
  1. 对用户影响:主要影响使用 Transformers 后端(model_impl="transformers")的用户。他们将受益于更正确的注意力缩放、对 model.to(device) 等操作的更好支持,以及更广泛的模型兼容性(更多模型可以通过放宽的条件被识别)。变更对用户基本是透明的。
  2. 对系统影响:显著改善了 Transformers 后端与 PyTorch 模块系统的集成度。将注意力实例纳入模块树解决了多个长期存在的设计缺陷,为后续优化(如量化权重加载、分布式训练)奠定了基础。同时,增强的融合器系统为未来添加更多非冲突的融合器(如注意力相关的其他后处理)提供了清晰的扩展点。
  3. 对团队影响:为维护者提供了更清晰、更健壮的融合器框架。新引入的 AttentionFuser 模式(通过源码分析而非 fx 图追踪)可以被其他类似场景复用。测试覆盖的增加也提升了代码质量。
核心路径变更 架构重构 依赖接口变更 未充分测试加速器场景

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论