Prhub

#52156 [Bugfix] Apply attention sinks in the Transformers backend

原始 PR 作者 tdoublep 合并时间 2026-09-08 21:54 文件变更 4 提交数 17 评论 26 代码增减 +127 / -31

执行摘要

修复 Transformers 后端丢失注意力 sink 导致模型输出错误的问题。

PR #48270 添加了 GraniteSWA 和 GraniteMoeSWA 的原生实现,理由是 'the transformers backend does NOT handle these models correctly. Sink tokens get dropped silently and so the model gives wrong output.' 本 PR 解决了这个根本问题,使所有当前和未来的 sink 模型都能在后端正常工作。使用可学习注意力 sink 的模型将 sink 传递为 s_aux,但后端的注意力转发函数 vllm_attention_forward 接受它却在 **kwargs 中丢弃,导致每个 softmax 都是错误的。模型在修复前输出垃圾内容(如 ':::::::::::' 或重复的 token),修复后输出正确结果。

推荐:

  1. 值得精读:本修复解决了影响多个重要模型的核心正确性问题,修复方式体现了良好的架构扩展性(扩展现有 AttentionFuser 而非引入新类),测试覆盖全面。对于关注 vLLM Transformers 后端演进或注意力机制集成的工程师特别值得精读。
  2. 关注点:
    • AttentionFuser.sinks() 的 AST 解析模式,展示了如何从 Transformers 模型源码中自动发现接口参数。
    • 防御性检查 check_sinks 的设计,平衡了运行时开销和错误检测。
    • 将注意力实例注册为 nn.ModuleList 的决策,解决了 PyTorch 模块系统的集成问题。
  3. 后续工作:如评论者提到的,与 PR #54941 的 AttentionFuser 集成值得跟进,以统一处理 scaling 和 s_aux 参数。
  4. 风险考量:建议在合并前运行多 GPU 测试以验证张量并行下的 sink 分片正确性,尽管单 GPU 场景已充分验证。
讨论亮点

核心讨论:

  1. 集成时机:评论者 hmellor 指出 'We should be able bolt this onto the new AttentionFuser that's being added in PR #54941. It contains a very similar pattern where we want to read scaling= from the interface call and this PR can read s_aux=',表明未来可能会进一步统一 scaling 和 s_aux 的处理模式。
  2. 防御性检查设计:在 vllm_attention_forward 中添加的 check_sinks 检查被讨论为 'not actually used here so this just checks that the Transformers modelling code calls forward as expected',并指出 'One way this could become load bearing would be if it validates that s_aux == self_attn.sinks in the first forward',暗示了未来可以加强验证。
  3. MLA 兼容性:AI 代码审查指出最初实现中 sinks 参数被无条件添加到 kwargs 中,可能导致 MLA 模型加载失败。最终修复将 sinks 仅添加到非 MLA 分支。
  4. 方法名拼写错误:代码审查发现测试中的 fuser.sink() 应为 fuser.sinks(),避免了 AttributeError。
    决策结论:修复将 sink 检测集成到现有 AttentionFuser 结构中,避免引入新的 SinkFuser 类(如早期提交所示)。添加了防御性检查以确保未来如果 sink 再次被忽略能快速失败。所有讨论的问题都已在最终提交中解决。

实现拆解

  1. 在 AttentionFuser 中发现 sink 参数:扩展 AttentionFuser 数据类,添加 s_aux_expr 字段用于记录 AST 中 s_aux 关键字参数。在 match 类方法中解析接口调用以捕获 s_aux 表达式。在 fuse 方法中,当 sink 存在时,创建具有分片权重加载器的 nn.Parameter,并将其设置到注意力模块的原始属性名上,确保权重加载正确且支持张量并行。
  2. 在注意力创建流程中传递 sink:修改 base.py 中的 _create_attention_instances 方法(原 create_attention_instances),在构建非 MLA 注意力的 kwargs 时,调用 attn_fuser.sinks(attn_module) 获取 sink 参数并添加到 kwargs['sinks'] 中。这确保了 Attention 构造函数能接收到 sink 张量。
  3. 添加防御性运行时检查:在 __init__.py 的 vllm_attention_forward 和 vllm_mla_attention_forward 中新增 check_sinks 函数调用。如果模型传递了 s_aux 但 Attention 层未标记为有 sink(has_sink 属性),则抛出 ValueError,防止 sink 被静默忽略导致错误输出。
  4. 修复注意力实例注册问题:将 create_attention_instances 重命名为 _create_attention_instances 并改为无返回值。注意力实例现在通过 setattr(attn_module, VLLM_ATTN_ATTR, attn_instance) 附加到模块树,并作为 nn.ModuleList 注册为子模块。这确保了 process_weights_after_loading 和 model.to(device) 能正确处理它们,同时替代了仅用于 MLA 的 _vllm_mla_attn 变通方案。
  5. 添加全面测试覆盖:在 tests/models/transformers/test_backend.py 中添加 test_sinks 测试,加载 tiny-random/gpt-oss-bf16 模型,验证每个注意力层都有一个 sink,且其张量与检查点匹配。check_implementation 现在也断言注意力实例是注册的子模块,覆盖缺陷 2。重构了 count_mla_layers 为通用的 count_modules 辅助函数。
文件 模块 状态 重要度
vllm/model_executor/models/transformers/fusers/attention.py 注意力融合器 modified 7.51
vllm/model_executor/models/transformers/base.py 模型执行器 modified 7.56
vllm/model_executor/models/transformers/__init__.py 模型执行器 modified 7.1

关键符号

_create_attention_instances sinks fuse check_sinks vllm_attention_forward

关键源码片段

vllm/model_executor/models/transformers/fusers/attention.py data-contract

核心修复文件,扩展 AttentionFuser 以发现和初始化注意力 sink 参数,包含 AST 解析和权重加载逻辑。

# attention.py: AttentionFuser.sinks 方法
# 通过 AST 解析发现的 s_aux 表达式来获取 sink 参数
# 如果表达式为空则返回 None,否则解析并验证类型
​
def sinks(self, module: nn.Module) -> nn.Parameter | None:
    """The per-head sink tensor `module` passes to the interface, or `None`."""
    if self.s_aux_expr is None:
        return None
    s_aux = _resolve(self.s_aux_expr, module)
    # 验证解析结果是 Parameter 或 None,否则抛出错误
    if not isinstance(s_aux, (nn.Parameter, type(None))):
        expression = ast.unparse(self.s_aux_expr)
        raise ValueError(
            f"Cannot resolve attention s_aux expression {expression!r} in "
            f"{type(module).__name__}."
        )
    return s_aux
​
# attention.py: AttentionFuser.fuse 方法中的 sink 初始化逻辑
# 当 sink 存在时,创建具有分片权重加载器的 nn.Parameter
# 并将其设置到模块的原始属性名上,确保权重加载正确
def fuse(self, module: nn.Module, prefix: str, vllm_config: "VllmConfig") -> nn.Module:
    if (sinks := self.sinks(module)) is not None:
        size = sinks.numel() // vllm_config.parallel_config.tensor_parallel_size
        device = vllm_config.device_config.device
        data = torch.empty(size, dtype=sinks.dtype, device=device)
        sinks_param = nn.Parameter(data, requires_grad=False)
        # 设置权重加载器以支持张量并行分片
        set_weight_attrs(sinks_param, {"weight_loader": sharded_weight_loader(0)})
        # 设置到模块的原始属性名,保持与 Transformers 模型接口一致
        setattr(module, self.s_aux_expr.attr, sinks_param)
    return module
vllm/model_executor/models/transformers/base.py data-contract

修改注意力实例创建和注册流程,将 sink 传递给 Attention 构造函数,并将实例注册为 nn.ModuleList。

# base.py: _create_attention_instances 方法中的 sink 处理逻辑
# 在非 MLA 注意力分支中,从 fuser 获取 sink 参数并添加到 kwargs
# 这确保 Attention 构造函数能接收到 sink 张量
for i in range(start, end):
    # ... 其他代码 ...
    if attn_cls is MLAAttention:
        # MLA 处理逻辑(不涉及 sink)
        pass
    else:
        kwargs.update(
            head_size=head_size,
            num_kv_heads=self.model_config.get_num_kv_heads(
                self.parallel_config, arch_config
            ),
            logits_soft_cap=logits_soft_cap,
        )
        # 处理滑动窗口注意力
        if layer_types and layer_types[i] == "sliding_attention":
            kwargs["per_layer_sliding_window"] = text_config.sliding_window
        # 新增:处理注意力 sink
        # 从 fuser 获取 sink 参数,如果存在则添加到 kwargs
        if (sinks := attn_fuser.sinks(attn_module)) is not None:
            kwargs["sinks"] = sinks
    attn_instance = attn_cls(**kwargs)
    # 将注意力实例附加到模块树,确保权重加载和设备移动正确
    setattr(attn_module, VLLM_ATTN_ATTR, attn_instance)
vllm/model_executor/models/transformers/__init__.py data-contract

添加 check_sinks 防御性检查,在注意力前向传播时验证 sink 是否正确传递给 Attention 层。

# __init__.py: check_sinks 函数
# 防御性检查:如果模型应用了 sink 但 Attention 层未接收到则抛出错误
# 防止 sink 被静默忽略导致每个 softmax 都是错误的
def check_sinks(
    module: "torch.nn.Module",
    self_attn: "Attention | MLAAttention",
    s_aux: "torch.Tensor | None",
):
    """Fail loudly if the model applies a sink the attention layer will not.
​
    Only the attention impl can fold a sink into the softmax denominator, so a sink
    that never reached `Attention` is dropped and every softmax is subtly wrong.
    """
    # 如果没有 sink 或 Attention 层已有 sink,则正常返回
    if s_aux is None or getattr(self_attn, "has_sink", False):
        return
    # 否则抛出明确错误,要求报告问题
    raise ValueError(
        f"{type(module).__name__} applies attention sinks, but they were not passed "
        f"to {type(self_attn).__name__}, so the output would be wrong. Either the "
        "Transformers modeling backend could not find the parameter holding them, or "
        "vLLM does not support sinks for this kind of attention. Please open an issue "
        "at https://github.com/vllm-project/vllm/issues/new"
    )
​
# __init__.py: vllm_attention_forward 中的调用
# 在注意力前向传播前检查 sink 传递情况
def vllm_attention_forward(
    module: "torch.nn.Module",
    query: "torch.Tensor",
    key: "torch.Tensor",
    value: "torch.Tensor",
    attention_mask: "torch.Tensor",
    **kwargs,
):
    self_attn = getattr(module, VLLM_ATTN_ATTR)
    # 新增:检查 sink 是否正确传递
    check_sinks(module, self_attn, kwargs.get("s_aux"))
    # ... 后续处理 ...

评论区精华

防御性检查的设计意图 设计

hmellor 指出 's_aux isn't actually used here so this just checks that the Transformers modelling code calls forward as expected',并建议 'One way this could become load bearing would be if it validates that s_aux == self_attn.sinks in the first forward'

结论:当前实现仅作为安全网,防止 sink 被静默忽略。未来可能加强为验证 sink 值是否与 Attention 层接收的一致。 · 已解决

MLA 模型的 sink 兼容性 正确性

AI 代码审查指出最初实现中 sinks 参数被无条件添加到 kwargs,可能导致 MLA 模型加载失败。修复将 sinks 仅添加到非 MLA 分支。

结论:最终实现确保 sink 仅添加到非 MLA 注意力分支,避免了 MLA 模型的类型错误。 · 已解决

测试中的方法名拼写错误 测试

代码审查发现测试中的 fuser.sink() 应为 fuser.sinks(),否则在 SM90+ 设备上测试会抛出 AttributeError。

结论:方法名已修正为 sinks(),确保测试能正确执行。 · 已解决

注意力实例注册为子模块的必要性 设计

hmellor 评论 'The instances are passed to the model's forward through a plain dict, so register them as submodules too, otherwise named_modules never yields them and their process_weights_after_loading does not run'

结论:将注意力实例注册为 nn.ModuleList 是解决 PyTorch 模块系统集成问题的正确方法。 · 已解决

风险与影响

技术风险:

  1. 回归风险:核心修复改变了注意力实例的创建和注册方式,从返回字典到直接注册为子模块。虽然新测试验证了注意力实例数量,但对所有模型类型的覆盖可能不完全。
  2. 张量并行兼容性:sink 参数使用 sharded_weight_loader(0) 进行分片,但 PR 中提到 'only one GPU was available. Worth running test_distributed-style coverage before merge',表明多 GPU 场景未充分测试。
  3. 性能影响:check_sinks 函数在每次注意力前向传播时运行,虽然开销很小(仅属性检查和条件判断),但在高频调用路径上仍需关注。
  4. MLA 模型兼容性:修复确保 sink 仅添加到非 MLA 分支,但需要验证所有 MLA 模型(如 DeepSeek-V2/V3)在后端仍能正常工作。
  5. 依赖变更:依赖 AttentionFuser.sinks() 方法的新实现和 has_sink 属性的存在,如果上游 Attention 类未按预期实现这些接口,可能导致运行时错误。

用户影响:

  • 直接影响:所有使用 Transformers 后端和可学习注意力 sink 的模型(如 GPT-OSS、GraniteSWA、GraniteMoeSWA、DeepSeek V4、MiMo-V2-Flash)的用户将获得正确输出。修复前这些模型在后端上输出垃圾内容,修复后输出正确结果。
  • 间接影响:所有使用 Transformers 后端的模型都将受益于注意力实例注册为子模块的改进,确保权重加载后处理正确运行,解决了潜在的静默错误。
    系统影响:

  • 模块化改进:将注意力实例从普通字典改为注册为 nn.ModuleList,提高了与 PyTorch 模块系统的集成度,为未来的 torch.compile 优化铺平道路。

  • 错误处理:添加的防御性检查提高了系统的健壮性,防止静默错误导致难以调试的问题。
    团队影响:

  • 开发流程:新测试 test_sinks 和对 check_implementation 的增强为未来回归提供了防护,减少了手动验证需求。

  • 架构演进:为 AttentionFuser 增加 sink 支持为统一处理 scaling 和 s_aux 参数奠定了基础,与 PR #54941 的 AttentionFuser 工作方向一致。
核心路径变更 多 GPU 测试不足 运行时检查开销

关联 Issue

#52158 [Bugfix] Apply logits scaling in the Transformers backend

完整报告

参与讨论