images: stop video's flash-attn backend leaking to image models (Z-Image attn_mask crash)

diffusers' Model.set_attention_backend() doesn't just set per-processor backends
— it ALSO flips a process-wide active backend (attention_dispatch's
_active_backend). The video path sets that to flash-attn for the Wan transformer;
image and video share the nvidia-engine process, so the global stayed flash and
leaked to the next image model. Z-Image's transformer sets no backend of its own
(passes backend=None → uses the global) and its attention is masked, so it
crashed with "`attn_mask` is not supported for flash-attn 2" → image/environment
generation 400. reset_attention_backend() clears per-processor backends but NOT
the global, so it didn't help.

Fix: restore the diffusers global backend to the env default (native/SDPA)
(a) before every image generation — bulletproof against a leaked flash backend —
and (b) in the video pipeline teardown (_free_pipeline_vram), so it can't persist
after a video pipe is freed. Masked image attention (SDPA) now always works; the
video transformer keeps its own per-processor backend.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RdMufYvtTbtGDWsiZVoXce
parent e300e9c4
...@@ -967,6 +967,23 @@ async def _generate_with_diffusers(pipeline, request, global_args, http_request= ...@@ -967,6 +967,23 @@ async def _generate_with_diffusers(pipeline, request, global_args, http_request=
except Exception as _ip_err: except Exception as _ip_err:
print(f"Warning: IP-Adapter injection failed ({_ip_err}), continuing without character refs") print(f"Warning: IP-Adapter injection failed ({_ip_err}), continuing without character refs")
# Reset the diffusers GLOBAL attention backend to the environment default
# (native/SDPA) before generating. diffusers' Model.set_attention_backend()
# ALSO flips a process-wide active backend, and the video path sets it to
# flash-attn — which then leaks to image transformers that don't set their own
# (e.g. Z-Image passes backend=None → uses the global) and crashes with
# "`attn_mask` is not supported for flash-attn 2". Image + video share the
# engine process, so restore the default here so masked image attention (SDPA)
# always works. Cheap + idempotent; no-op if diffusers lacks the dispatcher.
try:
from diffusers.models.attention_dispatch import (
_AttentionBackendRegistry, AttentionBackendName)
from diffusers.utils.constants import DIFFUSERS_ATTN_BACKEND
_AttentionBackendRegistry.set_active_backend(
AttentionBackendName(DIFFUSERS_ATTN_BACKEND))
except Exception:
pass
try: try:
result = await asyncio.to_thread(pipeline, **call_kwargs) result = await asyncio.to_thread(pipeline, **call_kwargs)
except TaskCancelled: except TaskCancelled:
......
...@@ -754,6 +754,18 @@ def _free_pipeline_vram(pipe) -> None: ...@@ -754,6 +754,18 @@ def _free_pipeline_vram(pipe) -> None:
_c.reset_attention_backend() _c.reset_attention_backend()
except Exception: except Exception:
pass pass
# reset_attention_backend() clears per-processor backends but NOT the
# process-wide active backend that set_attention_backend() flipped to
# flash — restore the env default so it can't leak to a later image
# model (Z-Image's masked attention rejects flash-attn 2).
try:
from diffusers.models.attention_dispatch import (
_AttentionBackendRegistry, AttentionBackendName)
from diffusers.utils.constants import DIFFUSERS_ATTN_BACKEND
_AttentionBackendRegistry.set_active_backend(
AttentionBackendName(DIFFUSERS_ATTN_BACKEND))
except Exception:
pass
try: try:
_unload_video_loras(pipe) _unload_video_loras(pipe)
except Exception: except Exception:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment