From a5e03e2f730ad7a9fee0991c4233ef66806557df Mon Sep 17 00:00:00 2001 From: Solaris-star <820622658@qq.com> Date: Mon, 20 Jul 2026 23:16:46 +0800 Subject: [PATCH 1/2] fix: do not leak set_attention_backend into process-wide registry Model.set_attention_backend() already pins the backend on each of this model's attention processors. Flipping _AttentionBackendRegistry's active backend made other models with unset per-module backends pick up the same backend (e.g. FA3 + attn_mask on an unrelated model). Leave the global active backend alone; use attention_backend() when a temporary process-wide override is intended. Fixes #14249 Signed-off-by: Solaris-star <820622658@qq.com> --- src/diffusers/models/modeling_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/diffusers/models/modeling_utils.py b/src/diffusers/models/modeling_utils.py index d79994de6efb..2e21a3594fde 100644 --- a/src/diffusers/models/modeling_utils.py +++ b/src/diffusers/models/modeling_utils.py @@ -644,8 +644,10 @@ def set_attention_backend(self, backend: str) -> None: continue processor._attention_backend = backend - # Important to set the active backend so that it propagates gracefully throughout. - _AttentionBackendRegistry.set_active_backend(backend) + # Only pin the backend on this model's attention modules. + # Do not flip the process-wide active backend: that would leak into + # other models whose per-module backend is still unset (see #14249). + # Use `attention_backend(...)` when a temporary global override is needed. def reset_attention_backend(self) -> None: """ From 01dbfd8716171dcde4f31aedebd3bd2f2ec2f828 Mon Sep 17 00:00:00 2001 From: Solaris-star <820622658@qq.com> Date: Mon, 20 Jul 2026 23:19:20 +0800 Subject: [PATCH 2/2] test: assert set_attention_backend does not mutate global registry Regression coverage for #14249 / #14250. Signed-off-by: Solaris-star <820622658@qq.com> --- .../test_attention_backend_isolation.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/models/test_attention_backend_isolation.py diff --git a/tests/models/test_attention_backend_isolation.py b/tests/models/test_attention_backend_isolation.py new file mode 100644 index 000000000000..b202bbfa9d14 --- /dev/null +++ b/tests/models/test_attention_backend_isolation.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# Copyright 2026 The HuggingFace Team. +# +# Licensed under the Apache License, Version 2.0. + +"""Unit tests for attention backend process isolation.""" + +from diffusers.models.attention_dispatch import ( + AttentionBackendName, + _AttentionBackendRegistry, +) +from diffusers.models.modeling_utils import ModelMixin +from diffusers.models.attention_processor import Attention + + +class _TinyAttentionModel(ModelMixin): + def __init__(self): + super().__init__() + # Minimal Attention module so set_attention_backend has a processor to stamp. + self.attn = Attention(query_dim=16, heads=2, dim_head=8) + + +def test_set_attention_backend_does_not_mutate_process_global_registry(): + """model.set_attention_backend must not leak into the process-global registry.""" + initial_backend, _ = _AttentionBackendRegistry.get_active_backend() + model = _TinyAttentionModel() + + try: + # Pick a backend different from the current global default when possible. + target = AttentionBackendName.NATIVE + if initial_backend == target: + # Still exercise the API; isolation is what we assert. + pass + model.set_attention_backend(target.value) + active_backend, _ = _AttentionBackendRegistry.get_active_backend() + assert active_backend == initial_backend, ( + "set_attention_backend must not change the process-global active backend; " + f"expected {initial_backend}, got {active_backend}" + ) + # Per-module processor should still be stamped. + assert model.attn.processor._attention_backend == target + finally: + model.reset_attention_backend() + _AttentionBackendRegistry.set_active_backend(initial_backend)