Prhub

#35796 [diffusion] fall back to a component's default attention backend

原始 PR 作者 lgy1027 合并时间 2026-08-21 22:56 文件变更 21 提交数 3 评论 3 代码增减 +462 / -63

执行摘要

全局 attention-backend 对辅助组件回退默认后端

Issue #35743 报告:传入全局 --attention-backend sage_attn 时,即使 MiniMax-H3 音频 VAE 的注意力层声明了 supported_attention_backends={FA, TORCH_SDPA} 与 default_attention_backend=TORCH_SDPA,selector 仍因 selection_is_explicit 为真而拒绝回退,抛出 ValueError: Attention backend 'sage_attn' is not supported by this attention layer,最终导致 Rank 0 scheduler is dead。Issue 原文指出:“Not honouring an explicit request silently is reasonable; the problem is that a global flag is treated as an explicit request for every component, including ones that cannot possibly satisfy it and that ship a declared default for that situation.”

值得精读。核心价值在于 get_attn_backend() 对“全局 CLI 选择 vs 组件级 override”的语义区分,以及 is_cross_attention 角色在多层注意力封装间的显式传播——这是多组件扩散管线中后端选择与安全回退的典型设计模式。建议重点阅读 selector.py 的回退分支和 component_loader.py 的上下文传递,以及新增的交叉注意力角色测试,理解 fail-closed 稀疏拒绝的取舍。

讨论亮点

该 PR 没有正式的 review 评论线程,PR 评论与 Issue 评论区有 3 条有效讨论,集中在 CI 状态归因上:

  • 作者 lgy1027 报告 NPU job 失败:若干既有 diffusion 用例超过性能基线,且日志显示 /usr/local/cuda/bin/nvcc 不可用导致 CUDA 快路径被禁用;模型仍正常选择 torch_sdpa,没有任何 attention-backend 错误。
  • 作者补充确认 NPU runner 还出现容器执行失败(self-hosted runner 基础设施问题),请求图片生成接口返回 200,后端日志显示正常选型,因此判断为基础设施/基线问题而非本 PR 回归。
  • 维护者 mickqian 触发 /tag-and-rerun-ci,作者请求补上 run-ci-extra 标签以通过额外门禁。

实现拆解

  1. 选择器契约扩展(python/sglang/multimodal_gen/runtime/layers/attention/selector.pyComponentAttnBackendContext 新增 allow_global_backend_fallback 字段,并新增 _component_allows_global_backend_fallback() 帮助函数;get_attn_backend() 引入 selected_from_global_cli 标志,在来自全局 CLI 的后端不受支持时新增 global backend fallback 回退分支;候选序列改为 default_attention_backend -> None -> 组件支持集合;同时在候选评估中对 is_cross_attention=True 且后端为 sparse 的候选直接拒绝(fail-closed)。这是整个回退机制的核心,影响所有 diffusion 组件的注意力后端解析。
  2. 组件加载器链路(runtime/loader/component_loaders/component_loader.pyComponentLoader 新增类属性 allow_global_attention_backend_fallback = True,而 GenericComponentLoader 显式置为 False,即未知 out-of-tree 组件默认不允许回退;_load_customized_with_context_load_native_with_contextload_component 均增加对应参数并传入 component_attn_backend_context_manager,让组件构造期间的所有 get_attn_backend() 调用都能感知该开关。
  3. 管线装配解耦(runtime/pipelines_core/composed_pipeline_base.pyload_modules 移除手动 component_attn_backend_context_manager 包装,改为把 component_attn_backendcomponent_attn_name 透传给 PipelineComponentLoader.load_component,由加载器统一创建上下文,职责更清晰。
  4. 交叉注意力角色传播layer.pyLocalAttention/USPAttentionis_cross_attention 传入 get_attn_backend()mova_dual_tower.pyltx_2.py、SANA WM、Cosmos3、Helios 等模型的注意力构造处标记条件/上下文注意力为交叉注意力;ltx_2_connector.py 删除未使用的 USPAttention 实例。
  5. VAE 后端约束与测试+文档:MiniMax-H3 的 video VAE 注意力层限制到已验证的 FA/Torch SDPA 后端;新增 test_cross_attention_backend_roles.py,扩展 test_attention_backend_selector.py(覆盖显式全局回退、辅助组件回退、组件级严格、稀疏交叉注意力拒绝四类场景)与 test_qwen3vl_text.py(辅助组件从全局后端回退),并在文档中说明选择契约。
文件 模块 状态 重要度
python/sglang/multimodal_gen/runtime/layers/attention/selector.py 注意力选择器 modified 7.43
python/sglang/multimodal_gen/runtime/loader/component_loaders/component_loader.py 组件加载器 modified 6.88
python/sglang/multimodal_gen/test/unit/test_cross_attention_backend_roles.py 注意力测试 added 7.14
python/sglang/multimodal_gen/test/unit/test_attention_backend_selector.py 选择器测试 modified 7.02
python/sglang/multimodal_gen/test/unit/test_qwen3vl_text.py 文本编码测试 modified 6.49
python/sglang/multimodal_gen/runtime/pipelines_core/composed_pipeline_base.py 管线装配 modified 5.98
python/sglang/multimodal_gen/runtime/models/adapter/ltx_2_connector.py 模型适配器 modified 5.81
python/sglang/multimodal_gen/runtime/models/vaes/minimax_h3_video_vae/attention.py VAE 注意力 modified 5.72

关键符号

get_attn_backend _component_allows_global_backend_fallback ComponentLoader._load_customized_with_context ComponentLoader._load_native_with_context ComponentLoader.load_component composed_pipeline_base.load_modules test_local_attention_forwards_cross_attention_role test_ltx2_derives_cross_attention_role_from_context test_mova_bridge_marks_conditional_attention_as_cross_attention test_explicit_global_backend_uses_component_default test_explicit_global_backend_falls_back_for_auxiliary_component test_explicit_component_backend_remains_strict test_sparse_backend_falls_back_for_unconstrained_cross_attention test_qwen3vl_auxiliary_component_falls_back_from_global_backend

关键源码片段

python/sglang/multimodal_gen/runtime/loader/component_loaders/component_loader.py core-logic

确定回退作用域:注册组件默认允许回退,未知 out-of-tree 组件默认严格,并把开关传递给注意力后端上下文的入口。

class ComponentLoader(ABC):
    # --attention-backend 主要面向 DiT 主模型;辅助组件在全局选择
    # 不受支持时允许回退到声明的默认后端或平台兼容后端。
    # 子类可以显式收紧该开关。
    allow_global_attention_backend_fallback = True
​
    def _load_customized_with_context(
        self,
        component_model_path,
        server_args,
        component_name: str,
        attn_backend: Any,
        component_attn_name: str | None,
        allow_global_backend_fallback: bool,
    ) -> AutoModel:
        with component_attn_backend_context_manager(
            attn_backend,
            component_name=component_attn_name,
            allow_global_backend_fallback=allow_global_backend_fallback,
        ):
            # 组件构造期间所有 get_attn_backend() 调用都会看到该上下文
            load_kwargs = self.customized_load_kwargs_for_component(
                server_args, component_name
            )
            return self.load_customized(
                component_model_path, server_args, component_name, load_kwargs
            )
​
    def _load_native_with_context(
        self,
        transformers_or_diffusers: str,
        attn_backend: Any,
        component_attn_name: str | None,
        allow_global_backend_fallback: bool,
    ) -> AutoModel:
        with component_attn_backend_context_manager(
            attn_backend,
            component_name=component_attn_name,
            allow_global_backend_fallback=allow_global_backend_fallback,
        ):
            # 原生 transformers/diffusers 路径同样在上下文中完成加载
            return self.load_native(
                component_model_path, server_args, component_name, ...
            )
​
    def load_component(self, ...):
        try:
            with component_attn_backend_context_manager(
                component_attn_backend,
                component_name=component_attn_name,
                allow_global_backend_fallback=(
                    loader.allow_global_attention_backend_fallback
                ),
            ):
                return loader.load(
                    component_model_path,
                    server_args,
                    component_name,
                    transformers_or_diffusers,
                )
        except Exception as e:
            # 保留原有异常归一化逻辑
            ...
​
​
class GenericComponentLoader(ComponentLoader):
    # 未知的 out-of-tree 组件本身也可能是主 transformer,
    # 必须通过注册的 loader 显式 opt-in 才允许回退;
    # 默认保持与 DiT 相同的严格语义,避免静默掩盖配置错误。
    allow_global_attention_backend_fallback = False
python/sglang/multimodal_gen/test/unit/test_cross_attention_backend_roles.py test-coverage

新增测试文件,锁定 LocalAttention/USPAttention 在 LTX-2 与 MOVA 桥接中的交叉注意力角色传递行为,防止后续回归。

from unittest import mockimport pytest
import torch
from torch import nnfrom sglang.multimodal_gen.runtime.layers.attention import layer as attention_layer
from sglang.multimodal_gen.runtime.models.bridges import mova_dual_tower
from sglang.multimodal_gen.runtime.models.dits import ltx_2
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
​
​
class _FakeAttentionImpl(nn.Module):
    # 仅用于隔离后端实现,避免真实内核参与单测
    def __init__(self, **_kwargs) -> None:
        super().__init__()
​
​
class _FakeAttentionBackend:
    @classmethod
    def get_enum(cls) -> AttentionBackendEnum:
        return AttentionBackendEnum.FA
​
    @classmethod
    def get_impl_cls(cls):
        return _FakeAttentionImpl
​
​
def test_local_attention_forwards_cross_attention_role():
    # LocalAttention 必须把 is_cross_attention 传给 get_attn_backend,
    # 否则稀疏回退与角色判定会失真
    with (
        mock.patch.object(attention_layer, "get_compute_dtype", return_value=torch.bfloat16),
        mock.patch.object(attention_layer, "get_attn_backend", return_value=_FakeAttentionBackend) as get_backend,
        mock.patch.object(attention_layer, "wrap_attention_impl_forward"),
    ):
        attention_layer.LocalAttention(
            num_heads=1,
            head_size=64,
            is_cross_attention=True,
        )
​
    assert get_backend.call_args.kwargs["is_cross_attention"] is True
​
​
@pytest.mark.parametrize("use_local_attention", [False, True])
def test_ltx2_derives_cross_attention_role_from_context(use_local_attention):
    # 无论走 LocalAttention 还是 USPAttention,LTX-2 都应从
    # context_dim 是否出现推导出 is_cross_attention 角色
    selected_layer = "LocalAttention" if use_local_attention else "USPAttention"
    with (
        mock.patch.object(ltx_2, "get_tp_world_size", return_value=1),
        mock.patch.object(ltx_2, "ColumnParallelLinear", return_value=nn.Identity()),
        mock.patch.object(ltx_2, "RowParallelLinear", return_value=nn.Identity()),
        mock.patch.object(ltx_2, selected_layer) as attention,
    ):
        # 带 context_dim 的构造:交叉注意力
        ltx_2.LTX2Attention(
            query_dim=8,
            context_dim=8,
            heads=1,
            dim_head=8,
            use_local_attention=use_local_attention,
        )
        cross_attention_kwargs = attention.call_args.kwargs
        attention.reset_mock()
        # 不带 context_dim 的构造:自注意力
        ltx_2.LTX2Attention(
            query_dim=8,
            heads=1,
            dim_head=8,
            use_local_attention=use_local_attention,
        )
        self_attention_kwargs = attention.call_args.kwargs
​
    assert cross_attention_kwargs["is_cross_attention"] is True
    assert self_attention_kwargs["is_cross_attention"] is False

评论区精华

NPU job 失败是否属于本 PR 回归 测试

lgy1027 反馈 NPU job 中多个既有 diffusion 用例超过性能基线,日志显示 /usr/local/cuda/bin/nvcc 不可用导致 CUDA 快路径被禁用;模型仍正常选择 torch_sdpa,且新增 selector 测试无失败。作者后续补充:NPU runner 还报容器执行失败(self-hosted runner 基础设施问题),请求接口返回 200。

结论:作者判断为基础设施与基线问题,非本 PR 引入的回归;单元测试与 GPU multimodal job 均通过,PR 最终合入。 · 已解决

额外 CI 门禁与 rerun other

维护者 mickqian 触发 /tag-and-rerun-ci;lgy1027 说明需要 run-ci-extra 标签以通过额外门禁。

结论:CI 重跑完成,额外门禁通过后 PR 合入。 · 已解决

风险与影响

  1. 核心选择器逻辑变更get_attn_backend() 是 diffusion 所有注意力后端的统一入口,回退候选顺序与 selected_from_global_cli 分支的改变可能影响 DiT 主模型的后端解析;虽然用条件分支隔离了全局 CLI 与组件级 override,仍需关注混合加载管线。
  2. 跨 21 文件回归面大:改动波及 MOVA、Cosmos3、Helios、LTX-2、SANA WM 等模型与多个 VAE,任何一处的 is_cross_attention 标记遗漏都可能导致后端选择错误或稀疏回退误判。
  3. fail-closed 拒绝稀疏交叉注意力:即使 layer 未声明 supported 集合,sparse 后端也会被拒绝,若某模型实际依赖稀疏 kernel 做交叉注意力且未声明,会直接失败(这是有意设计,但属于行为变更)。
  4. NPU 性能基线失败未完全归因:作者判断为环境(nvcc 缺失、容器失败)与基线问题,但该环境变量组合下回退逻辑仍可能产生与 CUDA 设备不同的行为,需要后续持续观察。
  5. GenericComponentLoader 行为收紧:未知 out-of-tree 组件默认 allow_global_attention_backend_fallback=False,之前可用的全局后端现在可能直接报错,属于有意的契约收紧,但对外部自定义组件是潜在破坏性变更。

用户影响:全局 --attention-backend 不再对辅助组件“一票否决”,MiniMax-H3 等混合组件管线的服务可用性显著提升;未知 out-of-tree 组件保持严格失败语义,契约更清晰。系统影响:注意力后端选择逻辑多了一级上下文开关与回退顺序,组件加载上下文从管线装配层移到加载器内部,后续新增 loader 需要理解并正确传递 allow_global_attention_backend_fallback团队影响:选择契约被文档化,交叉注意力角色成为后端判定的正式输入,降低后续模型接入成本;21 个文件的改动需要维护者在合并后留意扩散类 issue 的回归报告。

核心选择器逻辑变更 跨 21 文件回归面大 NPU 性能基线失败未归因 fail-closed 拒绝稀疏交叉注意力 GenericComponentLoader 行为收紧

关联 Issue

#35743 [Bug] A global --attention-backend kills the server when a non-DiT component does not declare it (MiniMax-H3 audio_vae)

完整报告

参与讨论