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: """ 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)