From c2efc7f9c2fe36d59cee4aa1f5d3f24946b2c325 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Sat, 3 Oct 2026 21:55:06 +0200 Subject: [PATCH 1/9] feat(attention): add opt-in SageAttention backend for diffusion models New `attention_backend: auto | sage` setting; with `sage`, eligible PyTorch SDPA calls inside a denoising scope run on SageAttention 2.2 and every other call keeps SDPA unchanged. Each GPU's first SageAttention call is checked against SDPA and a failing GPU falls back for the session; no model family enters the scope yet. Adds the SageAttention docs page and the regenerated config schema. --- .../Optimization/sage-attention.mdx | 124 ++++ docs/src/content/docs/troubleshooting/faq.mdx | 2 +- docs/src/generated/settings.json | 14 + invokeai/app/run_app.py | 2 +- .../app/services/config/config_default.py | 3 + invokeai/app/util/startup_utils.py | 12 +- invokeai/backend/util/sage_attention.py | 388 +++++++++++ invokeai/frontend/api/openapi.json | 9 +- invokeai/frontend/api/schema.ts | 8 + ...image_first_block_outlier_head.safetensors | Bin 0 -> 197240 bytes tests/backend/util/test_sage_attention.py | 635 ++++++++++++++++++ tests/test_config.py | 10 + 12 files changed, 1202 insertions(+), 5 deletions(-) create mode 100644 docs/src/content/docs/configuration/Optimization/sage-attention.mdx create mode 100644 invokeai/backend/util/sage_attention.py create mode 100644 tests/backend/util/data/qwen_image_first_block_outlier_head.safetensors create mode 100644 tests/backend/util/test_sage_attention.py diff --git a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx new file mode 100644 index 00000000000..5b5aea9df78 --- /dev/null +++ b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx @@ -0,0 +1,124 @@ +--- +title: SageAttention +sidebar: + order: 5 +--- + +import { Steps } from '@astrojs/starlight/components'; + +[SageAttention](https://github.com/thu-ml/SageAttention) is a faster attention kernel for NVIDIA GPUs. It quantizes the +attention inputs to 8 bits inside the kernel. On an RTX 4090 under Windows that makes the attention of the image and +video models two to three times faster than PyTorch's own kernels. How much of a generation that saves depends on how +much of each step is attention: it grows with resolution and video length. + +It is **opt-in**. Because the kernel is quantized, an image made with SageAttention is not pixel-identical to one made +without it at the same seed — composition details can shift the way they do between two GPU models. In a blind +side-by-side comparison of 48 image pairs from six image model families, no pair was judged worse with SageAttention; +the video models were checked against PyTorch's attention for picture changes and frame-to-frame flicker. + +## Supported models + +SageAttention is enabled per model family once that family has been measured with it. No model family uses it yet, +so every model keeps PyTorch's attention. + +## Requirements + +- **An NVIDIA GPU with compute capability 8.0 or newer** — RTX 30-series, RTX 40-series, RTX 50-series, A100, H100. + InvokeAI has measured it on an RTX 4090; the other generations use SageAttention's own kernels for them, which + InvokeAI has not validated. +- **SageAttention 2.2, installed separately.** InvokeAI was validated with 2.2.0. The `sageattention` package on PyPI is + the older SageAttention 1 and is not used. On Windows it also needs the `triton-windows` package. +- Not available on AMD (ROCm), Apple Silicon (MPS), Intel (XPU) or CPU. There InvokeAI keeps PyTorch's attention. + +## Installing + +SageAttention goes into the Python environment InvokeAI runs from — the `.venv` folder in your install directory. The +commands use [uv](https://docs.astral.sh/uv/), which InvokeAI's environment is managed with; install it first if +`uv --version` does not work in your terminal. + +### Windows + + +1. Open a terminal in your InvokeAI install directory. +2. Install Triton for Windows, matching the Triton version of InvokeAI's PyTorch (Triton 3.7 for PyTorch 2.13): + + ```powershell + uv pip install --python .venv\Scripts\python.exe "triton-windows>=3.7,<3.8" + ``` + +3. Install a SageAttention 2.2 wheel from the [SageAttention for Windows releases](https://github.com/woct0rdho/SageAttention/releases) + that matches InvokeAI's PyTorch (CUDA 13, PyTorch 2.10 or newer), without letting it touch PyTorch: + + ```powershell + uv pip install --python .venv\Scripts\python.exe --no-deps "https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post6/sageattention-2.2.0%2Bcu130torch2.10.0andhigher.post6-cp310-abi3-win_amd64.whl" + ``` + + +### Linux + +SageAttention 2 has no prebuilt wheels for Linux. Build it from source into InvokeAI's environment +(`.venv/bin/python`) as described in [SageAttention's installation instructions](https://github.com/thu-ml/SageAttention#installation), +with the CUDA toolkit that matches InvokeAI's PyTorch (CUDA 13) installed. InvokeAI's environment has no `pip`; use +`uv pip install --python .venv/bin/python ...` where those instructions say `pip install`. Triton ships with PyTorch on +Linux. + +:::caution[Keep it matched to InvokeAI's PyTorch] +SageAttention is built against PyTorch, and Triton must match PyTorch's version. When an update moves InvokeAI to a new +PyTorch, install the matching `triton-windows` again on Windows, and on Linux rebuild SageAttention. A wheel for a +different CUDA major version, or a repair that re-creates the environment, needs a matching SageAttention too. Until +then InvokeAI logs why it cannot use it and generates with PyTorch's attention. +::: + +## Enabling + +Set the backend in your [`invokeai.yaml`](/configuration/invokeai-yaml/) and restart InvokeAI: + +```yaml +attention_backend: sage +``` + +The startup log confirms it: + +``` +SageAttention 2.2.0+cu130torch2.10.0andhigher.post6 enabled for the attention inside supported diffusion models ... +``` + +and the first generation that uses it names the GPU and the kernel: + +``` +SageAttention 2.2.0+cu130torch2.10.0andhigher.post6 serves diffusion-model attention on cuda:0 (NVIDIA GeForce RTX 4090, sm_89, sageattn_qk_int8_pv_fp8_cuda). +``` + +If SageAttention cannot be used, the startup log says why — package missing, Triton missing, GPU too old — and +InvokeAI generates with PyTorch's attention as usual. With `log_level: debug`, every generation logs how many attention +calls ran on SageAttention and why the others did not. To go back, set `attention_backend: auto`. + +## What stays on PyTorch's attention + +SageAttention serves the attention inside the denoising model of the families under [Supported models](#supported-models). +Everything else keeps PyTorch's attention: + +- **Text encoders, image encoders and VAEs.** They run outside the denoising loop. +- **Masked attention**, such as regional prompts. SageAttention has no mask input. +- **Short sequences** — fewer than 1536 query or 512 key tokens. There the cost of quantizing the inputs outweighs the + faster kernel. +- **Head sizes other than 64 and 128.** +- **Models not listed under Supported models**, such as SD 1.5 and SD 2. + +Pinning a PyTorch kernel through diffusers' `DIFFUSERS_ATTN_BACKEND` does not stop SageAttention; set +`attention_backend: auto` to measure PyTorch's own kernels. + +## Notes + +- **The first generation pauses briefly.** SageAttention compiles a few Triton kernels on first use, about two seconds + on an RTX 4090; they are cached on disk afterwards. +- **Each GPU's first SageAttention call is checked against PyTorch's attention.** If the result is wrong or the kernel + fails, InvokeAI logs a warning once and that GPU uses PyTorch's attention until restart. A fault the GPU raises + later, while running a kernel, cannot be caught that way and ends the generation. +- **Memory.** Peak VRAM was unchanged for every measured model. Each call holds quantized copies of its inputs, about + 1.7 bytes per element of the query, key and value together — around 70 MB for FLUX.1 at 1024×1024, and around 2 GB + per call for Wan 2.2 14B at 1280×720 with 81 frames (not measured). +- **Recall.** `attention_backend` is not stored in an image's metadata, so an image recalled after switching it is + generated slightly differently. +- **Black or noisy images** have been reported with SageAttention for some models in other applications. None appeared + in InvokeAI's tests; if you see one, set `attention_backend: auto` and report it with the model and settings. diff --git a/docs/src/content/docs/troubleshooting/faq.mdx b/docs/src/content/docs/troubleshooting/faq.mdx index bdd339130bb..5b95ff6882a 100644 --- a/docs/src/content/docs/troubleshooting/faq.mdx +++ b/docs/src/content/docs/troubleshooting/faq.mdx @@ -44,7 +44,7 @@ Follow the same steps to scan and import the missing models. ## Triton error on startup -This can be safely ignored. Invoke doesn't use Triton, but if you are on Linux and wish to dismiss the error, you can install Triton. +This can be safely ignored. Invoke only uses Triton for [SageAttention](/configuration/optimization/sage-attention/), which is off unless you enable it. If you are on Linux and wish to dismiss the error, you can install Triton. ## Unable to Copy on Firefox diff --git a/docs/src/generated/settings.json b/docs/src/generated/settings.json index 58f45af1930..ef3a8e952a6 100644 --- a/docs/src/generated/settings.json +++ b/docs/src/generated/settings.json @@ -728,6 +728,20 @@ "type": "typing.Literal['auto', 'balanced', 'max', 1, 2, 3, 4, 5, 6, 7, 8]", "validation": {} }, + { + "category": "GENERATION", + "default": "auto", + "description": "Attention kernel for the attention inside diffusion models. `auto` lets PyTorch choose among its exact SDPA kernels and never selects a quantized one. `sage` uses SageAttention 2 for the models listed in the SageAttention docs: faster, most at high resolutions and for video, but quantized, so images differ slightly from `auto` at the same seed. SageAttention is installed separately and needs an NVIDIA GPU with compute capability 8.0 or newer; masked attention, text encoders and VAEs keep PyTorch SDPA.", + "env_var": "INVOKEAI_ATTENTION_BACKEND", + "literal_values": [ + "auto", + "sage" + ], + "name": "attention_backend", + "required": false, + "type": "typing.Literal['auto', 'sage']", + "validation": {} + }, { "category": "GENERATION", "default": false, diff --git a/invokeai/app/run_app.py b/invokeai/app/run_app.py index 4e513d210a8..6a7d4c0b616 100644 --- a/invokeai/app/run_app.py +++ b/invokeai/app/run_app.py @@ -103,7 +103,7 @@ def run_app() -> None: logger.warning(f"Port {orig_config_port} is already in use. Using port {app_config.port}.") # Miscellaneous startup tasks. - apply_monkeypatches() + apply_monkeypatches(app_config.attention_backend) register_mime_types() check_cudnn(logger) check_cuda_build_compatibility(logger) diff --git a/invokeai/app/services/config/config_default.py b/invokeai/app/services/config/config_default.py index b8af9900930..43728227998 100644 --- a/invokeai/app/services/config/config_default.py +++ b/invokeai/app/services/config/config_default.py @@ -28,6 +28,7 @@ LEGACY_INIT_FILE = Path("invokeai.init") PRECISION = Literal["auto", "float16", "bfloat16", "float32"] ATTENTION_TYPE = Literal["auto", "normal", "xformers", "sliced", "torch-sdp"] +ATTENTION_BACKEND = Literal["auto", "sage"] NOISE_DTYPE = Literal["float32", "float16"] ATTENTION_SLICE_SIZE = Literal["auto", "balanced", "max", 1, 2, 3, 4, 5, 6, 7, 8] LOG_FORMAT = Literal["plain", "color", "syslog", "legacy"] @@ -126,6 +127,7 @@ class InvokeAIAppConfig(BaseSettings): pid_memory_optimization: Enable experimental PiD decode memory optimizations. Roughly halves the peak activation memory of a PiD decode; in exchange the decoded image changes slightly, because neither the chunked pixel pathway nor the float32 sampler intermediates are bit-exact with the default path. attention_type: Attention type.
Valid values: `auto`, `normal`, `xformers`, `sliced`, `torch-sdp` attention_slice_size: Slice size, valid when attention_type=="sliced".
Valid values: `auto`, `balanced`, `max`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8` + attention_backend: Attention kernel for the attention inside diffusion models. `auto` lets PyTorch choose among its exact SDPA kernels and never selects a quantized one. `sage` uses SageAttention 2 for the models listed in the SageAttention docs: faster, most at high resolutions and for video, but quantized, so images differ slightly from `auto` at the same seed. SageAttention is installed separately and needs an NVIDIA GPU with compute capability 8.0 or newer; masked attention, text encoders and VAEs keep PyTorch SDPA.
Valid values: `auto`, `sage` force_tiled_decode: Whether to enable tiled VAE decode (reduces memory consumption with some performance penalty). A tiled decode is not pixel-identical to a single-pass one: a VAE decoder normalises and attends over the whole image, so the difference is spread across it rather than confined to the tile seams. Also tiles the encode. Read by the SD1.5/SDXL, FLUX.1, Z-Image and Qwen-Image VAE nodes, the Anima encode and the Krea-2 style reference; other model families ignore it. auto_tiled_decode: Decode large images in tiles when an untiled decode's estimated working memory would take most of the GPU's memory (FLUX.1, Z-Image, Qwen-Image, Krea-2 and Wan decodes). Turn off to decode untiled unless tiling is requested; the FLUX.1 and Z-Image decodes then still retry tiled after running out of memory. Anima keeps tiling a decode too large for its GPU either way, because tiling is what makes that case fast there. pil_compress_level: The compress_level setting of PIL.Image.save(), used for PNG encoding. All settings are lossless. 0 = no compression, 1 = fastest with slightly larger filesize, 9 = slowest with smallest filesize. 1 is typically the best setting. @@ -252,6 +254,7 @@ class InvokeAIAppConfig(BaseSettings): pid_memory_optimization: bool = Field(default=False, description="Enable experimental PiD decode memory optimizations. Roughly halves the peak activation memory of a PiD decode; in exchange the decoded image changes slightly, because neither the chunked pixel pathway nor the float32 sampler intermediates are bit-exact with the default path.") attention_type: ATTENTION_TYPE = Field(default="auto", description="Attention type.") attention_slice_size: ATTENTION_SLICE_SIZE = Field(default="auto", description='Slice size, valid when attention_type=="sliced".') + attention_backend: ATTENTION_BACKEND = Field(default="auto", description="Attention kernel for the attention inside diffusion models. `auto` lets PyTorch choose among its exact SDPA kernels and never selects a quantized one. `sage` uses SageAttention 2 for the models listed in the SageAttention docs: faster, most at high resolutions and for video, but quantized, so images differ slightly from `auto` at the same seed. SageAttention is installed separately and needs an NVIDIA GPU with compute capability 8.0 or newer; masked attention, text encoders and VAEs keep PyTorch SDPA.") force_tiled_decode: bool = Field(default=False, description="Whether to enable tiled VAE decode (reduces memory consumption with some performance penalty). A tiled decode is not pixel-identical to a single-pass one: a VAE decoder normalises and attends over the whole image, so the difference is spread across it rather than confined to the tile seams. Also tiles the encode. Read by the SD1.5/SDXL, FLUX.1, Z-Image and Qwen-Image VAE nodes, the Anima encode and the Krea-2 style reference; other model families ignore it.") auto_tiled_decode: bool = Field(default=True, description="Decode large images in tiles when an untiled decode's estimated working memory would take most of the GPU's memory (FLUX.1, Z-Image, Qwen-Image, Krea-2 and Wan decodes). Turn off to decode untiled unless tiling is requested; the FLUX.1 and Z-Image decodes then still retry tiled after running out of memory. Anima keeps tiling a decode too large for its GPU either way, because tiling is what makes that case fast there.") pil_compress_level: int = Field(default=1, description="The compress_level setting of PIL.Image.save(), used for PNG encoding. All settings are lossless. 0 = no compression, 1 = fastest with slightly larger filesize, 9 = slowest with smallest filesize. 1 is typically the best setting.") diff --git a/invokeai/app/util/startup_utils.py b/invokeai/app/util/startup_utils.py index aa88f104b5e..886872790c2 100644 --- a/invokeai/app/util/startup_utils.py +++ b/invokeai/app/util/startup_utils.py @@ -8,6 +8,8 @@ import torch +from invokeai.app.services.config.config_default import ATTENTION_BACKEND + def find_open_port(port: int) -> int: """Find a port not in use starting at given port""" @@ -200,8 +202,8 @@ def enable_dev_reload(custom_nodes_path=None) -> None: jurigged.watch(pattern=paths, logger=InvokeAILogger.get_logger(name="jurigged").info) -def apply_monkeypatches() -> None: - """Apply monkeypatches to fix issues with third-party libraries.""" +def apply_monkeypatches(attention_backend: ATTENTION_BACKEND = "auto") -> None: + """Apply monkeypatches to fix issues with third-party libraries, and install the configured attention backend.""" import invokeai.backend.util.hotfixes # noqa: F401 (monkeypatching on import) @@ -220,6 +222,12 @@ def apply_monkeypatches() -> None: install_rocm_sdpa_guard() + # Opt-in SageAttention for diffusion-model attention. Installs nothing unless asked for, and never on ROCm. + if attention_backend == "sage": + from invokeai.backend.util.sage_attention import install_sage_attention + + install_sage_attention() + def register_mime_types() -> None: """Register additional mime types for windows.""" diff --git a/invokeai/backend/util/sage_attention.py b/invokeai/backend/util/sage_attention.py new file mode 100644 index 00000000000..da590ba1ca9 --- /dev/null +++ b/invokeai/backend/util/sage_attention.py @@ -0,0 +1,388 @@ +"""SageAttention for the attention inside diffusion models, opt-in via ``attention_backend: sage``. + +SageAttention (thu-ml) quantizes Q/K to INT8 and P/V to FP8 or FP16 inside the attention kernel. On an RTX 4090 under +Windows it runs the attention shapes of the image and video transformers 2-3x faster than PyTorch's fastest SDPA kernel +there, and the gap widens with the token count. It is lossy: images differ from SDPA at the same seed (in a blind +comparison of 48 pairs over six architectures, none worse). That is why it is opt-in, and why it only runs where a +denoise invocation asks for it. + +How it is wired: + +* ``install_sage_attention`` rebinds ``torch.nn.functional.scaled_dot_product_attention`` once at startup, like + ``install_rocm_sdpa_guard``. Every caller that resolves the function at call time inherits it: InvokeAI's own + transformer code, diffusers' attention processors and diffusers' ``native`` dispatch backend. diffusers' own + ``sage`` backend is not used: it raises on any mask, is process-global, and never sees the first two routes. +* The wrapper does nothing outside ``sage_attention_scope``. Text encoders, image encoders and VAEs therefore keep + PyTorch's kernels, as do models whose denoise invocation has not opted in. The scope is a ``ContextVar``, so it is + per thread: InvokeAI runs one generation session per GPU concurrently, and a process-global switch would leak + between them (see ``sdpa_scope``). +* Inside the scope, a call goes to SageAttention only when ``_ineligible`` finds nothing wrong with it -- no mask, + a head size the kernels run natively, half precision, sequences long enough to be worth the quantization. + Everything else falls back to PyTorch with its arguments untouched. +""" + +import functools +import inspect +import math +import re +import threading +from collections import Counter +from collections.abc import Callable, Iterator +from contextlib import contextmanager +from contextvars import ContextVar +from dataclasses import dataclass, field +from typing import Any + +import torch + +from invokeai.backend.util.logging import InvokeAILogger + +logger = InvokeAILogger.get_logger(__name__) + +_SENTINEL = "_invokeai_sage_attention" +_MIN_VERSION = (2, 2, 0) +# Head sizes the kernels run natively. Other sizes are zero-padded by SageAttention (40 -> 64, 80 -> 128), which costs +# copies and measured slower than SDPA at the shapes that have them (SD1.5). +_HEAD_DIMS = frozenset({64, 128}) +# Shortest query and key sequences SageAttention is used for. It quantizes Q, K and V in separate kernels first, a cost +# that grows with each sequence's length, while what it saves grows with their product -- so a short side on either +# end loses. Measured on an RTX 4090 against the faster of SDPA's default and cuDNN kernels: keys of 77 or 128 tokens +# run 0.2-0.8x (SDXL cross-attention, LTX-2's audio streams), 256 break even, 512 and longer run 1.4-1.9x; queries of +# 512 or 1024 tokens against 13.7k keys run 0.6x and 1.0x, 1536 and longer 1.4x and up. +_MIN_QUERY_LEN = 1536 +_MIN_KEY_LEN = 512 +_HALF_PRECISION = (torch.float16, torch.bfloat16) +# Largest relative L2 difference from PyTorch SDPA a device's first SageAttention result may have. Real attention +# stays well below it (worst measured: 0.19, in Qwen-Image's 53rd block); a kernel that never ran leaves uninitialized +# memory, which lands near 1 or is not finite. +_FIRST_CALL_MAX_ERROR = 0.5 +# Query rows of the first call that are recomputed with PyTorch for that comparison, from the end so the last, partial +# tile is among them. A kernel that never ran garbles every row, so a slice catches what the whole output would; the +# whole output costs ~14 bytes per element to compare -- about 5 GB for one call of Wan 2.2 14B at 720p. +_FIRST_CALL_ROWS = 1024 + +DOCS_URL = "https://invoke-ai.github.io/InvokeAI-7/configuration/optimization/sage-attention/" + + +@dataclass +class _Usage: + """What one scope's attention calls ran on. Logged at DEBUG when the scope closes.""" + + served: int = 0 + fell_back: Counter[str] = field(default_factory=Counter) + + +_scope: ContextVar[_Usage | None] = ContextVar("invokeai_sage_attention_scope", default=None) +_lock = threading.Lock() +_disabled_devices: set[int] = set() +_validated_devices: set[int] = set() +_announced_devices: set[int] = set() + + +@contextmanager +def sage_attention_scope() -> Iterator[None]: + """Let SageAttention serve the eligible attention calls this thread makes inside the block. + + A no-op unless ``install_sage_attention`` installed the wrapper. Denoise invocations enter it around the + transformer forward passes of architectures whose output was validated against SDPA. + """ + usage = _Usage() + token = _scope.set(usage) + try: + yield + finally: + _scope.reset(token) + if usage.served or usage.fell_back: + fallbacks = ", ".join(f"{reason}={count}" for reason, count in usage.fell_back.most_common()) + logger.debug( + f"SageAttention: {usage.served + sum(usage.fell_back.values())} attention calls, " + f"{usage.served} on SageAttention" + (f"; on PyTorch SDPA: {fallbacks}" if fallbacks else "") + ) + + +def _import_sageattention() -> tuple[str, Any]: + """The only place that touches the optional package. Imported here, never at module import time.""" + from importlib.metadata import version + + import sageattention + + return version("sageattention"), sageattention + + +def _parse_version(version: str) -> tuple[int, int, int] | None: + # Wheels carry local labels: "2.2.0+cu130torch2.10.0andhigher.post6". + match = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", version) + return (int(match[1]), int(match[2]), int(match[3] or 0)) if match else None + + +def _kernel( + sageattention: Any, capability: tuple[int, int], cuda: tuple[int, int] +) -> Callable[..., torch.Tensor] | None: + """SageAttention's kernel for a GPU of this compute capability, or None where it has none. + + The same kernels and accumulation SageAttention 2.2's own ``sageattn`` dispatches to -- including its choice by the + CUDA version torch was built for: the FP16 accumulation inside the FP8 kernels needs CUDA 12.8 to compile, and + below that the build replaces it with a trap -- but called with ``smooth_k`` off, which ``sageattn`` always leaves + on and has no argument to change. Smoothing subtracts K's mean over the sequence before quantizing; in the first + block of Qwen-Image and Krea-2, where one channel of Q and K sits near +600 for every image token, that leaves the + INT8 product Q.K no precision for the rest: cosine 0.18 and 0.53 against a float32 reference, 0.997 and 0.988 with + smoothing off. Everywhere else measured it changes nothing, and off is 2-11 % faster (RTX 4090). Only sm_89 has been + measured in InvokeAI. + """ + major, minor = capability + recent_cuda = cuda >= (12, 8) + if major == 8 and minor != 9: # Ampere: FP16 P.V + kernel, options = sageattention.sageattn_qk_int8_pv_fp16_cuda, {"pv_accum_dtype": "fp32"} + elif (major, minor) == (8, 9): # Ada: FP8 P.V + kernel = sageattention.sageattn_qk_int8_pv_fp8_cuda + options = {"pv_accum_dtype": "fp32+fp16" if recent_cuda else "fp32+fp32"} + elif major == 9: # Hopper + kernel, options = sageattention.sageattn_qk_int8_pv_fp8_cuda_sm90, {"pv_accum_dtype": "fp32+fp32"} + elif (major, minor) in ((10, 0), (12, 0), (12, 1)): # Blackwell + kernel = sageattention.sageattn_qk_int8_pv_fp8_cuda + options = {"qk_quant_gran": "per_warp", "pv_accum_dtype": "fp32+fp16" if recent_cuda else "fp32"} + else: + return None + return functools.partial(kernel, smooth_k=False, **options) + + +def _unaccepted_arguments(kernel: functools.partial) -> list[str]: + """Arguments this kernel would silently swallow: every SageAttention kernel ends in ``**kwargs``, so a renamed + ``smooth_k`` would be ignored rather than rejected, and smoothing would quietly come back.""" + parameters = inspect.signature(kernel.func).parameters + return [name for name in (*kernel.keywords, "tensor_layout", "is_causal", "sm_scale") if name not in parameters] + + +def _cuda_index(query: torch.Tensor, key: torch.Tensor, value: torch.Tensor) -> int | None: + """The CUDA index of q/k/v when all three live on this thread's current device, else None. + + SageAttention launches its kernels on the thread's current device without switching to the tensors' device, so + tensors anywhere else would be read from the wrong GPU. Session workers pin their thread to their own device. + """ + device = query.device + if device.type != "cuda" or key.device != device or value.device != device: + return None + return device.index if device.index == torch.cuda.current_device() else None + + +def _ineligible( + query: torch.Tensor, + key: torch.Tensor, + value: torch.Tensor, + attn_mask: torch.Tensor | None, + dropout_p: float, + is_causal: bool, + enable_gqa: bool, + extra_arguments: bool, +) -> str | None: + """Why this call must stay on PyTorch's SDPA, or None if SageAttention may serve it. + + Reads tensor metadata only: anything that synchronizes the device (inspecting a mask's values, say) would cost + more than the kernel saves. Which device the call runs on is checked by the caller, which needs the index anyway. + """ + if extra_arguments: + return "arguments" + if attn_mask is not None: + return "mask" + if dropout_p != 0.0: + return "dropout" + if is_causal: + # No denoiser in scope attends causally, and torch and SageAttention have not been checked to align a causal + # mask the same way when query and key lengths differ. + return "causal" + if query.dim() != 4 or key.dim() != 4 or value.dim() != 4 or query.is_nested: + return "shape" + if query.dtype not in _HALF_PRECISION or key.dtype != query.dtype or value.dtype != query.dtype: + return "dtype" + batch, heads, query_len, head_dim = query.shape + if head_dim not in _HEAD_DIMS or key.shape[-1] != head_dim or value.shape != key.shape: + return "head_dim" + kv_heads, key_len = key.shape[1], key.shape[2] + if key.shape[0] != batch: + return "shape" + if kv_heads != heads and not (enable_gqa and heads % kv_heads == 0): + return "heads" + if query_len < _MIN_QUERY_LEN or key_len < _MIN_KEY_LEN: + return "short" + if query.stride(-1) != 1 or key.stride(-1) != 1 or value.stride(-1) != 1: + return "layout" + if torch.is_grad_enabled() and (query.requires_grad or key.requires_grad or value.requires_grad): + return "grad" + if torch.is_autocast_enabled("cuda"): + return "autocast" + return None + + +def _check_first_result( + index: int, + original: Callable[..., torch.Tensor], + query: torch.Tensor, + key: torch.Tensor, + value: torch.Tensor, + scale: float | None, + out: torch.Tensor, +) -> None: + """Raise unless a device's first SageAttention result agrees with PyTorch's. + + SageAttention launches its CUDA kernels without checking for errors, so a kernel the installed build lacks for + this GPU raises nothing: it returns uninitialized memory, and the launch error surfaces in some later, unrelated + torch operation -- outside the fallback. Recomputing a slice of the first result with PyTorch, and reading the + comparison back to the host, brings both into the caller's ``try``. Once per device; later calls are not compared. + The slice is the first sample's first K/V head group and its last ``_FIRST_CALL_ROWS`` query rows. + """ + group = query.shape[1] // key.shape[1] + rows = slice(-_FIRST_CALL_ROWS, None) + expected = original(query[:1, :group, rows], key[:1, :1], value[:1, :1], scale=scale, enable_gqa=group > 1) + difference = torch.linalg.vector_norm(out[:1, :group, rows] - expected, dtype=torch.float32) + reference = torch.linalg.vector_norm(expected, dtype=torch.float32).clamp_min(1e-6) + error = (difference / reference).item() + if not math.isfinite(error) or error > _FIRST_CALL_MAX_ERROR: + raise RuntimeError(f"its first result differs from PyTorch SDPA by {error:.2f} (relative L2)") + with _lock: + _validated_devices.add(index) + + +def _disable(index: int, error: Exception, query: torch.Tensor, key: torch.Tensor) -> None: + with _lock: + first = index not in _disabled_devices + _disabled_devices.add(index) + if first: + logger.warning( + f"SageAttention failed on cuda:{index} for query {tuple(query.shape)}, key {tuple(key.shape)}, " + f"{query.dtype} ({error!r}). Attention on this device uses PyTorch SDPA until restart; set " + "`attention_backend: auto` to stop using SageAttention." + ) + + +def _announce(index: int, version: str, kernel: Callable[..., torch.Tensor]) -> None: + if index in _announced_devices: + return + with _lock: + if index in _announced_devices: + return + _announced_devices.add(index) + major, minor = torch.cuda.get_device_capability(index) + name = getattr(kernel, "func", kernel).__name__ + logger.info( + f"SageAttention {version} serves diffusion-model attention on cuda:{index} " + f"({torch.cuda.get_device_name(index)}, sm_{major}{minor}, {name})." + ) + + +def install_sage_attention() -> None: + """Rebind ``F.scaled_dot_product_attention`` so ``sage_attention_scope`` can route calls to SageAttention. + + Idempotent and never raises: a machine that cannot run SageAttention logs why and keeps PyTorch's SDPA. Never + installs on ROCm, which also keeps ``rocm_sdpa_chunks_math`` reading the ROCm guard's sentinel on the live binding. + Installed once at startup via ``apply_monkeypatches`` when ``attention_backend`` is ``sage``. + """ + from importlib.metadata import PackageNotFoundError + + functional = torch.nn.functional + if getattr(functional.scaled_dot_product_attention, _SENTINEL, False): + return + unavailable = "attention_backend is 'sage', but SageAttention is unavailable here: " + see_docs = f"See {DOCS_URL}. Using PyTorch SDPA." + try: + if torch.version.hip is not None or torch.version.cuda is None or not torch.cuda.is_available(): + logger.warning(unavailable + "it needs an NVIDIA GPU with CUDA. Using PyTorch SDPA.") + return + capabilities = {index: torch.cuda.get_device_capability(index) for index in range(torch.cuda.device_count())} + if not any(capability >= (8, 0) for capability in capabilities.values()): + logger.warning( + unavailable + "it needs an NVIDIA GPU with compute capability 8.0 or newer. Using PyTorch SDPA." + ) + return + version, sageattention = _import_sageattention() + parsed = _parse_version(version) + if parsed is None or parsed < _MIN_VERSION: + logger.warning( + unavailable + f"version {version} is installed, and {'.'.join(map(str, _MIN_VERSION))} or newer is " + f"required (the `sageattention` release on PyPI is SageAttention 1). {see_docs}" + ) + return + cuda = _parse_version(torch.version.cuda) or (0, 0, 0) + kernels = { + index: kernel for index, cap in capabilities.items() if (kernel := _kernel(sageattention, cap, cuda[:2])) + } + for kernel in kernels.values(): + if unaccepted := _unaccepted_arguments(kernel): + logger.warning( + unavailable + f"its {kernel.func.__name__} does not take {', '.join(unaccepted)}, so this " + f"SageAttention {version} differs from the version InvokeAI was validated with. {see_docs}" + ) + return + except PackageNotFoundError: + logger.warning( + unavailable + f"`sageattention` imports, but has no package metadata to check its version. {see_docs}" + ) + return + except ModuleNotFoundError as e: + if (e.name or "").split(".")[0] == "triton": + hint = "it needs Triton, which on Windows is the `triton-windows` package" + else: + hint = "the `sageattention` package is not installed" + logger.warning(unavailable + f"{hint}. {see_docs}") + return + except Exception as e: + logger.warning(unavailable + f"loading it failed ({e!r}). {see_docs}") + return + + original = functional.scaled_dot_product_attention + + @functools.wraps(original) + def sage_scaled_dot_product_attention( + query: torch.Tensor, + key: torch.Tensor, + value: torch.Tensor, + attn_mask: torch.Tensor | None = None, + dropout_p: float = 0.0, + is_causal: bool = False, + *args: Any, + scale: float | None = None, + enable_gqa: bool = False, + **kwargs: Any, + ) -> torch.Tensor: + usage = _scope.get() + if usage is not None: + reason = _ineligible(query, key, value, attn_mask, dropout_p, is_causal, enable_gqa, bool(args or kwargs)) + if reason is None: + index = _cuda_index(query, key, value) + kernel = kernels.get(index) if index is not None and index not in _disabled_devices else None + if index is None or kernel is None: + reason = "device" + else: + try: + out = kernel( + query, + key, + value, + tensor_layout="HND", + is_causal=False, + sm_scale=scale if scale is not None else query.shape[-1] ** -0.5, + ) + if index not in _validated_devices: + _check_first_result(index, original, query, key, value, scale, out) + except torch.OutOfMemoryError: + # A resource condition, not a sign the kernel cannot serve this device. + raise + except Exception as e: + _disable(index, e, query, key) + reason = "error" + else: + usage.served += 1 + _announce(index, version, kernel) + return out + usage.fell_back[reason] += 1 + return original( + query, key, value, attn_mask, dropout_p, is_causal, *args, scale=scale, enable_gqa=enable_gqa, **kwargs + ) + + setattr(sage_scaled_dot_product_attention, _SENTINEL, True) + functional.scaled_dot_product_attention = sage_scaled_dot_product_attention + unserved = sorted(index for index in capabilities if index not in kernels) + logger.info( + f"SageAttention {version} enabled for the attention inside supported diffusion models " + "(attention_backend: sage). Text encoders, VAEs and masked attention keep PyTorch SDPA" + + (f"; no SageAttention kernel for cuda:{', cuda:'.join(map(str, unserved))}." if unserved else ".") + ) diff --git a/invokeai/frontend/api/openapi.json b/invokeai/frontend/api/openapi.json index 6a3c8162b47..c05839b72f4 100644 --- a/invokeai/frontend/api/openapi.json +++ b/invokeai/frontend/api/openapi.json @@ -58282,6 +58282,13 @@ "description": "Slice size, valid when attention_type==\"sliced\".", "default": "auto" }, + "attention_backend": { + "type": "string", + "enum": ["auto", "sage"], + "title": "Attention Backend", + "description": "Attention kernel for the attention inside diffusion models. `auto` lets PyTorch choose among its exact SDPA kernels and never selects a quantized one. `sage` uses SageAttention 2 for the models listed in the SageAttention docs: faster, most at high resolutions and for video, but quantized, so images differ slightly from `auto` at the same seed. SageAttention is installed separately and needs an NVIDIA GPU with compute capability 8.0 or newer; masked attention, text encoders and VAEs keep PyTorch SDPA.", + "default": "auto" + }, "force_tiled_decode": { "type": "boolean", "title": "Force Tiled Decode", @@ -58588,7 +58595,7 @@ "additionalProperties": false, "type": "object", "title": "InvokeAIAppConfig", - "description": "Invoke's global app configuration.\n\nTypically, you won't need to interact with this class directly. Instead, use the `get_config` function from `invokeai.app.services.config` to get a singleton config object.\n\nAttributes:\n host: IP address to bind to. Use `0.0.0.0` to serve to your local network.\n port: Port to bind to.\n allow_origins: Allowed CORS origins.\n allow_credentials: Allow CORS credentials.\n allow_methods: Methods allowed for CORS.\n allow_headers: Headers allowed for CORS.\n ssl_certfile: SSL certificate file for HTTPS. See https://www.uvicorn.dev/settings/#https.\n ssl_keyfile: SSL key file for HTTPS. See https://www.uvicorn.dev/settings/#https.\n log_tokenization: Enable logging of parsed prompt tokens.\n patchmatch: Enable patchmatch inpaint code.\n models_dir: Path to the models directory.\n convert_cache_dir: Path to the converted models cache directory (DEPRECATED, but do not delete because it is needed for migration from previous versions).\n download_cache_dir: Path to the directory that contains dynamically downloaded models.\n legacy_conf_dir: Path to directory of legacy checkpoint config files.\n db_dir: Path to InvokeAI databases directory.\n db_synchronous: SQLite durability setting. `full`, the default and what InvokeAI has always used, flushes every commit to disk. `normal` acknowledges commits without waiting for that flush - measured at roughly 12x shorter commits on an SSD - and cannot corrupt the database under WAL, which is why it is refused, with a warning, when WAL is unavailable for the database file. What `normal` gives up is the most recent transactions on a power loss or OS crash: a just-written image record or queue status, not the image file itself.
Valid values: `full`, `normal`\n outputs_dir: Path to directory for outputs.\n image_subfolder_strategy: Strategy for organizing images into subfolders. 'flat' stores all images in a single folder. 'date' organizes by YYYY/MM/DD. 'type' organizes by image category. 'hash' uses first 2 characters of UUID for filesystem performance.
Valid values: `flat`, `date`, `type`, `hash`\n custom_nodes_dir: Path to directory for custom nodes.\n style_presets_dir: Path to directory for style presets.\n workflow_thumbnails_dir: Path to directory for workflow thumbnails.\n log_handlers: Log handler. Valid options are \"console\", \"file=\", \"syslog=path|address:host:port\", \"http=\".\n log_format: Log format. Use \"plain\" for text-only, \"color\" for colorized output, \"legacy\" for 2.3-style logging and \"syslog\" for syslog-style.
Valid values: `plain`, `color`, `syslog`, `legacy`\n log_level: Emit logging messages at this level or higher.
Valid values: `debug`, `info`, `warning`, `error`, `critical`\n log_sql: Log SQL queries. `log_level` must be `debug` for this to do anything. Extremely verbose.\n log_level_network: Log level for network-related messages. 'info' and 'debug' are very verbose.
Valid values: `debug`, `info`, `warning`, `error`, `critical`\n use_memory_db: Use in-memory database. Useful for development.\n dev_reload: Automatically reload when Python sources are changed. Does not reload node definitions.\n profile_graphs: Enable graph profiling using `cProfile`.\n profile_prefix: An optional prefix for profile output files.\n profiles_dir: Path to profiles output directory.\n max_cache_ram_gb: The maximum amount of CPU RAM to use for model caching in GB. If unset, the limit will be configured based on the available RAM. In most cases, it is recommended to leave this unset.\n max_cache_vram_gb: The amount of VRAM to use for model caching in GB. If unset, the limit will be configured based on the available VRAM and the device_working_mem_gb. In most cases, it is recommended to leave this unset.\n log_memory_usage: If True, a memory snapshot will be captured before and after every model cache operation, and the result will be logged (at debug level). There is a time cost to capturing the memory snapshots, so it is recommended to only enable this feature if you are actively inspecting the model cache's behaviour.\n model_cache_keep_alive_min: How long to keep models in cache after last use, in minutes. A value of 0 (the default) means models are kept in cache indefinitely. If no model generations occur within the timeout period, the model cache is cleared using the same logic as the 'Clear Model Cache' button.\n device_working_mem_gb: The amount of working memory to keep available on the compute device (in GB). Has no effect if running on CPU. If you are experiencing OOM errors, try increasing this value.\n enable_partial_loading: Enable partial loading of models. This enables models to run with reduced VRAM requirements (at the cost of slower speed) by streaming the model from RAM to VRAM as its used. In some edge cases, partial loading can cause models to run more slowly if they were previously being fully loaded into VRAM.\n keep_ram_copy_of_weights: Whether to keep a full RAM copy of a model's weights when the model is loaded in VRAM. Keeping a RAM copy increases average RAM usage, but speeds up model switching and LoRA patching (assuming there is sufficient RAM). Set this to False if RAM pressure is consistently high.\n fp8_compute: Keep ComfyUI 'scaled fp8' checkpoints quantized instead of dequantizing them at load, and run their matmuls on the fp8 tensor cores (requires an Ada/SM 8.9 or newer NVIDIA GPU, or a ROCm GPU that passes an fp8 matmul probe; falls back automatically otherwise). Speeds up denoising and keeps the transformer at fp8 size - FP8 Storage alone also keeps it at that size, without the speedup - but quantizes activations as well, so images will differ from previous versions at the same seed. Reproducibility also requires the model to be FULLY resident in VRAM: a layer whose weights are still in RAM falls back to the dequantized path, and since which layers are resident shifts from run to run, the same seed then yields visibly different images. For repeatable output, ensure the model loads at 100% (e.g. enable_partial_loading=false with enough free VRAM).\n fp8_compute_full_precision_hints: Honor the per-layer 'full_precision_matrix_mult' flags that some scaled-fp8 checkpoints ship. Those layers then dequantize on every forward instead of using the fp8 tensor cores, which can cost a large part of the fp8_compute speedup - on checkpoints that mark many layers, most of it. Set to false to run every quantized layer on the fp8 tensor cores, ignoring the producer's instruction; faster, but the marked layers were flagged as numerically sensitive, so quality may suffer. Only has an effect when fp8_compute is enabled.\n ram: DEPRECATED: This setting is no longer used. It has been replaced by `max_cache_ram_gb`, but most users will not need to use this config since automatic cache size limits should work well in most cases. This config setting will be removed once the new model cache behavior is stable.\n vram: DEPRECATED: This setting is no longer used. It has been replaced by `max_cache_vram_gb`, but most users will not need to use this config since automatic cache size limits should work well in most cases. This config setting will be removed once the new model cache behavior is stable.\n lazy_offload: DEPRECATED: This setting is no longer used. Lazy-offloading is enabled by default. This config setting will be removed once the new model cache behavior is stable.\n pytorch_cuda_alloc_conf: Configure the Torch CUDA memory allocator. This will impact peak reserved VRAM usage and performance. Setting to \"backend:cudaMallocAsync\" works well on many systems. The optimal configuration is highly dependent on the system configuration (device type, VRAM, CUDA driver version, etc.), so must be tuned experimentally. Unset on a ROCm build of PyTorch on Windows, \"expandable_segments:True\" is used.\n device: Preferred execution device. `auto` will choose the device depending on the hardware platform and the installed torch capabilities.
Valid values: `auto`, `cpu`, `cuda`, `mps`, `xpu`, `cuda:N`, `xpu:N` (where N is a device number)\n precision: Floating point precision. `float16` will consume half the memory of `float32` but produce slightly lower-quality images. The `auto` setting will guess the proper precision based on your video card and operating system.
Valid values: `auto`, `float16`, `bfloat16`, `float32`\n sequential_guidance: Whether to calculate guidance in serial instead of in parallel, lowering memory requirements.\n noise_dtype: The dtype seeded noise is drawn in for SD1.5/SDXL, SD3, FLUX.1, FLUX.2, CogView4 and the Z-Image seed variance enhancer. `float32` draws the same noise on every platform. `float16` is the half-precision draw of earlier versions: on macOS it keeps the images your seeds gave before this update; on Windows and Linux both values give (nearly) the same images, and images made before the update cannot be reproduced there.
Valid values: `float32`, `float16`\n wan_memory_optimization: Enable experimental Wan memory optimizations at the cost of slower generation.\n pid_memory_optimization: Enable experimental PiD decode memory optimizations. Roughly halves the peak activation memory of a PiD decode; in exchange the decoded image changes slightly, because neither the chunked pixel pathway nor the float32 sampler intermediates are bit-exact with the default path.\n attention_type: Attention type.
Valid values: `auto`, `normal`, `xformers`, `sliced`, `torch-sdp`\n attention_slice_size: Slice size, valid when attention_type==\"sliced\".
Valid values: `auto`, `balanced`, `max`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`\n force_tiled_decode: Whether to enable tiled VAE decode (reduces memory consumption with some performance penalty). A tiled decode is not pixel-identical to a single-pass one: a VAE decoder normalises and attends over the whole image, so the difference is spread across it rather than confined to the tile seams. Also tiles the encode. Read by the SD1.5/SDXL, FLUX.1, Z-Image and Qwen-Image VAE nodes, the Anima encode and the Krea-2 style reference; other model families ignore it.\n auto_tiled_decode: Decode large images in tiles when an untiled decode's estimated working memory would take most of the GPU's memory (FLUX.1, Z-Image, Qwen-Image, Krea-2 and Wan decodes). Turn off to decode untiled unless tiling is requested; the FLUX.1 and Z-Image decodes then still retry tiled after running out of memory. Anima keeps tiling a decode too large for its GPU either way, because tiling is what makes that case fast there.\n pil_compress_level: The compress_level setting of PIL.Image.save(), used for PNG encoding. All settings are lossless. 0 = no compression, 1 = fastest with slightly larger filesize, 9 = slowest with smallest filesize. 1 is typically the best setting.\n max_queue_size: Maximum number of items in the session queue.\n session_queue_mode: Session queue mode. Use 'FIFO' for traditional first-in-first-out, or 'round_robin' to serve each user's jobs in turn. In single-user mode, FIFO is always used regardless of this setting.
Valid values: `FIFO`, `round_robin`\n clear_queue_on_startup: Empties session queue on startup. If true, disables `max_queue_history`.\n max_queue_history: Keep the last N completed, failed, and canceled queue items. Older items are deleted on startup. Set to 0 to prune all terminal items. Ignored if `clear_queue_on_startup` is true.\n allow_nodes: List of nodes to allow. Omit to allow all.\n deny_nodes: List of nodes to deny. Omit to deny none.\n node_cache_size: How many cached nodes to keep in memory.\n hashing_algorithm: Model hashing algorthim for model installs. 'blake3_multi' is best for SSDs. 'blake3_single' is best for spinning disk HDDs. 'random' disables hashing, instead assigning a UUID to models. Useful when using a memory db to reduce model installation time, or if you don't care about storing stable hashes for models. Alternatively, any other hashlib algorithm is accepted, though these are not nearly as performant as blake3.
Valid values: `blake3_multi`, `blake3_single`, `random`, `md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`, `blake2b`, `blake2s`, `sha3_224`, `sha3_256`, `sha3_384`, `sha3_512`, `shake_128`, `shake_256`\n remote_api_tokens: List of regular expression and token pairs used when downloading models from URLs. The download URL is tested against the regex, and if it matches, the token is provided in as a Bearer token.\n scan_models_on_startup: Scan the models directory on startup, registering orphaned models. This is typically only used in conjunction with `use_memory_db` for testing purposes.\n allow_private_download_urls: Allow the download queue to fetch from loopback, link-local and private-network addresses. Disabled by default so that a download URL cannot be used to reach services that are only reachable from the server. Enable this only if you install models from a mirror on your own network.\n download_proxy: Optional HTTP proxy for model downloads. The proxy must enforce the public-address policy because proxy-side DNS cannot be checked by InvokeAI.\n unsafe_disable_picklescan: UNSAFE. Disable the picklescan security check during model installation. Recommended only for development and testing purposes. This will allow arbitrary code execution during model installation, so should never be used in production.\n allow_unknown_models: Allow installation of models that we are unable to identify. If enabled, models will be marked as `unknown` in the database, and will not have any metadata associated with them. If disabled, unknown models will be rejected during installation.\n multiuser: Enable multiuser support. When disabled, the application runs in single-user mode using a default system account with administrator privileges. When enabled, requires user authentication and authorization.\n strict_password_checking: Enforce strict password requirements. When True, passwords must contain uppercase, lowercase, and numbers. When False (default), any password is accepted but its strength (weak/moderate/strong) is reported to the user.\n image_index_enabled: Maintain a semantic embedding index of gallery images and videos, used by the image map and semantic search features.\n image_index_model: Name of the installed CLIP Vision or SigLIP model used to embed gallery images and video thumbnails. Changing the model discards embeddings computed by the previous model.\n image_index_device: Set to `cpu` to compute image embeddings on the CPU with a service-local copy of the model - avoids VRAM use and lets indexing run during generations. Any other value is ignored: embeddings otherwise run on the model cache's device, pausing while generations are in progress.\n image_index_batch_size: Number of gallery items embedded per batch by the image index worker.\n external_alibabacloud_api_key: API key for Alibaba Cloud DashScope image generation.\n external_alibabacloud_base_url: Base URL override for Alibaba Cloud DashScope image generation.\n external_gemini_api_key: API key for Gemini image generation.\n external_openai_api_key: API key for OpenAI image generation.\n external_gemini_base_url: Base URL override for Gemini image generation.\n external_openai_base_url: Base URL override for OpenAI image generation.\n external_seedream_api_key: API key for Seedream image generation.\n external_seedream_base_url: Base URL override for Seedream image generation.\n base_url: Public base path when running behind a reverse proxy under a sub-path, e.g. `/invoke`. Set only when the proxy PRESERVES the sub-path (the backend receives `/invoke/api/...`). Leave unset when the proxy strips the sub-path or when serving at the domain root.\n forwarded_allow_ips: Comma-separated list of IPs (or `*`) allowed to set X-Forwarded-* headers. Set to the reverse proxy's IP. Only used when `base_url` is set.\n http_compression_level: Compression level for gzipped HTTP API responses. 0 disables response compression entirely, 1 is fastest, 9 (the default) is smallest. Compression runs on the event loop and blocks the whole server while it works, and level 9 costs about 5.5x the time of level 1 for 0.4 percentage points of extra compression, so lowering this makes the app noticeably more responsive on large libraries. Set to 0 when a reverse proxy already compresses responses." + "description": "Invoke's global app configuration.\n\nTypically, you won't need to interact with this class directly. Instead, use the `get_config` function from `invokeai.app.services.config` to get a singleton config object.\n\nAttributes:\n host: IP address to bind to. Use `0.0.0.0` to serve to your local network.\n port: Port to bind to.\n allow_origins: Allowed CORS origins.\n allow_credentials: Allow CORS credentials.\n allow_methods: Methods allowed for CORS.\n allow_headers: Headers allowed for CORS.\n ssl_certfile: SSL certificate file for HTTPS. See https://www.uvicorn.dev/settings/#https.\n ssl_keyfile: SSL key file for HTTPS. See https://www.uvicorn.dev/settings/#https.\n log_tokenization: Enable logging of parsed prompt tokens.\n patchmatch: Enable patchmatch inpaint code.\n models_dir: Path to the models directory.\n convert_cache_dir: Path to the converted models cache directory (DEPRECATED, but do not delete because it is needed for migration from previous versions).\n download_cache_dir: Path to the directory that contains dynamically downloaded models.\n legacy_conf_dir: Path to directory of legacy checkpoint config files.\n db_dir: Path to InvokeAI databases directory.\n db_synchronous: SQLite durability setting. `full`, the default and what InvokeAI has always used, flushes every commit to disk. `normal` acknowledges commits without waiting for that flush - measured at roughly 12x shorter commits on an SSD - and cannot corrupt the database under WAL, which is why it is refused, with a warning, when WAL is unavailable for the database file. What `normal` gives up is the most recent transactions on a power loss or OS crash: a just-written image record or queue status, not the image file itself.
Valid values: `full`, `normal`\n outputs_dir: Path to directory for outputs.\n image_subfolder_strategy: Strategy for organizing images into subfolders. 'flat' stores all images in a single folder. 'date' organizes by YYYY/MM/DD. 'type' organizes by image category. 'hash' uses first 2 characters of UUID for filesystem performance.
Valid values: `flat`, `date`, `type`, `hash`\n custom_nodes_dir: Path to directory for custom nodes.\n style_presets_dir: Path to directory for style presets.\n workflow_thumbnails_dir: Path to directory for workflow thumbnails.\n log_handlers: Log handler. Valid options are \"console\", \"file=\", \"syslog=path|address:host:port\", \"http=\".\n log_format: Log format. Use \"plain\" for text-only, \"color\" for colorized output, \"legacy\" for 2.3-style logging and \"syslog\" for syslog-style.
Valid values: `plain`, `color`, `syslog`, `legacy`\n log_level: Emit logging messages at this level or higher.
Valid values: `debug`, `info`, `warning`, `error`, `critical`\n log_sql: Log SQL queries. `log_level` must be `debug` for this to do anything. Extremely verbose.\n log_level_network: Log level for network-related messages. 'info' and 'debug' are very verbose.
Valid values: `debug`, `info`, `warning`, `error`, `critical`\n use_memory_db: Use in-memory database. Useful for development.\n dev_reload: Automatically reload when Python sources are changed. Does not reload node definitions.\n profile_graphs: Enable graph profiling using `cProfile`.\n profile_prefix: An optional prefix for profile output files.\n profiles_dir: Path to profiles output directory.\n max_cache_ram_gb: The maximum amount of CPU RAM to use for model caching in GB. If unset, the limit will be configured based on the available RAM. In most cases, it is recommended to leave this unset.\n max_cache_vram_gb: The amount of VRAM to use for model caching in GB. If unset, the limit will be configured based on the available VRAM and the device_working_mem_gb. In most cases, it is recommended to leave this unset.\n log_memory_usage: If True, a memory snapshot will be captured before and after every model cache operation, and the result will be logged (at debug level). There is a time cost to capturing the memory snapshots, so it is recommended to only enable this feature if you are actively inspecting the model cache's behaviour.\n model_cache_keep_alive_min: How long to keep models in cache after last use, in minutes. A value of 0 (the default) means models are kept in cache indefinitely. If no model generations occur within the timeout period, the model cache is cleared using the same logic as the 'Clear Model Cache' button.\n device_working_mem_gb: The amount of working memory to keep available on the compute device (in GB). Has no effect if running on CPU. If you are experiencing OOM errors, try increasing this value.\n enable_partial_loading: Enable partial loading of models. This enables models to run with reduced VRAM requirements (at the cost of slower speed) by streaming the model from RAM to VRAM as its used. In some edge cases, partial loading can cause models to run more slowly if they were previously being fully loaded into VRAM.\n keep_ram_copy_of_weights: Whether to keep a full RAM copy of a model's weights when the model is loaded in VRAM. Keeping a RAM copy increases average RAM usage, but speeds up model switching and LoRA patching (assuming there is sufficient RAM). Set this to False if RAM pressure is consistently high.\n fp8_compute: Keep ComfyUI 'scaled fp8' checkpoints quantized instead of dequantizing them at load, and run their matmuls on the fp8 tensor cores (requires an Ada/SM 8.9 or newer NVIDIA GPU, or a ROCm GPU that passes an fp8 matmul probe; falls back automatically otherwise). Speeds up denoising and keeps the transformer at fp8 size - FP8 Storage alone also keeps it at that size, without the speedup - but quantizes activations as well, so images will differ from previous versions at the same seed. Reproducibility also requires the model to be FULLY resident in VRAM: a layer whose weights are still in RAM falls back to the dequantized path, and since which layers are resident shifts from run to run, the same seed then yields visibly different images. For repeatable output, ensure the model loads at 100% (e.g. enable_partial_loading=false with enough free VRAM).\n fp8_compute_full_precision_hints: Honor the per-layer 'full_precision_matrix_mult' flags that some scaled-fp8 checkpoints ship. Those layers then dequantize on every forward instead of using the fp8 tensor cores, which can cost a large part of the fp8_compute speedup - on checkpoints that mark many layers, most of it. Set to false to run every quantized layer on the fp8 tensor cores, ignoring the producer's instruction; faster, but the marked layers were flagged as numerically sensitive, so quality may suffer. Only has an effect when fp8_compute is enabled.\n ram: DEPRECATED: This setting is no longer used. It has been replaced by `max_cache_ram_gb`, but most users will not need to use this config since automatic cache size limits should work well in most cases. This config setting will be removed once the new model cache behavior is stable.\n vram: DEPRECATED: This setting is no longer used. It has been replaced by `max_cache_vram_gb`, but most users will not need to use this config since automatic cache size limits should work well in most cases. This config setting will be removed once the new model cache behavior is stable.\n lazy_offload: DEPRECATED: This setting is no longer used. Lazy-offloading is enabled by default. This config setting will be removed once the new model cache behavior is stable.\n pytorch_cuda_alloc_conf: Configure the Torch CUDA memory allocator. This will impact peak reserved VRAM usage and performance. Setting to \"backend:cudaMallocAsync\" works well on many systems. The optimal configuration is highly dependent on the system configuration (device type, VRAM, CUDA driver version, etc.), so must be tuned experimentally. Unset on a ROCm build of PyTorch on Windows, \"expandable_segments:True\" is used.\n device: Preferred execution device. `auto` will choose the device depending on the hardware platform and the installed torch capabilities.
Valid values: `auto`, `cpu`, `cuda`, `mps`, `xpu`, `cuda:N`, `xpu:N` (where N is a device number)\n precision: Floating point precision. `float16` will consume half the memory of `float32` but produce slightly lower-quality images. The `auto` setting will guess the proper precision based on your video card and operating system.
Valid values: `auto`, `float16`, `bfloat16`, `float32`\n sequential_guidance: Whether to calculate guidance in serial instead of in parallel, lowering memory requirements.\n noise_dtype: The dtype seeded noise is drawn in for SD1.5/SDXL, SD3, FLUX.1, FLUX.2, CogView4 and the Z-Image seed variance enhancer. `float32` draws the same noise on every platform. `float16` is the half-precision draw of earlier versions: on macOS it keeps the images your seeds gave before this update; on Windows and Linux both values give (nearly) the same images, and images made before the update cannot be reproduced there.
Valid values: `float32`, `float16`\n wan_memory_optimization: Enable experimental Wan memory optimizations at the cost of slower generation.\n pid_memory_optimization: Enable experimental PiD decode memory optimizations. Roughly halves the peak activation memory of a PiD decode; in exchange the decoded image changes slightly, because neither the chunked pixel pathway nor the float32 sampler intermediates are bit-exact with the default path.\n attention_type: Attention type.
Valid values: `auto`, `normal`, `xformers`, `sliced`, `torch-sdp`\n attention_slice_size: Slice size, valid when attention_type==\"sliced\".
Valid values: `auto`, `balanced`, `max`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`\n attention_backend: Attention kernel for the attention inside diffusion models. `auto` lets PyTorch choose among its exact SDPA kernels and never selects a quantized one. `sage` uses SageAttention 2 for the models listed in the SageAttention docs: faster, most at high resolutions and for video, but quantized, so images differ slightly from `auto` at the same seed. SageAttention is installed separately and needs an NVIDIA GPU with compute capability 8.0 or newer; masked attention, text encoders and VAEs keep PyTorch SDPA.
Valid values: `auto`, `sage`\n force_tiled_decode: Whether to enable tiled VAE decode (reduces memory consumption with some performance penalty). A tiled decode is not pixel-identical to a single-pass one: a VAE decoder normalises and attends over the whole image, so the difference is spread across it rather than confined to the tile seams. Also tiles the encode. Read by the SD1.5/SDXL, FLUX.1, Z-Image and Qwen-Image VAE nodes, the Anima encode and the Krea-2 style reference; other model families ignore it.\n auto_tiled_decode: Decode large images in tiles when an untiled decode's estimated working memory would take most of the GPU's memory (FLUX.1, Z-Image, Qwen-Image, Krea-2 and Wan decodes). Turn off to decode untiled unless tiling is requested; the FLUX.1 and Z-Image decodes then still retry tiled after running out of memory. Anima keeps tiling a decode too large for its GPU either way, because tiling is what makes that case fast there.\n pil_compress_level: The compress_level setting of PIL.Image.save(), used for PNG encoding. All settings are lossless. 0 = no compression, 1 = fastest with slightly larger filesize, 9 = slowest with smallest filesize. 1 is typically the best setting.\n max_queue_size: Maximum number of items in the session queue.\n session_queue_mode: Session queue mode. Use 'FIFO' for traditional first-in-first-out, or 'round_robin' to serve each user's jobs in turn. In single-user mode, FIFO is always used regardless of this setting.
Valid values: `FIFO`, `round_robin`\n clear_queue_on_startup: Empties session queue on startup. If true, disables `max_queue_history`.\n max_queue_history: Keep the last N completed, failed, and canceled queue items. Older items are deleted on startup. Set to 0 to prune all terminal items. Ignored if `clear_queue_on_startup` is true.\n allow_nodes: List of nodes to allow. Omit to allow all.\n deny_nodes: List of nodes to deny. Omit to deny none.\n node_cache_size: How many cached nodes to keep in memory.\n hashing_algorithm: Model hashing algorthim for model installs. 'blake3_multi' is best for SSDs. 'blake3_single' is best for spinning disk HDDs. 'random' disables hashing, instead assigning a UUID to models. Useful when using a memory db to reduce model installation time, or if you don't care about storing stable hashes for models. Alternatively, any other hashlib algorithm is accepted, though these are not nearly as performant as blake3.
Valid values: `blake3_multi`, `blake3_single`, `random`, `md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`, `blake2b`, `blake2s`, `sha3_224`, `sha3_256`, `sha3_384`, `sha3_512`, `shake_128`, `shake_256`\n remote_api_tokens: List of regular expression and token pairs used when downloading models from URLs. The download URL is tested against the regex, and if it matches, the token is provided in as a Bearer token.\n scan_models_on_startup: Scan the models directory on startup, registering orphaned models. This is typically only used in conjunction with `use_memory_db` for testing purposes.\n allow_private_download_urls: Allow the download queue to fetch from loopback, link-local and private-network addresses. Disabled by default so that a download URL cannot be used to reach services that are only reachable from the server. Enable this only if you install models from a mirror on your own network.\n download_proxy: Optional HTTP proxy for model downloads. The proxy must enforce the public-address policy because proxy-side DNS cannot be checked by InvokeAI.\n unsafe_disable_picklescan: UNSAFE. Disable the picklescan security check during model installation. Recommended only for development and testing purposes. This will allow arbitrary code execution during model installation, so should never be used in production.\n allow_unknown_models: Allow installation of models that we are unable to identify. If enabled, models will be marked as `unknown` in the database, and will not have any metadata associated with them. If disabled, unknown models will be rejected during installation.\n multiuser: Enable multiuser support. When disabled, the application runs in single-user mode using a default system account with administrator privileges. When enabled, requires user authentication and authorization.\n strict_password_checking: Enforce strict password requirements. When True, passwords must contain uppercase, lowercase, and numbers. When False (default), any password is accepted but its strength (weak/moderate/strong) is reported to the user.\n image_index_enabled: Maintain a semantic embedding index of gallery images and videos, used by the image map and semantic search features.\n image_index_model: Name of the installed CLIP Vision or SigLIP model used to embed gallery images and video thumbnails. Changing the model discards embeddings computed by the previous model.\n image_index_device: Set to `cpu` to compute image embeddings on the CPU with a service-local copy of the model - avoids VRAM use and lets indexing run during generations. Any other value is ignored: embeddings otherwise run on the model cache's device, pausing while generations are in progress.\n image_index_batch_size: Number of gallery items embedded per batch by the image index worker.\n external_alibabacloud_api_key: API key for Alibaba Cloud DashScope image generation.\n external_alibabacloud_base_url: Base URL override for Alibaba Cloud DashScope image generation.\n external_gemini_api_key: API key for Gemini image generation.\n external_openai_api_key: API key for OpenAI image generation.\n external_gemini_base_url: Base URL override for Gemini image generation.\n external_openai_base_url: Base URL override for OpenAI image generation.\n external_seedream_api_key: API key for Seedream image generation.\n external_seedream_base_url: Base URL override for Seedream image generation.\n base_url: Public base path when running behind a reverse proxy under a sub-path, e.g. `/invoke`. Set only when the proxy PRESERVES the sub-path (the backend receives `/invoke/api/...`). Leave unset when the proxy strips the sub-path or when serving at the domain root.\n forwarded_allow_ips: Comma-separated list of IPs (or `*`) allowed to set X-Forwarded-* headers. Set to the reverse proxy's IP. Only used when `base_url` is set.\n http_compression_level: Compression level for gzipped HTTP API responses. 0 disables response compression entirely, 1 is fastest, 9 (the default) is smallest. Compression runs on the event loop and blocks the whole server while it works, and level 9 costs about 5.5x the time of level 1 for 0.4 percentage points of extra compression, so lowering this makes the app noticeably more responsive on large libraries. Set to 0 when a reverse proxy already compresses responses." }, "InvokeAIAppConfigWithSetFields": { "properties": { diff --git a/invokeai/frontend/api/schema.ts b/invokeai/frontend/api/schema.ts index fddf71a2711..0b136b00289 100644 --- a/invokeai/frontend/api/schema.ts +++ b/invokeai/frontend/api/schema.ts @@ -22238,6 +22238,7 @@ export type components = { * pid_memory_optimization: Enable experimental PiD decode memory optimizations. Roughly halves the peak activation memory of a PiD decode; in exchange the decoded image changes slightly, because neither the chunked pixel pathway nor the float32 sampler intermediates are bit-exact with the default path. * attention_type: Attention type.
Valid values: `auto`, `normal`, `xformers`, `sliced`, `torch-sdp` * attention_slice_size: Slice size, valid when attention_type=="sliced".
Valid values: `auto`, `balanced`, `max`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8` + * attention_backend: Attention kernel for the attention inside diffusion models. `auto` lets PyTorch choose among its exact SDPA kernels and never selects a quantized one. `sage` uses SageAttention 2 for the models listed in the SageAttention docs: faster, most at high resolutions and for video, but quantized, so images differ slightly from `auto` at the same seed. SageAttention is installed separately and needs an NVIDIA GPU with compute capability 8.0 or newer; masked attention, text encoders and VAEs keep PyTorch SDPA.
Valid values: `auto`, `sage` * force_tiled_decode: Whether to enable tiled VAE decode (reduces memory consumption with some performance penalty). A tiled decode is not pixel-identical to a single-pass one: a VAE decoder normalises and attends over the whole image, so the difference is spread across it rather than confined to the tile seams. Also tiles the encode. Read by the SD1.5/SDXL, FLUX.1, Z-Image and Qwen-Image VAE nodes, the Anima encode and the Krea-2 style reference; other model families ignore it. * auto_tiled_decode: Decode large images in tiles when an untiled decode's estimated working memory would take most of the GPU's memory (FLUX.1, Z-Image, Qwen-Image, Krea-2 and Wan decodes). Turn off to decode untiled unless tiling is requested; the FLUX.1 and Z-Image decodes then still retry tiled after running out of memory. Anima keeps tiling a decode too large for its GPU either way, because tiling is what makes that case fast there. * pil_compress_level: The compress_level setting of PIL.Image.save(), used for PNG encoding. All settings are lossless. 0 = no compression, 1 = fastest with slightly larger filesize, 9 = slowest with smallest filesize. 1 is typically the best setting. @@ -22669,6 +22670,13 @@ export type components = { * @enum {unknown} */ attention_slice_size?: "auto" | "balanced" | "max" | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; + /** + * Attention Backend + * @description Attention kernel for the attention inside diffusion models. `auto` lets PyTorch choose among its exact SDPA kernels and never selects a quantized one. `sage` uses SageAttention 2 for the models listed in the SageAttention docs: faster, most at high resolutions and for video, but quantized, so images differ slightly from `auto` at the same seed. SageAttention is installed separately and needs an NVIDIA GPU with compute capability 8.0 or newer; masked attention, text encoders and VAEs keep PyTorch SDPA. + * @default auto + * @enum {string} + */ + attention_backend?: "auto" | "sage"; /** * Force Tiled Decode * @description Whether to enable tiled VAE decode (reduces memory consumption with some performance penalty). A tiled decode is not pixel-identical to a single-pass one: a VAE decoder normalises and attends over the whole image, so the difference is spread across it rather than confined to the tile seams. Also tiles the encode. Read by the SD1.5/SDXL, FLUX.1, Z-Image and Qwen-Image VAE nodes, the Anima encode and the Krea-2 style reference; other model families ignore it. diff --git a/tests/backend/util/data/qwen_image_first_block_outlier_head.safetensors b/tests/backend/util/data/qwen_image_first_block_outlier_head.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..2306ea05e6ea58b68b7859a26fbfeca37fa737bd GIT binary patch literal 197240 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 d)0L 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∨)HN#$!oJK{Sd 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%0DQ^KYq(54xFw^wI6U62*>>P!7f2|&XAl-&lI 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 torch.Tensor: + """Attention in float32 over [B, H, S, D], K/V heads repeated for GQA.""" + q, k, v = q.float(), k.float(), v.float() + if k.shape[1] != q.shape[1]: + k, v = (t.repeat_interleave(q.shape[1] // t.shape[1], dim=1) for t in (k, v)) + scale = q.shape[-1] ** -0.5 if scale is None else scale + return torch.softmax(q @ k.transpose(-1, -2) * scale, dim=-1) @ v + + +class FakeSageAttention: + """Stands in for the `sageattention` module. Its kernels take the real kernels' named parameters, record each + call and return the exact result -- or `result`, when a test sets one.""" + + def __init__(self, without: tuple[str, ...] = ()) -> None: + self.calls: list[dict] = [] + self.error: Exception | None = None + self.result: torch.Tensor | None = None + for name in KERNELS: + if name not in without: + setattr(self, name, self._kernel(name)) + + def _kernel(self, name: str): + def kernel( + q, k, v, tensor_layout="HND", is_causal=False, qk_quant_gran="per_thread", sm_scale=None, + pv_accum_dtype="fp32", smooth_k=True, smooth_v=False, return_lse=False, **kwargs, + ): # fmt: skip + self.calls.append( + {"kernel": name, "layout": tensor_layout, "causal": is_causal, "sm_scale": sm_scale, "k": k.shape, + "qk_quant_gran": qk_quant_gran, "pv_accum_dtype": pv_accum_dtype, "smooth_k": smooth_k} + ) # fmt: skip + if self.error is not None: + raise self.error + if self.result is not None: + return self.result + return _reference(q, k, v, sm_scale).to(q.dtype) + + kernel.__name__ = name + return kernel + + +def _q(*shape: int, dtype: torch.dtype = torch.float16) -> torch.Tensor: + return torch.randn(*shape).to(dtype) + + +# Long enough for SageAttention by every rule, small enough to be cheap on the CPU. +QUERY_LEN = sage._MIN_QUERY_LEN +KEY_LEN = sage._MIN_KEY_LEN + + +def _cuda_build( + monkeypatch, capabilities: dict[int, tuple[int, int]], module: FakeSageAttention | None = None +) -> tuple[FakeSageAttention, list[dict]]: + """Make this build look like CUDA 13 with the given devices and the fake module the installed SageAttention, with + every device's first-call check already passed. Returns the module and the argument tuples of every call that + reached torch's function (computed in float32: no CPU half-precision GEMM).""" + module = module or FakeSageAttention() + torch_calls: list[dict] = [] + + def torch_spy( + query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, *args, scale=None, enable_gqa=False, **kwargs + ): + torch_calls.append( + {"mask": attn_mask, "dropout": dropout_p, "causal": is_causal, "scale": scale, "gqa": enable_gqa, + "args": args, "kwargs": kwargs, "q": query.shape} + ) # fmt: skip + mask = attn_mask if attn_mask is None or attn_mask.dtype == torch.bool else attn_mask.float() + out = TORCH_SDPA( + query.float(), key.float(), value.float(), mask, dropout_p, is_causal, scale=scale, enable_gqa=enable_gqa + ) + return out.to(query.dtype) + + monkeypatch.setattr(F, "scaled_dot_product_attention", torch_spy) + monkeypatch.setattr(torch.version, "hip", None) + monkeypatch.setattr(torch.version, "cuda", "13.0") + monkeypatch.setattr(torch.cuda, "is_available", lambda: True) + monkeypatch.setattr(torch.cuda, "device_count", lambda: len(capabilities)) + monkeypatch.setattr(torch.cuda, "get_device_capability", lambda index=None: capabilities[index or 0]) + monkeypatch.setattr(torch.cuda, "get_device_name", lambda index=None: "Fake GPU") + monkeypatch.setattr(sage, "_import_sageattention", lambda: (VERSION, module)) + monkeypatch.setattr(sage, "_cuda_index", lambda q, k, v: 0) + monkeypatch.setattr(sage, "_disabled_devices", set()) + monkeypatch.setattr(sage, "_validated_devices", set(capabilities)) + monkeypatch.setattr(sage, "_announced_devices", set()) + return module, torch_calls + + +@pytest.fixture +def installed(monkeypatch): + """The wrapper installed on a build that looks like CUDA 13 with one sm_89 device.""" + module, torch_calls = _cuda_build(monkeypatch, {0: (8, 9)}) + spy = F.scaled_dot_product_attention + install_sage_attention() + assert F.scaled_dot_product_attention is not spy + return module, torch_calls + + +class TestKernelTable: + @pytest.mark.parametrize( + ("capability", "cuda", "kernel", "options"), + [ + ((8, 0), (13, 0), "sageattn_qk_int8_pv_fp16_cuda", {"pv_accum_dtype": "fp32"}), + ((8, 6), (13, 0), "sageattn_qk_int8_pv_fp16_cuda", {"pv_accum_dtype": "fp32"}), + ((8, 9), (13, 0), "sageattn_qk_int8_pv_fp8_cuda", {"pv_accum_dtype": "fp32+fp16"}), + ((8, 9), (12, 6), "sageattn_qk_int8_pv_fp8_cuda", {"pv_accum_dtype": "fp32+fp32"}), + ((9, 0), (13, 0), "sageattn_qk_int8_pv_fp8_cuda_sm90", {"pv_accum_dtype": "fp32+fp32"}), + ((12, 0), (13, 0), "sageattn_qk_int8_pv_fp8_cuda", {"pv_accum_dtype": "fp32+fp16", "qk_quant_gran": "per_warp"}), + ((12, 0), (12, 6), "sageattn_qk_int8_pv_fp8_cuda", {"pv_accum_dtype": "fp32", "qk_quant_gran": "per_warp"}), + ], + ids=["ampere-a100", "ampere-rtx30", "ada", "ada-cuda12.6", "hopper", "blackwell", "blackwell-cuda12.6"], + ) # fmt: skip + def test_each_generation_gets_its_kernel_with_smoothing_off(self, capability, cuda, kernel, options): + """K smoothing broke the first block of Qwen-Image and Krea-2 (cosine 0.18 and 0.53); FP16 accumulation inside + the FP8 kernels needs CUDA 12.8. See `_kernel`.""" + module = FakeSageAttention() + q = _q(1, 2, 8, 64) + + sage._kernel(module, capability, cuda)(q, q, q, tensor_layout="HND", is_causal=False, sm_scale=None) + + assert module.calls[0]["kernel"] == kernel + assert {name: module.calls[0][name] for name in (*options, "smooth_k")} == options | {"smooth_k": False} + + @pytest.mark.parametrize("capability", [(7, 5), (10, 3), (11, 0)], ids=["turing", "sm103", "sm110"]) + def test_gpus_sageattention_has_no_kernel_for_get_none(self, capability): + assert sage._kernel(FakeSageAttention(), capability, (13, 0)) is None + + +class TestRouting: + def test_an_eligible_call_in_scope_runs_on_the_kernel_in_hnd_layout(self, installed): + module, torch_calls = installed + q, k, v = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64), _q(1, 2, KEY_LEN, 64) + + with sage_attention_scope(): + out = F.scaled_dot_product_attention(q, k, v) + + assert torch_calls == [] + call = module.calls[0] + assert (call["layout"], call["causal"], call["sm_scale"], call["k"]) == ("HND", False, 64**-0.5, k.shape) + torch.testing.assert_close(out.float(), _reference(q, k, v), atol=2e-3, rtol=2e-3) + + def test_an_explicit_scale_reaches_the_kernel(self, installed): + module, _ = installed + q, k = _q(1, 2, QUERY_LEN, 128), _q(1, 2, KEY_LEN, 128) + + with sage_attention_scope(): + F.scaled_dot_product_attention(q, k, k, scale=0.3) + + assert module.calls[0]["sm_scale"] == 0.3 + + def test_outside_a_scope_every_call_reaches_torch_untouched(self, installed): + """Text encoders and VAEs run outside any scope, so they must keep torch's exact semantics.""" + module, torch_calls = installed + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + + F.scaled_dot_product_attention(q, k, k, None, 0.0, False, scale=0.5) + + assert module.calls == [] + assert torch_calls == [ + {"mask": None, "dropout": 0.0, "causal": False, "scale": 0.5, "gqa": False, "args": (), "kwargs": {}, + "q": q.shape} + ] # fmt: skip + + @pytest.mark.parametrize( + ("make", "reason"), + [ + (lambda: ((_q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64)), + {"attn_mask": torch.ones(QUERY_LEN, KEY_LEN, dtype=torch.bool)}), "mask"), + (lambda: ((_q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64)), + {"attn_mask": torch.zeros(1, 1, 1, KEY_LEN, dtype=torch.float16)}), "mask"), + (lambda: ((_q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64)), {"dropout_p": 0.1}), "dropout"), + (lambda: ((_q(1, 2, QUERY_LEN, 64), _q(1, 2, QUERY_LEN, 64)), {"is_causal": True}), "causal"), + (lambda: ((_q(1, 2, QUERY_LEN, 64, dtype=torch.float32), _q(1, 2, KEY_LEN, 64, dtype=torch.float32)), {}), + "dtype"), + (lambda: ((_q(1, 2, QUERY_LEN, 40), _q(1, 2, KEY_LEN, 40)), {}), "head_dim"), + (lambda: ((_q(1, 2, QUERY_LEN, 80), _q(1, 2, KEY_LEN, 80)), {}), "head_dim"), + (lambda: ((_q(1, 2, QUERY_LEN, 256), _q(1, 2, KEY_LEN, 256)), {}), "head_dim"), + (lambda: ((_q(2, QUERY_LEN, 64), _q(2, KEY_LEN, 64)), {}), "shape"), + (lambda: ((_q(2, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64)), {}), "shape"), + (lambda: ((_q(1, 2, QUERY_LEN - 1, 64), _q(1, 2, KEY_LEN, 64)), {}), "short"), + (lambda: ((_q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN - 1, 64)), {}), "short"), + (lambda: ((_q(1, 2, QUERY_LEN, 128)[..., ::2], _q(1, 2, KEY_LEN, 64)), {}), "layout"), + ], + ids=["bool-mask", "additive-mask", "dropout", "causal", "fp32", "hd40", "hd80", "hd256", "3d", + "broadcast-batch", "short-query", "short-key", "strided-last-dim"], + ) # fmt: skip + def test_ineligible_calls_reach_torch_with_their_arguments(self, installed, caplog, make, reason): + module, torch_calls = installed + (q, k), kwargs = make() + caplog.set_level(logging.DEBUG, logger=sage.__name__) + + with sage_attention_scope(): + out = F.scaled_dot_product_attention(q, k, k, **kwargs) + + assert module.calls == [] + assert len(torch_calls) == 1 + assert torch_calls[0]["mask"] is kwargs.get("attn_mask") + assert torch_calls[0]["dropout"] == kwargs.get("dropout_p", 0.0) + assert torch_calls[0]["causal"] == kwargs.get("is_causal", False) + assert torch_calls[0]["gqa"] == kwargs.get("enable_gqa", False) + assert out.dtype == q.dtype + assert any(f"on PyTorch SDPA: {reason}=1" in r.message for r in caplog.records) + + @pytest.mark.parametrize(("kv_heads", "enable_gqa"), [(2, False), (3, True)], ids=["gqa-flag-off", "indivisible"]) + def test_mismatched_heads_get_torchs_error_not_a_kernel_answer(self, installed, kv_heads, enable_gqa): + module, torch_calls = installed + q, k = _q(1, 8, QUERY_LEN, 64), _q(1, kv_heads, KEY_LEN, 64) + + with sage_attention_scope(), pytest.raises(RuntimeError): + F.scaled_dot_product_attention(q, k, k, enable_gqa=enable_gqa) + + assert module.calls == [] and len(torch_calls) == 1 + + def test_an_input_that_requires_grad_under_grad_mode_reaches_torch(self, installed): + module, torch_calls = installed + q = _q(1, 2, QUERY_LEN, 64).requires_grad_() + k = _q(1, 2, KEY_LEN, 64) + + with torch.enable_grad(), sage_attention_scope(): + F.scaled_dot_product_attention(q, k, k) + + assert module.calls == [] and len(torch_calls) == 1 + + def test_an_unknown_keyword_reaches_torch_rather_than_being_dropped(self, installed): + module, torch_calls = installed + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + + with sage_attention_scope(): + F.scaled_dot_product_attention(q, k, k, some_future_flag=True) + + assert module.calls == [] and torch_calls[0]["kwargs"] == {"some_future_flag": True} + + def test_grouped_query_attention_reaches_the_kernel_unexpanded(self, installed): + module, torch_calls = installed + q, k, v = _q(1, 8, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64), _q(1, 2, KEY_LEN, 64) + + with sage_attention_scope(): + out = F.scaled_dot_product_attention(q, k, v, enable_gqa=True) + + assert torch_calls == [] and module.calls[0]["k"] == k.shape + torch.testing.assert_close(out.float(), _reference(q, k, v), atol=2e-3, rtol=2e-3) + + +class TestFirstCallCheck: + def test_a_devices_first_result_is_compared_with_torch_once(self, monkeypatch): + module, torch_calls = _cuda_build(monkeypatch, {0: (8, 9)}) + monkeypatch.setattr(sage, "_validated_devices", set()) + install_sage_attention() + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + + with sage_attention_scope(): + F.scaled_dot_product_attention(q, k, k) + F.scaled_dot_product_attention(q, k, k) + + assert len(module.calls) == 2, "both calls ran on the kernel" + assert len(torch_calls) == 1, "only the first was also computed by torch, for the comparison" + + @pytest.mark.parametrize("garbage", [torch.nan, 50.0], ids=["non-finite", "wrong"]) + def test_a_wrong_first_result_retires_the_device_and_returns_torchs(self, monkeypatch, caplog, garbage): + """A kernel the build lacks for this GPU returns uninitialized memory instead of raising.""" + module, torch_calls = _cuda_build(monkeypatch, {0: (8, 9)}) + monkeypatch.setattr(sage, "_validated_devices", set()) + install_sage_attention() + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + module.result = torch.full((1, 2, QUERY_LEN, 64), garbage, dtype=torch.float16) + caplog.set_level(logging.WARNING, logger=sage.__name__) + + with sage_attention_scope(): + out = F.scaled_dot_product_attention(q, k, k) + F.scaled_dot_product_attention(q, k, k) + + torch.testing.assert_close(out.float(), _reference(q, k, k), atol=2e-3, rtol=2e-3) + assert len(module.calls) == 1, "a retired device must not be tried again" + assert any("first result differs from PyTorch SDPA" in r.message for r in caplog.records) + + +class TestFailures: + def test_a_kernel_failure_falls_back_warns_once_and_retires_only_that_device(self, monkeypatch, caplog): + module, _ = _cuda_build(monkeypatch, {0: (8, 9), 1: (8, 9)}) + install_sage_attention() + device = {"index": 0} + monkeypatch.setattr(sage, "_cuda_index", lambda q, k, v: device["index"]) + module.error = RuntimeError("kernel not compiled for this architecture") + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + caplog.set_level(logging.WARNING, logger=sage.__name__) + + with sage_attention_scope(): + first = F.scaled_dot_product_attention(q, k, k) + F.scaled_dot_product_attention(q, k, k) + + assert len(module.calls) == 1, "a retired device must not be tried again" + torch.testing.assert_close(first.float(), _reference(q, k, k), atol=2e-3, rtol=2e-3) + warnings = [r.message for r in caplog.records if r.levelno == logging.WARNING] + assert len(warnings) == 1 and "cuda:0" in warnings[0] + + module.error = None + device["index"] = 1 + with sage_attention_scope(): + F.scaled_dot_product_attention(q, k, k) + assert len(module.calls) == 2, "another device is unaffected" + + def test_out_of_memory_propagates_and_does_not_retire_the_device(self, installed): + module, _ = installed + module.error = torch.OutOfMemoryError("CUDA out of memory") + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + + with sage_attention_scope(), pytest.raises(torch.OutOfMemoryError): + F.scaled_dot_product_attention(q, k, k) + + module.error = None + with sage_attention_scope(): + F.scaled_dot_product_attention(q, k, k) + assert len(module.calls) == 2 + + def test_the_scope_closes_when_its_block_raises(self, installed): + module, torch_calls = installed + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + + with pytest.raises(ValueError), sage_attention_scope(): + raise ValueError("generation failed") + + F.scaled_dot_product_attention(q, k, k) + assert module.calls == [] and len(torch_calls) == 1 + + +class TestScopeIsPerThread: + def test_only_the_thread_inside_the_scope_reaches_the_kernel(self, installed): + """One generation session per GPU runs concurrently; a scope must not leak into the other session.""" + module, torch_calls = installed + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + both_inside = threading.Barrier(2) + errors: list[BaseException] = [] + + def scoped() -> None: + try: + with sage_attention_scope(): + both_inside.wait(timeout=30) + F.scaled_dot_product_attention(q, k, k) + both_inside.wait(timeout=30) + except BaseException as e: # noqa: BLE001 - re-raised in the main thread + errors.append(e) + + def unscoped() -> None: + try: + both_inside.wait(timeout=30) + F.scaled_dot_product_attention(q, k, k) + both_inside.wait(timeout=30) + except BaseException as e: # noqa: BLE001 + errors.append(e) + + threads = [threading.Thread(target=scoped), threading.Thread(target=unscoped)] + for t in threads: + t.start() + for t in threads: + t.join(timeout=60) + + assert errors == [] + assert len(module.calls) == 1 and len(torch_calls) == 1 + + +class TestDeviceRule: + @staticmethod + def _on(index: int) -> SimpleNamespace: + return SimpleNamespace(device=torch.device("cuda", index)) + + def test_tensors_on_the_threads_current_device_are_eligible(self, monkeypatch): + monkeypatch.setattr(torch.cuda, "current_device", lambda: 1) + assert sage._cuda_index(self._on(1), self._on(1), self._on(1)) == 1 + + def test_tensors_on_another_device_are_not(self, monkeypatch): + """SageAttention launches on the thread's current device; tensors elsewhere would be read from the wrong GPU.""" + monkeypatch.setattr(torch.cuda, "current_device", lambda: 0) + assert sage._cuda_index(self._on(1), self._on(1), self._on(1)) is None + + def test_mixed_devices_and_cpu_are_not(self, monkeypatch): + monkeypatch.setattr(torch.cuda, "current_device", lambda: 0) + assert sage._cuda_index(self._on(0), self._on(1), self._on(0)) is None + cpu = SimpleNamespace(device=torch.device("cpu")) + assert sage._cuda_index(cpu, cpu, cpu) is None + + +def _raise(error: Exception): + def raiser(): + raise error + + return raiser + + +class TestInstall: + def test_install_is_idempotent(self, monkeypatch): + _cuda_build(monkeypatch, {0: (8, 9)}) + spy = F.scaled_dot_product_attention + install_sage_attention() + wrapped = F.scaled_dot_product_attention + install_sage_attention() + assert F.scaled_dot_product_attention is wrapped + assert wrapped.__wrapped__ is spy + + @pytest.mark.parametrize( + ("patch", "message"), + [ + (lambda m: m.setattr(torch.version, "hip", "7.2"), "NVIDIA GPU with CUDA"), + (lambda m: m.setattr(torch.cuda, "is_available", lambda: False), "NVIDIA GPU with CUDA"), + (lambda m: m.setattr(torch.cuda, "get_device_capability", lambda index=None: (7, 5)), "8.0 or newer"), + (lambda m: m.setattr(sage, "_import_sageattention", _raise(ModuleNotFoundError("x", name="sageattention"))), + "`sageattention` package is not installed"), + (lambda m: m.setattr(sage, "_import_sageattention", _raise(ModuleNotFoundError("x", name="triton.language"))), + "triton-windows"), + (lambda m: m.setattr(sage, "_import_sageattention", _raise(PackageNotFoundError("sageattention"))), + "no package metadata"), + (lambda m: m.setattr(sage, "_import_sageattention", _raise(OSError("DLL load failed"))), "DLL load failed"), + (lambda m: m.setattr(sage, "_import_sageattention", lambda: ("1.0.6", FakeSageAttention())), + "SageAttention 1"), + (lambda m: m.setattr(sage, "_import_sageattention", + lambda: (VERSION, FakeSageAttention(without=("sageattn_qk_int8_pv_fp8_cuda",)))), + "sageattn_qk_int8_pv_fp8_cuda"), + ], + ids=["rocm", "no-cuda", "turing", "no-package", "no-triton", "no-metadata", "broken-import", "sageattention-1", + "missing-kernel"], + ) # fmt: skip + def test_install_keeps_torchs_function_and_says_why(self, monkeypatch, caplog, patch, message): + _cuda_build(monkeypatch, {0: (8, 9)}) + spy = F.scaled_dot_product_attention + patch(monkeypatch) + caplog.set_level(logging.WARNING, logger=sage.__name__) + + install_sage_attention() + + assert F.scaled_dot_product_attention is spy + assert any(message in r.message for r in caplog.records), [r.message for r in caplog.records] + + def test_a_kernel_that_would_swallow_smooth_k_is_refused(self, monkeypatch, caplog): + """Every real kernel ends in **kwargs, so a renamed `smooth_k` would be ignored and smoothing come back.""" + module = FakeSageAttention() + + def renamed(q, k, v, tensor_layout="HND", is_causal=False, sm_scale=None, pv_accum_dtype="fp32", **kwargs): + raise AssertionError("must not be called") + + renamed.__name__ = "sageattn_qk_int8_pv_fp8_cuda" + module.sageattn_qk_int8_pv_fp8_cuda = renamed + _cuda_build(monkeypatch, {0: (8, 9)}, module) + spy = F.scaled_dot_product_attention + caplog.set_level(logging.WARNING, logger=sage.__name__) + + install_sage_attention() + + assert F.scaled_dot_product_attention is spy + assert any("does not take smooth_k" in r.message for r in caplog.records) + + @pytest.mark.parametrize(("version", "installs"), [("2.2", True), ("3.0", True), ("2.1.1", False)]) + def test_versions_are_compared_by_their_numbers(self, monkeypatch, version, installs): + _cuda_build(monkeypatch, {0: (8, 9)}) + monkeypatch.setattr(sage, "_import_sageattention", lambda: (version, FakeSageAttention())) + + install_sage_attention() + + assert getattr(F.scaled_dot_product_attention, sage._SENTINEL, False) is installs + + def test_a_device_without_a_kernel_keeps_torch_while_others_serve(self, monkeypatch, caplog): + module, _ = _cuda_build(monkeypatch, {0: (8, 9), 1: (7, 5)}) + caplog.set_level(logging.INFO, logger=sage.__name__) + install_sage_attention() + device = {"index": 1} + monkeypatch.setattr(sage, "_cuda_index", lambda q, k, v: device["index"]) + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + + with sage_attention_scope(): + F.scaled_dot_product_attention(q, k, k) + device["index"] = 0 + F.scaled_dot_product_attention(q, k, k) + + assert len(module.calls) == 1, "only the sm_89 device runs SageAttention" + assert any("no SageAttention kernel for cuda:1" in r.message for r in caplog.records) + + @pytest.mark.parametrize("backend", ["sage", "auto"]) + def test_apply_monkeypatches_installs_sageattention_only_when_configured(self, monkeypatch, backend): + from invokeai.app.util.startup_utils import apply_monkeypatches + + _cuda_build(monkeypatch, {0: (8, 9)}) + monkeypatch.setattr(torch.cuda, "empty_cache", torch.cuda.empty_cache) + + apply_monkeypatches(backend) + + assert getattr(F.scaled_dot_product_attention, sage._SENTINEL, False) is (backend == "sage") + + +class TestLogging: + def test_first_use_on_a_device_is_announced_once_with_its_kernel(self, installed, caplog): + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + caplog.set_level(logging.INFO, logger=sage.__name__) + + with sage_attention_scope(): + F.scaled_dot_product_attention(q, k, k) + F.scaled_dot_product_attention(q, k, k) + + announcements = [r.message for r in caplog.records if "serves diffusion-model attention" in r.message] + assert announcements == [ + f"SageAttention {VERSION} serves diffusion-model attention on cuda:0 " + "(Fake GPU, sm_89, sageattn_qk_int8_pv_fp8_cuda)." + ] + + +_HAVE_SAGE = importlib.util.find_spec("sageattention") is not None + + +@pytest.mark.slow +@pytest.mark.skipif(not torch.cuda.is_available() or not _HAVE_SAGE, reason="needs an NVIDIA GPU and sageattention") +class TestOnNvidiaHardware: + """Real kernels: needs CUDA, `sageattention` >= 2.2 and compute capability 8.0+, and about 10 GB of free VRAM for + the float32 references. Measured on an RTX 4090.""" + + @pytest.fixture + def kernel(self, monkeypatch): + import sageattention + + capability = torch.cuda.get_device_capability(0) + if capability < (8, 0): + pytest.skip("SageAttention 2 needs compute capability 8.0 or newer") + monkeypatch.setattr(F, "scaled_dot_product_attention", TORCH_SDPA) + monkeypatch.setattr(sage, "_disabled_devices", set()) + monkeypatch.setattr(sage, "_validated_devices", set()) + install_sage_attention() + if not getattr(F.scaled_dot_product_attention, sage._SENTINEL, False): + pytest.skip("the installed sageattention is not one InvokeAI uses (see the startup warning)") + cuda = sage._parse_version(torch.version.cuda) + assert cuda is not None + return sage._kernel(sageattention, capability, cuda[:2]) + + def test_every_kernel_takes_the_arguments_it_is_given(self, kernel): + import sageattention + + for capability in ((8, 0), (8, 9), (9, 0), (12, 0)): + assert sage._unaccepted_arguments(sage._kernel(sageattention, capability, (13, 0))) == [] + + @pytest.mark.parametrize( + ("shape_q", "shape_kv", "dtype", "gqa"), + [ + ((1, 24, 4608, 128), (1, 24, 4608, 128), torch.bfloat16, False), # FLUX.1 1024^2 + ((2, 10, 4096, 64), (2, 10, 4096, 64), torch.float16, False), # SDXL 1024^2, level 1 + ((1, 48, 4608, 128), (1, 12, 4608, 128), torch.bfloat16, True), # Krea-2 GQA + ], + ids=["flux", "sdxl", "krea2-gqa"], + ) + def test_output_stays_close_to_a_float32_reference(self, kernel, shape_q, shape_kv, dtype, gqa): + g = torch.Generator(device="cuda").manual_seed(0) + + def make(b, h, s, d): # real activations arrive as [B, S, H, D] storage viewed as [B, H, S, D] + return torch.randn(b, s, h, d, device="cuda", generator=g).to(dtype).transpose(1, 2) + + q, k, v = make(*shape_q), make(*shape_kv), make(*shape_kv) + with sage_attention_scope(): + out = F.scaled_dot_product_attention(q, k, v, enable_gqa=gqa) + + ref = _reference(q, k, v) + assert torch.isfinite(out).all() + cos = F.cosine_similarity(out.float().flatten(2), ref.flatten(2), dim=-1) + # Measured on random inputs: worst head 0.9991-0.9993; SDPA in bf16 reaches 0.99999. + assert cos.min() > 0.998 + assert (out.float() - ref).norm() / ref.norm() < 0.06 + + def test_the_outlier_channel_of_qwen_images_first_block_stays_accurate(self, kernel): + """Real activations (see the fixture's metadata). With K smoothing on, as `sageattn` runs it, the relative + error here is 1.62; with it off, 0.10.""" + from safetensors.torch import load_file + + t = {name: tensor.cuda() for name, tensor in load_file(FIXTURE).items()} + + out = kernel(t["q"], t["k"], t["v"], tensor_layout="HND", is_causal=False, sm_scale=128**-0.5) + + ref = _reference(t["q"], t["k"], t["v"]) + assert (out.float() - ref).norm() / ref.norm() < 0.2 + + def test_in_scope_it_is_the_kernel_exactly_and_outside_it_is_torch_exactly(self, kernel): + q, k, v = (torch.randn(1, 4608, 24, 128, device="cuda", dtype=torch.bfloat16).transpose(1, 2) for _ in range(3)) + mask = torch.zeros(1, 1, 4608, 4608, device="cuda", dtype=torch.bfloat16) + direct = kernel(q, k, v, tensor_layout="HND", is_causal=False, sm_scale=128**-0.5) + + with sage_attention_scope(): + assert torch.equal(F.scaled_dot_product_attention(q, k, v), direct) + masked = F.scaled_dot_product_attention(q, k, v, attn_mask=mask) + assert torch.equal(masked, TORCH_SDPA(q, k, v, attn_mask=mask)) + with torch.autocast("cuda", dtype=torch.bfloat16): + assert torch.equal(F.scaled_dot_product_attention(q, k, v), TORCH_SDPA(q, k, v)) + assert torch.equal(F.scaled_dot_product_attention(q, k, v), TORCH_SDPA(q, k, v)) + + def test_extra_memory_per_call_stays_within_the_measured_bound(self, kernel): + """Including the first call, whose check against PyTorch must not cost a full-size reference.""" + q, k, v = (torch.randn(1, 4608, 24, 128, device="cuda", dtype=torch.bfloat16).transpose(1, 2) for _ in range(3)) + kernel(q, k, v, tensor_layout="HND", is_causal=False, sm_scale=128**-0.5) # load the kernels unmeasured + # Quantized copies of Q, K and V at ~1 byte per element; measured 1.7x that on an RTX 4090. + bound = 2.5 * (q.numel() + k.numel() + v.numel()) + with sage_attention_scope(): + for call in ("first, checked", "later"): + torch.cuda.synchronize() + torch.cuda.reset_peak_memory_stats() + before = torch.cuda.memory_allocated() + out = F.scaled_dot_product_attention(q, k, v) + torch.cuda.synchronize() + extra = torch.cuda.max_memory_allocated() - before - out.numel() * out.element_size() + assert extra <= bound, call + del out diff --git a/tests/test_config.py b/tests/test_config.py index e372baf6e33..128e0278875 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -101,6 +101,16 @@ def test_db_synchronous_defaults_to_full_and_loads_from_yaml(tmp_path: Path, pat assert load_and_migrate_config(temp_config_file).db_synchronous == "normal" +def test_attention_backend_defaults_to_auto_and_loads_from_yaml(tmp_path: Path, patch_rootdir: None) -> None: + # The default must stay `auto`: SageAttention changes images at the same seed, so it is opt-in only. + assert InvokeAIAppConfig().attention_backend == "auto" + + temp_config_file = tmp_path / "temp_invokeai.yaml" + temp_config_file.write_text('schema_version: "4.0.3"\nattention_backend: sage\n') + + assert load_and_migrate_config(temp_config_file).attention_backend == "sage" + + def test_read_config_from_file(tmp_path: Path, patch_rootdir: None): """Test reading configuration from a file.""" temp_config_file = tmp_path / "temp_invokeai.yaml" From e864d62574ef8088bfd93d51f00e23f29b8bae0f Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Sat, 3 Oct 2026 21:55:07 +0200 Subject: [PATCH 2/9] feat(flux): run FLUX.1 attention on SageAttention when enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With `attention_backend: sage`, FLUX.1 denoising, including ControlNets inside the loop, enters the SageAttention scope. RTX 4090, FLUX.1 dev FP8: 16.1 s → 14.0 s at 1024², 84.2 s → 54.8 s at 2048², peak VRAM unchanged; a blind review judged no image worse. --- .../Optimization/sage-attention.mdx | 16 ++++++- invokeai/app/invocations/flux/flux_denoise.py | 42 ++++++++++--------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx index 5b5aea9df78..1f539afadaf 100644 --- a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx +++ b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx @@ -18,8 +18,20 @@ the video models were checked against PyTorch's attention for picture changes an ## Supported models -SageAttention is enabled per model family once that family has been measured with it. No model family uses it yet, -so every model keeps PyTorch's attention. +SageAttention is enabled per model family once that family has been measured with it. All other models keep PyTorch's +attention. + +Whole generation (text encoding, denoising and decoding) with the model already loaded, on an RTX 4090 under Windows, +compared with PyTorch's default attention there: + +| Model (as measured) | 1024×1024 | 2048×2048 | Notes | +| --- | --- | --- | --- | +| FLUX.1 dev (FP8) | 16.1 s → 14.0 s (−13 %) | 84.2 s → 54.8 s (−35 %) | Also serves ControlNets inside the denoising loop. | + +At 1024×1024 most of a step is spent outside attention, so the gain is small; at high resolutions and for video, +attention dominates. GGUF models spend much of each step dequantizing weights, which SageAttention does not touch. +Windows' PyTorch has no flash-attention kernel; on Linux PyTorch's own baseline is faster and the gain correspondingly +smaller (not measured). ## Requirements diff --git a/invokeai/app/invocations/flux/flux_denoise.py b/invokeai/app/invocations/flux/flux_denoise.py index 120effcf1d3..d1e33d9c500 100644 --- a/invokeai/app/invocations/flux/flux_denoise.py +++ b/invokeai/app/invocations/flux/flux_denoise.py @@ -69,6 +69,7 @@ from invokeai.backend.stable_diffusion.diffusers_pipeline import PipelineIntermediateState from invokeai.backend.stable_diffusion.diffusion.conditioning_data import FLUXConditioningInfo from invokeai.backend.util.devices import TorchDevice +from invokeai.backend.util.sage_attention import sage_attention_scope @invocation( @@ -524,26 +525,27 @@ def _run_diffusion( else: context.logger.debug(f"DyPE disabled: resolution={self.width}x{self.height}, preset={self.dype_preset}") - x = denoise( - model=transformer, - img=x, - img_ids=img_ids, - pos_regional_prompting_extension=pos_regional_prompting_extension, - neg_regional_prompting_extension=neg_regional_prompting_extension, - timesteps=timesteps, - step_callback=self._build_step_callback(context), - guidance=self.guidance, - cfg_scale=cfg_scale, - inpaint_extension=inpaint_extension, - controlnet_extensions=controlnet_extensions, - pos_ip_adapter_extensions=pos_ip_adapter_extensions, - neg_ip_adapter_extensions=neg_ip_adapter_extensions, - img_cond=img_cond, - img_cond_seq=img_cond_seq, - img_cond_seq_ids=img_cond_seq_ids, - dype_extension=dype_extension, - scheduler=scheduler, - ) + with sage_attention_scope(): + x = denoise( + model=transformer, + img=x, + img_ids=img_ids, + pos_regional_prompting_extension=pos_regional_prompting_extension, + neg_regional_prompting_extension=neg_regional_prompting_extension, + timesteps=timesteps, + step_callback=self._build_step_callback(context), + guidance=self.guidance, + cfg_scale=cfg_scale, + inpaint_extension=inpaint_extension, + controlnet_extensions=controlnet_extensions, + pos_ip_adapter_extensions=pos_ip_adapter_extensions, + neg_ip_adapter_extensions=neg_ip_adapter_extensions, + img_cond=img_cond, + img_cond_seq=img_cond_seq, + img_cond_seq_ids=img_cond_seq_ids, + dype_extension=dype_extension, + scheduler=scheduler, + ) x = unpack(x.float(), self.height, self.width) return x From a1e97b7b096c2ebe53ea5f25c4223738a6ac5c09 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Sat, 3 Oct 2026 21:55:08 +0200 Subject: [PATCH 3/9] feat(flux2): run FLUX.2 attention on SageAttention when enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With `attention_backend: sage`, FLUX.2 denoising enters the SageAttention scope. RTX 4090, FLUX.2 Klein 9B FP8: 15.6 s → 15.1 s at 1024², 27.1 s → 23.1 s at 2048², peak VRAM unchanged; a blind review judged no image worse. --- .../Optimization/sage-attention.mdx | 1 + .../app/invocations/flux2/flux2_denoise.py | 40 ++++++++++--------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx index 1f539afadaf..4a0d5190e0c 100644 --- a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx +++ b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx @@ -27,6 +27,7 @@ compared with PyTorch's default attention there: | Model (as measured) | 1024×1024 | 2048×2048 | Notes | | --- | --- | --- | --- | | FLUX.1 dev (FP8) | 16.1 s → 14.0 s (−13 %) | 84.2 s → 54.8 s (−35 %) | Also serves ControlNets inside the denoising loop. | +| FLUX.2 Klein 9B (FP8) | 15.6 s → 15.1 s (−3 %) | 27.1 s → 23.1 s (−15 %) | | At 1024×1024 most of a step is spent outside attention, so the gain is small; at high resolutions and for video, attention dominates. GGUF models spend much of each step dequantizing weights, which SageAttention does not touch. diff --git a/invokeai/app/invocations/flux2/flux2_denoise.py b/invokeai/app/invocations/flux2/flux2_denoise.py index b8470746e1e..b4404c7f8d5 100644 --- a/invokeai/app/invocations/flux2/flux2_denoise.py +++ b/invokeai/app/invocations/flux2/flux2_denoise.py @@ -59,6 +59,7 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import FLUXConditioningInfo from invokeai.backend.util.attention import sdpa_score_matrix_bytes from invokeai.backend.util.devices import TorchDevice +from invokeai.backend.util.sage_attention import sage_attention_scope # FLUX.2 attention geometry. The head dim is 128 across every variant and the head count follows # the hidden size (Klein 4B: 3072/24, Klein 9B: 4096/32, [dev] 6144/48), so the width is the single @@ -623,25 +624,26 @@ def _run_diffusion(self, context: InvocationContext) -> torch.Tensor: "Regional masks will be ignored for this generation." ) - x = denoise( - model=transformer, - img=x, - img_ids=img_ids, - txt=txt, - txt_ids=txt_ids, - timesteps=timesteps, - step_callback=self._build_step_callback(context), - guidance=self.guidance, - cfg_scale=cfg_scale_list, - neg_txt=neg_txt, - neg_txt_ids=neg_txt_ids, - scheduler=scheduler, - mu=mu, - inpaint_extension=inpaint_extension, - img_cond_seq=img_cond_seq, - img_cond_seq_ids=img_cond_seq_ids, - pos_joint_attention_kwargs=pos_joint_attention_kwargs, - ) + with sage_attention_scope(): + x = denoise( + model=transformer, + img=x, + img_ids=img_ids, + txt=txt, + txt_ids=txt_ids, + timesteps=timesteps, + step_callback=self._build_step_callback(context), + guidance=self.guidance, + cfg_scale=cfg_scale_list, + neg_txt=neg_txt, + neg_txt_ids=neg_txt_ids, + scheduler=scheduler, + mu=mu, + inpaint_extension=inpaint_extension, + img_cond_seq=img_cond_seq, + img_cond_seq_ids=img_cond_seq_ids, + pos_joint_attention_kwargs=pos_joint_attention_kwargs, + ) # Apply BN denormalization if BN stats are available # The diffusers Flux2KleinPipeline applies: latents = latents * bn_std + bn_mean From 6c8718945af881bdaeb2b092567df7e094370eee Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Sat, 3 Oct 2026 21:55:10 +0200 Subject: [PATCH 4/9] feat(z-image): run Z-Image attention on SageAttention when enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With `attention_backend: sage`, Z-Image denoising enters the SageAttention scope; Z-Image Control masks its attention and keeps SDPA. RTX 4090, Z-Image FP8: 10.4 s → 10.0 s at 1024², 33.7 s → 25.9 s at 2048², peak VRAM unchanged; a blind review judged no image worse. --- .../content/docs/configuration/Optimization/sage-attention.mdx | 1 + invokeai/app/invocations/z_image/z_image_denoise.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx index 4a0d5190e0c..915182f6cde 100644 --- a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx +++ b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx @@ -28,6 +28,7 @@ compared with PyTorch's default attention there: | --- | --- | --- | --- | | FLUX.1 dev (FP8) | 16.1 s → 14.0 s (−13 %) | 84.2 s → 54.8 s (−35 %) | Also serves ControlNets inside the denoising loop. | | FLUX.2 Klein 9B (FP8) | 15.6 s → 15.1 s (−3 %) | 27.1 s → 23.1 s (−15 %) | | +| Z-Image (FP8) | 10.4 s → 10.0 s (−5 %) | 33.7 s → 25.9 s (−23 %) | Z-Image Control masks its attention and keeps PyTorch's. | At 1024×1024 most of a step is spent outside attention, so the gain is small; at high resolutions and for video, attention dominates. GGUF models spend much of each step dequantizing weights, which SageAttention does not touch. diff --git a/invokeai/app/invocations/z_image/z_image_denoise.py b/invokeai/app/invocations/z_image/z_image_denoise.py index 5894899b873..e9d7659d1b8 100644 --- a/invokeai/app/invocations/z_image/z_image_denoise.py +++ b/invokeai/app/invocations/z_image/z_image_denoise.py @@ -39,6 +39,7 @@ from invokeai.backend.util.attention import sdpa_score_matrix_bytes from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.fp8 import get_model_compute_dtype +from invokeai.backend.util.sage_attention import sage_attention_scope from invokeai.backend.z_image.extensions.regional_prompting_extension import ZImageRegionalPromptingExtension from invokeai.backend.z_image.text_conditioning import ZImageTextConditioning from invokeai.backend.z_image.z_image_control_adapter import ZImageControlAdapter @@ -699,6 +700,8 @@ def _run_diffusion(self, context: InvocationContext) -> torch.Tensor: ) ) + exit_stack.enter_context(sage_attention_scope()) + # Denoising loop - supports both built-in Euler and diffusers schedulers # Track user-facing step for progress (accounts for Heun's double steps) user_step = 0 From 4abdcd48dde9e59a371739a3646e786e6f1879fa Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Sat, 3 Oct 2026 21:55:11 +0200 Subject: [PATCH 5/9] feat(qwen-image): run Qwen-Image attention on SageAttention when enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With `attention_backend: sage`, Qwen-Image denoising enters the SageAttention scope. RTX 4090, Qwen-Image 2512 GGUF Q4_K_M: 78.8 s → 73.5 s at 1024², 208.9 s → 150.3 s at 2048², peak VRAM unchanged; a blind review judged no image worse. --- .../content/docs/configuration/Optimization/sage-attention.mdx | 1 + invokeai/app/invocations/qwen_image/qwen_image_denoise.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx index 915182f6cde..563925e6255 100644 --- a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx +++ b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx @@ -29,6 +29,7 @@ compared with PyTorch's default attention there: | FLUX.1 dev (FP8) | 16.1 s → 14.0 s (−13 %) | 84.2 s → 54.8 s (−35 %) | Also serves ControlNets inside the denoising loop. | | FLUX.2 Klein 9B (FP8) | 15.6 s → 15.1 s (−3 %) | 27.1 s → 23.1 s (−15 %) | | | Z-Image (FP8) | 10.4 s → 10.0 s (−5 %) | 33.7 s → 25.9 s (−23 %) | Z-Image Control masks its attention and keeps PyTorch's. | +| Qwen-Image 2512 (GGUF Q4_K_M) | 78.8 s → 73.5 s (−7 %) | 208.9 s → 150.3 s (−28 %) | One block reaches about 17 % error per call; images were still judged equivalent. | At 1024×1024 most of a step is spent outside attention, so the gain is small; at high resolutions and for video, attention dominates. GGUF models spend much of each step dequantizing weights, which SageAttention does not touch. diff --git a/invokeai/app/invocations/qwen_image/qwen_image_denoise.py b/invokeai/app/invocations/qwen_image/qwen_image_denoise.py index 3cd1d897171..f161d20c6ce 100644 --- a/invokeai/app/invocations/qwen_image/qwen_image_denoise.py +++ b/invokeai/app/invocations/qwen_image/qwen_image_denoise.py @@ -34,6 +34,7 @@ from invokeai.backend.stable_diffusion.diffusers_pipeline import PipelineIntermediateState from invokeai.backend.stable_diffusion.diffusion.conditioning_data import QwenImageConditioningInfo from invokeai.backend.util.devices import TorchDevice +from invokeai.backend.util.sage_attention import sage_attention_scope @invocation( @@ -484,6 +485,8 @@ def _run_diffusion(self, context: InvocationContext): ) ) + exit_stack.enter_context(sage_attention_scope()) + for step_idx, t in enumerate(tqdm(timesteps_sched)): # The pipeline passes timestep / 1000 to the transformer timestep = t.expand(latents.shape[0]).to(inference_dtype) From 482e7f348f0fc5e1a0b0eeb8ce9ccf0ec3d0646a Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Sat, 3 Oct 2026 21:55:12 +0200 Subject: [PATCH 6/9] feat(krea2): run Krea-2 attention on SageAttention when enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With `attention_backend: sage`, Krea-2 denoising enters the SageAttention scope unless INVOKE_KREA2_SDPA_BACKEND pins a PyTorch kernel. RTX 4090, Krea-2 Turbo GGUF Q4_K_M: 11.5 s → 11.0 s at 1024², 42.7 s → 36.2 s at 2048², peak VRAM unchanged; a blind review judged no image worse. --- .../docs/configuration/Optimization/sage-attention.mdx | 1 + invokeai/app/invocations/krea2/krea2_denoise.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx index 563925e6255..dbbe1133297 100644 --- a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx +++ b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx @@ -30,6 +30,7 @@ compared with PyTorch's default attention there: | FLUX.2 Klein 9B (FP8) | 15.6 s → 15.1 s (−3 %) | 27.1 s → 23.1 s (−15 %) | | | Z-Image (FP8) | 10.4 s → 10.0 s (−5 %) | 33.7 s → 25.9 s (−23 %) | Z-Image Control masks its attention and keeps PyTorch's. | | Qwen-Image 2512 (GGUF Q4_K_M) | 78.8 s → 73.5 s (−7 %) | 208.9 s → 150.3 s (−28 %) | One block reaches about 17 % error per call; images were still judged equivalent. | +| Krea-2 Turbo (GGUF Q4_K_M) | 11.5 s → 11.0 s (−4 %) | 42.7 s → 36.2 s (−15 %) | Off while `INVOKE_KREA2_SDPA_BACKEND` pins a PyTorch kernel. | At 1024×1024 most of a step is spent outside attention, so the gain is small; at high resolutions and for video, attention dominates. GGUF models spend much of each step dequantizing weights, which SageAttention does not touch. diff --git a/invokeai/app/invocations/krea2/krea2_denoise.py b/invokeai/app/invocations/krea2/krea2_denoise.py index d9aca4e851b..b17c1066d14 100644 --- a/invokeai/app/invocations/krea2/krea2_denoise.py +++ b/invokeai/app/invocations/krea2/krea2_denoise.py @@ -63,6 +63,7 @@ from invokeai.backend.util.attention import sdpa_score_matrix_bytes from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.logging import InvokeAILogger +from invokeai.backend.util.sage_attention import sage_attention_scope # Krea-2 latent channels (Qwen-Image VAE z_dim). The packed transformer in_channels is 16 * patch_size**2 = 64. KREA2_LATENT_CHANNELS = 16 @@ -625,6 +626,11 @@ def _run_diffusion(self, context: InvocationContext): # the sampler will use. style_extension.prepare([sigma.item() for sigma in sigmas_sched[:total_steps]]) + # An INVOKE_KREA2_SDPA_BACKEND override pins one PyTorch kernel to measure it; SageAttention must not + # serve those calls instead. + if resolve_krea2_sdpa_backends().override is None: + exit_stack.enter_context(sage_attention_scope()) + benchmark = _Krea2StepBenchmark.create(device) for step_idx, t in enumerate(tqdm(timesteps_sched)): From 1968eb9c5862dd9e0c68509e0d6b1a878d85ce0b Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Sat, 3 Oct 2026 21:55:14 +0200 Subject: [PATCH 7/9] feat(sdxl): run SDXL attention on SageAttention when enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With `attention_backend: sage`, SDXL denoising and tiled multi-diffusion enter the SageAttention scope; SD1.5 and SD2 do not. RTX 4090: 4.4 s → 4.2 s at 1024², 19.6 s → 16.1 s at 2048², peak VRAM unchanged; a blind review judged no image worse. --- .../Optimization/sage-attention.mdx | 2 + .../app/invocations/sd/denoise_latents.py | 43 +++++++++++-------- .../tiled_multi_diffusion_denoise_latents.py | 26 ++++++----- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx index dbbe1133297..53cace0feb9 100644 --- a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx +++ b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx @@ -31,6 +31,7 @@ compared with PyTorch's default attention there: | Z-Image (FP8) | 10.4 s → 10.0 s (−5 %) | 33.7 s → 25.9 s (−23 %) | Z-Image Control masks its attention and keeps PyTorch's. | | Qwen-Image 2512 (GGUF Q4_K_M) | 78.8 s → 73.5 s (−7 %) | 208.9 s → 150.3 s (−28 %) | One block reaches about 17 % error per call; images were still judged equivalent. | | Krea-2 Turbo (GGUF Q4_K_M) | 11.5 s → 11.0 s (−4 %) | 42.7 s → 36.2 s (−15 %) | Off while `INVOKE_KREA2_SDPA_BACKEND` pins a PyTorch kernel. | +| SDXL | 4.4 s → 4.2 s (−5 %) | 19.6 s → 16.1 s (−18 %) | Text cross-attention stays on PyTorch (too short to gain). Also serves ControlNets. | At 1024×1024 most of a step is spent outside attention, so the gain is small; at high resolutions and for video, attention dominates. GGUF models spend much of each step dequantizing weights, which SageAttention does not touch. @@ -119,6 +120,7 @@ Everything else keeps PyTorch's attention: - **Short sequences** — fewer than 1536 query or 512 key tokens. There the cost of quantizing the inputs outweighs the faster kernel. - **Head sizes other than 64 and 128.** +- **SDXL with `attention_type: sliced`** — sliced attention does not go through PyTorch's attention kernel. - **Models not listed under Supported models**, such as SD 1.5 and SD 2. Pinning a PyTorch kernel through diffusers' `DIFFUSERS_ATTN_BACKEND` does not stop SageAttention; set diff --git a/invokeai/app/invocations/sd/denoise_latents.py b/invokeai/app/invocations/sd/denoise_latents.py index aa5373fd0f8..c80e24e8a62 100644 --- a/invokeai/app/invocations/sd/denoise_latents.py +++ b/invokeai/app/invocations/sd/denoise_latents.py @@ -81,6 +81,7 @@ from invokeai.backend.util.fp8 import get_model_compute_dtype from invokeai.backend.util.hotfixes import ControlNetModel from invokeai.backend.util.mask import to_standard_float_mask +from invokeai.backend.util.sage_attention import sage_attention_scope from invokeai.backend.util.silence_warnings import SilenceWarnings @@ -1008,7 +1009,11 @@ def step_callback(state: PipelineIntermediateState) -> None: sd_backend = StableDiffusionBackend(unet, scheduler) denoise_ctx.unet = unet - result_latents = sd_backend.latents_from_embeddings(denoise_ctx, ext_manager) + # SageAttention is validated on SDXL only; SD1.5's head sizes never qualify, SD2 is unmeasured. + with ( + sage_attention_scope() if self.unet.unet.base == BaseModelType.StableDiffusionXL else nullcontext() + ): + result_latents = sd_backend.latents_from_embeddings(denoise_ctx, ext_manager) # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 result_latents = result_latents.detach().to("cpu") @@ -1162,22 +1167,26 @@ def _lora_loader() -> Iterator[PatchSpec]: if self.hidiffusion else nullcontext() ): - result_latents = pipeline.latents_from_embeddings( - latents=latents, - timesteps=timesteps, - init_timestep=init_timestep, - noise=noise, - seed=seed, - mask=mask, - masked_latents=masked_latents, - is_gradient_mask=gradient_mask, - scheduler_step_kwargs=scheduler_step_kwargs, - conditioning_data=conditioning_data, - control_data=controlnet_data, - ip_adapter_data=ip_adapter_data, - t2i_adapter_data=t2i_adapter_data, - callback=step_callback, - ) + # SageAttention is validated on SDXL only; SD1.5's head sizes never qualify, SD2 is unmeasured. + with ( + sage_attention_scope() if self.unet.unet.base == BaseModelType.StableDiffusionXL else nullcontext() + ): + result_latents = pipeline.latents_from_embeddings( + latents=latents, + timesteps=timesteps, + init_timestep=init_timestep, + noise=noise, + seed=seed, + mask=mask, + masked_latents=masked_latents, + is_gradient_mask=gradient_mask, + scheduler_step_kwargs=scheduler_step_kwargs, + conditioning_data=conditioning_data, + control_data=controlnet_data, + ip_adapter_data=ip_adapter_data, + t2i_adapter_data=t2i_adapter_data, + callback=step_callback, + ) # https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699 result_latents = result_latents.to("cpu") diff --git a/invokeai/app/invocations/sd/tiled_multi_diffusion_denoise_latents.py b/invokeai/app/invocations/sd/tiled_multi_diffusion_denoise_latents.py index 7e4e93428ab..039f5a711f4 100644 --- a/invokeai/app/invocations/sd/tiled_multi_diffusion_denoise_latents.py +++ b/invokeai/app/invocations/sd/tiled_multi_diffusion_denoise_latents.py @@ -1,5 +1,5 @@ import copy -from contextlib import ExitStack +from contextlib import ExitStack, nullcontext from typing import Iterator import torch @@ -22,6 +22,7 @@ from invokeai.app.invocations.sd.controlnet import ControlField from invokeai.app.invocations.sd.denoise_latents import DenoiseLatentsInvocation, get_scheduler from invokeai.app.services.shared.invocation_context import InvocationContext +from invokeai.backend.model_manager.taxonomy import BaseModelType from invokeai.backend.patches.layer_patcher import LayerPatcher, PatchSpec from invokeai.backend.patches.model_patch_raw import ModelPatchRaw from invokeai.backend.stable_diffusion.diffusers_pipeline import ControlNetData, PipelineIntermediateState @@ -36,6 +37,7 @@ from invokeai.backend.tiles.utils import TBLR from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.fp8 import get_model_compute_dtype +from invokeai.backend.util.sage_attention import sage_attention_scope def crop_controlnet_data(control_data: ControlNetData, latent_region: TBLR) -> ControlNetData: @@ -279,16 +281,18 @@ def _lora_loader() -> Iterator[PatchSpec]: ) # Run Multi-Diffusion denoising. - result_latents = pipeline.multi_diffusion_denoise( - multi_diffusion_conditioning=multi_diffusion_conditioning, - target_overlap=latent_tile_overlap, - latents=latents, - scheduler_step_kwargs=scheduler_step_kwargs, - noise=noise, - timesteps=timesteps, - init_timestep=init_timestep, - callback=step_callback, - ) + # SageAttention is validated on SDXL only; SD1.5's head sizes never qualify, SD2 is unmeasured. + with sage_attention_scope() if self.unet.unet.base == BaseModelType.StableDiffusionXL else nullcontext(): + result_latents = pipeline.multi_diffusion_denoise( + multi_diffusion_conditioning=multi_diffusion_conditioning, + target_overlap=latent_tile_overlap, + latents=latents, + scheduler_step_kwargs=scheduler_step_kwargs, + noise=noise, + timesteps=timesteps, + init_timestep=init_timestep, + callback=step_callback, + ) result_latents = result_latents.to("cpu") # TODO(ryand): I copied this from DenoiseLatentsInvocation. I'm not sure if it's actually important. From e9aa6c6e2fdbfdd9f65a4d15b767c33deb5e1863 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Fri, 9 Oct 2026 05:30:40 +0200 Subject: [PATCH 8/9] fix(attention): follow upstream's sm86 kernel and recover from transient SageAttention failures sm86 now runs the Triton kernel upstream's sageattn picks there, and sm87 gets none; the Windows build's CUDA routing for both is unexplained and unmeasured here. Out-of-memory and Triton cache errors fall back to SDPA for that call (three retire the device), and the first-call check runs per precision and head size. The kernel runs with the tensors' device current, so a single-device install on cuda:1 uses SageAttention too. --- .../Optimization/sage-attention.mdx | 23 ++- invokeai/backend/util/sage_attention.py | 91 ++++++--- tests/backend/util/test_sage_attention.py | 174 +++++++++++++++--- 3 files changed, 226 insertions(+), 62 deletions(-) diff --git a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx index 5b5aea9df78..f1d9baad464 100644 --- a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx +++ b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx @@ -24,8 +24,8 @@ so every model keeps PyTorch's attention. ## Requirements - **An NVIDIA GPU with compute capability 8.0 or newer** — RTX 30-series, RTX 40-series, RTX 50-series, A100, H100. - InvokeAI has measured it on an RTX 4090; the other generations use SageAttention's own kernels for them, which - InvokeAI has not validated. + InvokeAI has measured it on an RTX 4090; the other generations use the kernels SageAttention picks for them, which + InvokeAI has not validated (on RTX 30-series cards, its Triton kernel). Jetson Orin is not supported. - **SageAttention 2.2, installed separately.** InvokeAI was validated with 2.2.0. The `sageattention` package on PyPI is the older SageAttention 1 and is not used. On Windows it also needs the `triton-windows` package. - Not available on AMD (ROCm), Apple Silicon (MPS), Intel (XPU) or CPU. There InvokeAI keeps PyTorch's attention. @@ -98,26 +98,31 @@ calls ran on SageAttention and why the others did not. To go back, set `attentio SageAttention serves the attention inside the denoising model of the families under [Supported models](#supported-models). Everything else keeps PyTorch's attention: -- **Text encoders, image encoders and VAEs.** They run outside the denoising loop. +- **Text encoders, image encoders and VAEs.** They run outside the denoising loop. What runs inside it, such as a + ControlNet, is served like the model itself. - **Masked attention**, such as regional prompts. SageAttention has no mask input. - **Short sequences** — fewer than 1536 query or 512 key tokens. There the cost of quantizing the inputs outweighs the faster kernel. - **Head sizes other than 64 and 128.** - **Models not listed under Supported models**, such as SD 1.5 and SD 2. -Pinning a PyTorch kernel through diffusers' `DIFFUSERS_ATTN_BACKEND` does not stop SageAttention; set -`attention_backend: auto` to measure PyTorch's own kernels. +Pinning a PyTorch kernel through diffusers' `DIFFUSERS_ATTN_BACKEND`, or in a custom node with +`torch.nn.attention.sdpa_kernel`, does not stop SageAttention; set `attention_backend: auto` to measure PyTorch's own +kernels. ## Notes - **The first generation pauses briefly.** SageAttention compiles a few Triton kernels on first use, about two seconds on an RTX 4090; they are cached on disk afterwards. -- **Each GPU's first SageAttention call is checked against PyTorch's attention.** If the result is wrong or the kernel - fails, InvokeAI logs a warning once and that GPU uses PyTorch's attention until restart. A fault the GPU raises - later, while running a kernel, cannot be caught that way and ends the generation. +- **Each GPU's first SageAttention call is checked against PyTorch's attention**, once for each precision and head + size. If the result is wrong or the kernel fails, InvokeAI logs a warning once and that GPU uses PyTorch's attention + until restart. Some GPU faults (an illegal memory access, for example) leave the GPU unusable for the rest of the + process: then PyTorch's attention fails the same way, and only a restart helps. A fault the GPU raises later, while + running a kernel, cannot be caught at all and ends the generation. - **Memory.** Peak VRAM was unchanged for every measured model. Each call holds quantized copies of its inputs, about 1.7 bytes per element of the query, key and value together — around 70 MB for FLUX.1 at 1024×1024, and around 2 GB - per call for Wan 2.2 14B at 1280×720 with 81 frames (not measured). + per call for Wan 2.2 14B at 1280×720 with 81 frames (not measured). A call that does not fit runs on PyTorch's + attention instead, which needs less. - **Recall.** `attention_backend` is not stored in an image's metadata, so an image recalled after switching it is generated slightly differently. - **Black or noisy images** have been reported with SageAttention for some models in other applications. None appeared diff --git a/invokeai/backend/util/sage_attention.py b/invokeai/backend/util/sage_attention.py index da590ba1ca9..36879cb892b 100644 --- a/invokeai/backend/util/sage_attention.py +++ b/invokeai/backend/util/sage_attention.py @@ -60,6 +60,10 @@ # tile is among them. A kernel that never ran garbles every row, so a slice catches what the whole output would; the # whole output costs ~14 bytes per element to compare -- about 5 GB for one call of Wan 2.2 14B at 720p. _FIRST_CALL_ROWS = 1024 +# Failures that say nothing about the kernel -- Triton running out of memory while loading a compiled kernel, or losing +# a race for its cache file to a compile on another GPU -- fall back for that call only. A device that keeps failing +# that way is retired like any other, so a broken Triton cache does not recompile on every call. +_MAX_TRANSIENT_FAILURES = 3 DOCS_URL = "https://invoke-ai.github.io/InvokeAI-7/configuration/optimization/sage-attention/" @@ -75,7 +79,9 @@ class _Usage: _scope: ContextVar[_Usage | None] = ContextVar("invokeai_sage_attention_scope", default=None) _lock = threading.Lock() _disabled_devices: set[int] = set() -_validated_devices: set[int] = set() +# (device, dtype, head size): the kernel variants whose first result was compared with SDPA. +_validated: set[tuple[int, torch.dtype, int]] = set() +_transient_failures: Counter[int] = Counter() _announced_devices: set[int] = set() @@ -120,10 +126,12 @@ def _kernel( ) -> Callable[..., torch.Tensor] | None: """SageAttention's kernel for a GPU of this compute capability, or None where it has none. - The same kernels and accumulation SageAttention 2.2's own ``sageattn`` dispatches to -- including its choice by the - CUDA version torch was built for: the FP16 accumulation inside the FP8 kernels needs CUDA 12.8 to compile, and - below that the build replaces it with a trap -- but called with ``smooth_k`` off, which ``sageattn`` always leaves - on and has no argument to change. Smoothing subtracts K's mean over the sequence before quantizing; in the first + The kernels and accumulation upstream SageAttention 2.2's ``sageattn`` (thu-ml) dispatches to: the FP16 CUDA + kernel on sm80, the Triton one on sm86, nothing on sm87. The Windows build moved sm86 and sm87 to the CUDA kernel + without saying why; neither has been measured here, so upstream's choice stands. From the Windows build come the + sm100 kernel and the choice by the CUDA version torch was built for: the FP16 accumulation inside the FP8 kernels + needs CUDA 12.8 to compile, and below that the build replaces it with a trap. Every kernel is called with + ``smooth_k`` off, which ``sageattn`` always leaves on and has no argument to change. Smoothing subtracts K's mean over the sequence before quantizing; in the first block of Qwen-Image and Krea-2, where one channel of Q and K sits near +600 for every image token, that leaves the INT8 product Q.K no precision for the rest: cosine 0.18 and 0.53 against a float32 reference, 0.997 and 0.988 with smoothing off. Everywhere else measured it changes nothing, and off is 2-11 % faster (RTX 4090). Only sm_89 has been @@ -131,8 +139,10 @@ def _kernel( """ major, minor = capability recent_cuda = cuda >= (12, 8) - if major == 8 and minor != 9: # Ampere: FP16 P.V + if (major, minor) == (8, 0): # A100: FP16 P.V kernel, options = sageattention.sageattn_qk_int8_pv_fp16_cuda, {"pv_accum_dtype": "fp32"} + elif (major, minor) == (8, 6): # RTX 30-series, A10, A40: FP16 P.V in Triton + kernel, options = sageattention.sageattn_qk_int8_pv_fp16_triton, {} elif (major, minor) == (8, 9): # Ada: FP8 P.V kernel = sageattention.sageattn_qk_int8_pv_fp8_cuda options = {"pv_accum_dtype": "fp32+fp16" if recent_cuda else "fp32+fp32"} @@ -154,15 +164,16 @@ def _unaccepted_arguments(kernel: functools.partial) -> list[str]: def _cuda_index(query: torch.Tensor, key: torch.Tensor, value: torch.Tensor) -> int | None: - """The CUDA index of q/k/v when all three live on this thread's current device, else None. + """The CUDA index of q/k/v when all three live on the same CUDA device, else None. - SageAttention launches its kernels on the thread's current device without switching to the tensors' device, so - tensors anywhere else would be read from the wrong GPU. Session workers pin their thread to their own device. + SageAttention launches its kernels on the thread's current device without switching to the tensors' device, so the + wrapper makes that device current for the call. Session workers pin their thread to their device already; a + single-device install with `device: cuda:1` does not. """ device = query.device - if device.type != "cuda" or key.device != device or value.device != device: + if device.type != "cuda" or device.index is None or key.device != device or value.device != device: return None - return device.index if device.index == torch.cuda.current_device() else None + return device.index def _ineligible( @@ -222,13 +233,14 @@ def _check_first_result( scale: float | None, out: torch.Tensor, ) -> None: - """Raise unless a device's first SageAttention result agrees with PyTorch's. + """Raise unless the first SageAttention result of this kernel variant agrees with PyTorch's. SageAttention launches its CUDA kernels without checking for errors, so a kernel the installed build lacks for this GPU raises nothing: it returns uninitialized memory, and the launch error surfaces in some later, unrelated torch operation -- outside the fallback. Recomputing a slice of the first result with PyTorch, and reading the - comparison back to the host, brings both into the caller's ``try``. Once per device; later calls are not compared. - The slice is the first sample's first K/V head group and its last ``_FIRST_CALL_ROWS`` query rows. + comparison back to the host, brings both into the caller's ``try``. Once per device, precision and head size -- + the inputs that pick a different compiled kernel; later calls are not compared. The slice is the first sample's + first K/V head group and its last ``_FIRST_CALL_ROWS`` query rows. """ group = query.shape[1] // key.shape[1] rows = slice(-_FIRST_CALL_ROWS, None) @@ -239,7 +251,23 @@ def _check_first_result( if not math.isfinite(error) or error > _FIRST_CALL_MAX_ERROR: raise RuntimeError(f"its first result differs from PyTorch SDPA by {error:.2f} (relative L2)") with _lock: - _validated_devices.add(index) + _validated.add((index, query.dtype, query.shape[-1])) + + +def _transient(error: Exception) -> bool: + """Whether a failure says nothing about the kernel: memory, or a file Triton could not write. + + A CUDA fault that poisons the context (illegal address, a trap) is not among them; torch's fallback then raises the + same error, and a restart is the only way out. + """ + return isinstance(error, OSError) or "out of memory" in str(error).lower() + + +def _transient_failure(index: int) -> bool: + """Count a transient failure on this device; True once there were too many to keep retrying.""" + with _lock: + _transient_failures[index] += 1 + return _transient_failures[index] >= _MAX_TRANSIENT_FAILURES def _disable(index: int, error: Exception, query: torch.Tensor, key: torch.Tensor) -> None: @@ -353,22 +381,25 @@ def sage_scaled_dot_product_attention( reason = "device" else: try: - out = kernel( - query, - key, - value, - tensor_layout="HND", - is_causal=False, - sm_scale=scale if scale is not None else query.shape[-1] ** -0.5, - ) - if index not in _validated_devices: - _check_first_result(index, original, query, key, value, scale, out) - except torch.OutOfMemoryError: - # A resource condition, not a sign the kernel cannot serve this device. - raise + with torch.cuda.device(index): + out = kernel( + query, + key, + value, + tensor_layout="HND", + is_causal=False, + sm_scale=scale if scale is not None else query.shape[-1] ** -0.5, + ) + if (index, query.dtype, query.shape[-1]) not in _validated: + _check_first_result(index, original, query, key, value, scale, out) except Exception as e: - _disable(index, e, query, key) - reason = "error" + # SageAttention's quantized copies did not fit, or Triton could not load or cache a kernel: + # this call uses SDPA, which needs less memory, and the next tries SageAttention again. + if _transient(e) and not _transient_failure(index): + reason = "transient" + else: + _disable(index, e, query, key) + reason = "error" else: usage.served += 1 _announce(index, version, kernel) diff --git a/tests/backend/util/test_sage_attention.py b/tests/backend/util/test_sage_attention.py index f86ecfa4440..d670f085d1d 100644 --- a/tests/backend/util/test_sage_attention.py +++ b/tests/backend/util/test_sage_attention.py @@ -9,9 +9,12 @@ how the real kernels behave; `TestOnNvidiaHardware` does, on a GPU with SageAttention installed (`-m slow`). """ +import contextlib +import functools import importlib.util import logging import threading +from collections import Counter from importlib.metadata import PackageNotFoundError from pathlib import Path from types import SimpleNamespace @@ -28,6 +31,7 @@ VERSION = "2.2.0+cu130torch2.10.0andhigher.post6" KERNELS = ( "sageattn_qk_int8_pv_fp16_cuda", + "sageattn_qk_int8_pv_fp16_triton", "sageattn_qk_int8_pv_fp8_cuda", "sageattn_qk_int8_pv_fp8_cuda_sm90", ) @@ -115,11 +119,20 @@ def torch_spy( monkeypatch.setattr(sage, "_import_sageattention", lambda: (VERSION, module)) monkeypatch.setattr(sage, "_cuda_index", lambda q, k, v: 0) monkeypatch.setattr(sage, "_disabled_devices", set()) - monkeypatch.setattr(sage, "_validated_devices", set(capabilities)) + monkeypatch.setattr(sage, "_validated", _all_variants(capabilities)) + monkeypatch.setattr(sage, "_transient_failures", Counter()) monkeypatch.setattr(sage, "_announced_devices", set()) + monkeypatch.setattr(torch.cuda, "device", lambda index: contextlib.nullcontext()) return module, torch_calls +def _all_variants(devices) -> set: + """Every kernel variant of these devices, as if each had passed its first-call check.""" + return { + (index, dtype, head_dim) for index in devices for dtype in sage._HALF_PRECISION for head_dim in sage._HEAD_DIMS + } + + @pytest.fixture def installed(monkeypatch): """The wrapper installed on a build that looks like CUDA 13 with one sm_89 device.""" @@ -135,7 +148,8 @@ class TestKernelTable: ("capability", "cuda", "kernel", "options"), [ ((8, 0), (13, 0), "sageattn_qk_int8_pv_fp16_cuda", {"pv_accum_dtype": "fp32"}), - ((8, 6), (13, 0), "sageattn_qk_int8_pv_fp16_cuda", {"pv_accum_dtype": "fp32"}), + # Upstream's `sageattn` picks the Triton kernel here; the Windows build moved sm86 to CUDA unexplained. + ((8, 6), (13, 0), "sageattn_qk_int8_pv_fp16_triton", {}), ((8, 9), (13, 0), "sageattn_qk_int8_pv_fp8_cuda", {"pv_accum_dtype": "fp32+fp16"}), ((8, 9), (12, 6), "sageattn_qk_int8_pv_fp8_cuda", {"pv_accum_dtype": "fp32+fp32"}), ((9, 0), (13, 0), "sageattn_qk_int8_pv_fp8_cuda_sm90", {"pv_accum_dtype": "fp32+fp32"}), @@ -155,7 +169,9 @@ def test_each_generation_gets_its_kernel_with_smoothing_off(self, capability, cu assert module.calls[0]["kernel"] == kernel assert {name: module.calls[0][name] for name in (*options, "smooth_k")} == options | {"smooth_k": False} - @pytest.mark.parametrize("capability", [(7, 5), (10, 3), (11, 0)], ids=["turing", "sm103", "sm110"]) + @pytest.mark.parametrize( + "capability", [(7, 5), (8, 7), (10, 3), (11, 0)], ids=["turing", "jetson-orin", "sm103", "sm110"] + ) def test_gpus_sageattention_has_no_kernel_for_get_none(self, capability): assert sage._kernel(FakeSageAttention(), capability, (13, 0)) is None @@ -255,6 +271,24 @@ def test_an_input_that_requires_grad_under_grad_mode_reaches_torch(self, install assert module.calls == [] and len(torch_calls) == 1 + def test_a_call_under_cuda_autocast_reaches_torch(self, installed, monkeypatch): + """Autocast would hand SageAttention whatever dtype it picks; torch's SDPA applies autocast's rules itself.""" + module, torch_calls = installed + monkeypatch.setattr(torch, "is_autocast_enabled", lambda device_type=None: device_type == "cuda") + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + + with sage_attention_scope(): + F.scaled_dot_product_attention(q, k, k) + + assert module.calls == [] and len(torch_calls) == 1 + + @pytest.mark.filterwarnings("ignore:The PyTorch API of nested tensors") + def test_a_nested_query_is_not_eligible(self): + """Nested (jagged) tensors report four dimensions but have no fixed sequence length to quantize over.""" + q = torch.nested.nested_tensor([_q(2, QUERY_LEN, 64), _q(2, QUERY_LEN + 1, 64)]) + k = _q(2, 2, KEY_LEN, 64) + assert sage._ineligible(q, k, k, None, 0.0, False, False, False) == "shape" + def test_an_unknown_keyword_reaches_torch_rather_than_being_dropped(self, installed): module, torch_calls = installed q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) @@ -278,7 +312,7 @@ def test_grouped_query_attention_reaches_the_kernel_unexpanded(self, installed): class TestFirstCallCheck: def test_a_devices_first_result_is_compared_with_torch_once(self, monkeypatch): module, torch_calls = _cuda_build(monkeypatch, {0: (8, 9)}) - monkeypatch.setattr(sage, "_validated_devices", set()) + monkeypatch.setattr(sage, "_validated", set()) install_sage_attention() q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) @@ -289,11 +323,41 @@ def test_a_devices_first_result_is_compared_with_torch_once(self, monkeypatch): assert len(module.calls) == 2, "both calls ran on the kernel" assert len(torch_calls) == 1, "only the first was also computed by torch, for the comparison" + def test_the_check_pairs_a_gqa_query_group_with_its_own_kv_head(self, monkeypatch): + """Query heads 0-3 attend to K/V head 0; comparing them with head 1 instead would retire a correct kernel.""" + module, torch_calls = _cuda_build(monkeypatch, {0: (8, 9)}) + monkeypatch.setattr(sage, "_validated", set()) + install_sage_attention() + q, k, v = _q(1, 8, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64), _q(1, 2, KEY_LEN, 64) + v[:, 1] += 3.0 # a K/V head unlike head 0, so a wrong pairing cannot pass + + with sage_attention_scope(): + F.scaled_dot_product_attention(q, k, v, enable_gqa=True) + F.scaled_dot_product_attention(q, k, v, enable_gqa=True) + + assert len(module.calls) == 2, "the check passed, so the device stays on the kernel" + assert len(torch_calls) == 1 and torch_calls[0]["gqa"] and torch_calls[0]["q"] == (1, 4, 1024, 64) + + def test_each_precision_and_head_size_is_checked_on_its_first_call(self, monkeypatch): + """They select different compiled kernels; one passing says nothing about the others.""" + module, torch_calls = _cuda_build(monkeypatch, {0: (8, 9)}) + monkeypatch.setattr(sage, "_validated", set()) + install_sage_attention() + hd64, hd128 = _q(1, 2, QUERY_LEN, 64), _q(1, 2, QUERY_LEN, 128) + bf16 = _q(1, 2, QUERY_LEN, 64, dtype=torch.bfloat16) + + with sage_attention_scope(): + for q in (hd64, hd64, hd128, hd128, bf16, bf16): + F.scaled_dot_product_attention(q, q, q) + + assert len(module.calls) == 6 + assert len(torch_calls) == 3, "one comparison per variant" + @pytest.mark.parametrize("garbage", [torch.nan, 50.0], ids=["non-finite", "wrong"]) def test_a_wrong_first_result_retires_the_device_and_returns_torchs(self, monkeypatch, caplog, garbage): """A kernel the build lacks for this GPU returns uninitialized memory instead of raising.""" module, torch_calls = _cuda_build(monkeypatch, {0: (8, 9)}) - monkeypatch.setattr(sage, "_validated_devices", set()) + monkeypatch.setattr(sage, "_validated", set()) install_sage_attention() q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) module.result = torch.full((1, 2, QUERY_LEN, 64), garbage, dtype=torch.float16) @@ -333,18 +397,46 @@ def test_a_kernel_failure_falls_back_warns_once_and_retires_only_that_device(sel F.scaled_dot_product_attention(q, k, k) assert len(module.calls) == 2, "another device is unaffected" - def test_out_of_memory_propagates_and_does_not_retire_the_device(self, installed): - module, _ = installed - module.error = torch.OutOfMemoryError("CUDA out of memory") + @pytest.mark.parametrize( + "error", + [ + torch.OutOfMemoryError("CUDA out of memory. Tried to allocate 1.50 GiB"), + RuntimeError("Triton Error [CUDA]: out of memory"), + PermissionError("[WinError 5] Access is denied: 'triton\\cache\\tmp.pid_123'"), + ], + ids=["torch-oom", "triton-oom", "triton-cache-race"], + ) + def test_a_transient_failure_falls_back_for_that_call_only(self, installed, caplog, error): + """Memory or a file Triton could not write says nothing about the kernel: SDPA serves the call, the next one + tries SageAttention again, and nothing is logged as a failure.""" + module, torch_calls = installed + module.error = error q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + caplog.set_level(logging.WARNING, logger=sage.__name__) - with sage_attention_scope(), pytest.raises(torch.OutOfMemoryError): + with sage_attention_scope(): + out = F.scaled_dot_product_attention(q, k, k) + module.error = None F.scaled_dot_product_attention(q, k, k) - module.error = None + torch.testing.assert_close(out.float(), _reference(q, k, k), atol=2e-3, rtol=2e-3) + assert len(module.calls) == 2 and len(torch_calls) == 1 + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + + def test_a_device_that_keeps_failing_transiently_is_retired(self, installed, caplog): + """A Triton cache that can never be written would otherwise recompile on every call.""" + module, torch_calls = installed + module.error = PermissionError("Access is denied") + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + caplog.set_level(logging.WARNING, logger=sage.__name__) + with sage_attention_scope(): - F.scaled_dot_product_attention(q, k, k) - assert len(module.calls) == 2 + for _ in range(sage._MAX_TRANSIENT_FAILURES + 2): + F.scaled_dot_product_attention(q, k, k) + + assert len(module.calls) == sage._MAX_TRANSIENT_FAILURES + assert len(torch_calls) == sage._MAX_TRANSIENT_FAILURES + 2 + assert any("cuda:0" in r.message for r in caplog.records if r.levelno == logging.WARNING) def test_the_scope_closes_when_its_block_raises(self, installed): module, torch_calls = installed @@ -397,21 +489,43 @@ class TestDeviceRule: def _on(index: int) -> SimpleNamespace: return SimpleNamespace(device=torch.device("cuda", index)) - def test_tensors_on_the_threads_current_device_are_eligible(self, monkeypatch): - monkeypatch.setattr(torch.cuda, "current_device", lambda: 1) + def test_tensors_on_one_cuda_device_give_its_index(self): assert sage._cuda_index(self._on(1), self._on(1), self._on(1)) == 1 - def test_tensors_on_another_device_are_not(self, monkeypatch): - """SageAttention launches on the thread's current device; tensors elsewhere would be read from the wrong GPU.""" - monkeypatch.setattr(torch.cuda, "current_device", lambda: 0) - assert sage._cuda_index(self._on(1), self._on(1), self._on(1)) is None - - def test_mixed_devices_and_cpu_are_not(self, monkeypatch): - monkeypatch.setattr(torch.cuda, "current_device", lambda: 0) + def test_mixed_devices_and_cpu_do_not(self): assert sage._cuda_index(self._on(0), self._on(1), self._on(0)) is None cpu = SimpleNamespace(device=torch.device("cpu")) assert sage._cuda_index(cpu, cpu, cpu) is None + def test_the_kernel_runs_with_the_tensors_device_current(self, monkeypatch): + """SageAttention launches on the thread's current device. A single-device install with `device: cuda:1` never + makes that device current, so the wrapper does, for the call only.""" + module, _ = _cuda_build(monkeypatch, {0: (8, 9), 1: (8, 9)}) + events: list[str] = [] + kernel = module.sageattn_qk_int8_pv_fp8_cuda + + @functools.wraps(kernel) + def recording_kernel(*args, **kwargs): + events.append("kernel") + return kernel(*args, **kwargs) + + @contextlib.contextmanager + def recording_device(index): + events.append(f"enter cuda:{index}") + yield + events.append("exit") + + module.sageattn_qk_int8_pv_fp8_cuda = recording_kernel + monkeypatch.setattr(torch.cuda, "device", recording_device) + monkeypatch.setattr(sage, "_cuda_index", lambda q, k, v: 1) + install_sage_attention() + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + + with sage_attention_scope(): + F.scaled_dot_product_attention(q, k, k) + + assert events == ["enter cuda:1", "kernel", "exit"] + def _raise(error: Exception): def raiser(): @@ -552,7 +666,7 @@ def kernel(self, monkeypatch): pytest.skip("SageAttention 2 needs compute capability 8.0 or newer") monkeypatch.setattr(F, "scaled_dot_product_attention", TORCH_SDPA) monkeypatch.setattr(sage, "_disabled_devices", set()) - monkeypatch.setattr(sage, "_validated_devices", set()) + monkeypatch.setattr(sage, "_validated", set()) install_sage_attention() if not getattr(F.scaled_dot_product_attention, sage._SENTINEL, False): pytest.skip("the installed sageattention is not one InvokeAI uses (see the startup warning)") @@ -563,7 +677,7 @@ def kernel(self, monkeypatch): def test_every_kernel_takes_the_arguments_it_is_given(self, kernel): import sageattention - for capability in ((8, 0), (8, 9), (9, 0), (12, 0)): + for capability in ((8, 0), (8, 6), (8, 9), (9, 0), (12, 0)): assert sage._unaccepted_arguments(sage._kernel(sageattention, capability, (13, 0))) == [] @pytest.mark.parametrize( @@ -592,6 +706,20 @@ def make(b, h, s, d): # real activations arrive as [B, S, H, D] storage viewed assert cos.min() > 0.998 assert (out.float() - ref).norm() / ref.norm() < 0.06 + def test_the_triton_kernel_chosen_for_sm86_stays_close_to_a_float32_reference(self, kernel): + """No sm86 GPU here; the Triton kernel itself runs on any Ampere or newer GPU, so its numbers are checked.""" + import sageattention + + g = torch.Generator(device="cuda").manual_seed(0) + q, k, v = (torch.randn(1, 4608, 24, 128, device="cuda", generator=g).bfloat16().transpose(1, 2) for _ in "qkv") + triton_kernel = sage._kernel(sageattention, (8, 6), (13, 0)) + + out = triton_kernel(q, k, v, tensor_layout="HND", is_causal=False, sm_scale=128**-0.5) + + ref = _reference(q, k, v) + assert torch.isfinite(out).all() + assert (out.float() - ref).norm() / ref.norm() < 0.06 + def test_the_outlier_channel_of_qwen_images_first_block_stays_accurate(self, kernel): """Real activations (see the fixture's metadata). With K smoothing on, as `sageattn` runs it, the relative error here is 1.62; with it off, 0.10.""" From ce561e38ff9786ebe8e9f66fe3ce8c26e0a43dc1 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Fri, 9 Oct 2026 05:37:36 +0200 Subject: [PATCH 9/9] fix(attention): keep a GPU on SageAttention after running out of memory An out-of-memory error now moves only the rest of that generation to SDPA, logged once, instead of counting toward retiring the device; out-of-memory is recognised by the shared is_oom_error. Only Triton cache failures in a row retire a device, and a success resets the count. --- .../Optimization/sage-attention.mdx | 5 +- invokeai/backend/util/sage_attention.py | 66 ++++++++------ tests/backend/util/test_sage_attention.py | 89 ++++++++++++------- 3 files changed, 98 insertions(+), 62 deletions(-) diff --git a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx index f1d9baad464..def8250fab6 100644 --- a/docs/src/content/docs/configuration/Optimization/sage-attention.mdx +++ b/docs/src/content/docs/configuration/Optimization/sage-attention.mdx @@ -121,8 +121,9 @@ kernels. running a kernel, cannot be caught at all and ends the generation. - **Memory.** Peak VRAM was unchanged for every measured model. Each call holds quantized copies of its inputs, about 1.7 bytes per element of the query, key and value together — around 70 MB for FLUX.1 at 1024×1024, and around 2 GB - per call for Wan 2.2 14B at 1280×720 with 81 frames (not measured). A call that does not fit runs on PyTorch's - attention instead, which needs less. + per call for Wan 2.2 14B at 1280×720 with 81 frames (not measured). When a call does not fit, the rest of that + generation runs on PyTorch's attention, which needs less, and the log says so; the next generation tries + SageAttention again. - **Recall.** `attention_backend` is not stored in an image's metadata, so an image recalled after switching it is generated slightly differently. - **Black or noisy images** have been reported with SageAttention for some models in other applications. None appeared diff --git a/invokeai/backend/util/sage_attention.py b/invokeai/backend/util/sage_attention.py index 36879cb892b..7510e5a278f 100644 --- a/invokeai/backend/util/sage_attention.py +++ b/invokeai/backend/util/sage_attention.py @@ -36,6 +36,7 @@ import torch from invokeai.backend.util.logging import InvokeAILogger +from invokeai.backend.util.oom import is_oom_error logger = InvokeAILogger.get_logger(__name__) @@ -60,10 +61,10 @@ # tile is among them. A kernel that never ran garbles every row, so a slice catches what the whole output would; the # whole output costs ~14 bytes per element to compare -- about 5 GB for one call of Wan 2.2 14B at 720p. _FIRST_CALL_ROWS = 1024 -# Failures that say nothing about the kernel -- Triton running out of memory while loading a compiled kernel, or losing -# a race for its cache file to a compile on another GPU -- fall back for that call only. A device that keeps failing -# that way is retired like any other, so a broken Triton cache does not recompile on every call. -_MAX_TRANSIENT_FAILURES = 3 +# Triton failing to write a compiled kernel to its cache (on Windows, when two GPUs compile the same kernel at once) +# says nothing about the kernel: that call falls back and the next tries again. A device that fails this way this many +# times in a row is retired like any other, so a cache that can never be written does not recompile on every call. +_MAX_CACHE_FAILURES = 3 DOCS_URL = "https://invoke-ai.github.io/InvokeAI-7/configuration/optimization/sage-attention/" @@ -74,6 +75,8 @@ class _Usage: served: int = 0 fell_back: Counter[str] = field(default_factory=Counter) + # Set once SageAttention's quantized copies did not fit; the rest of the scope then stays on SDPA. + memory_short: bool = False _scope: ContextVar[_Usage | None] = ContextVar("invokeai_sage_attention_scope", default=None) @@ -81,7 +84,7 @@ class _Usage: _disabled_devices: set[int] = set() # (device, dtype, head size): the kernel variants whose first result was compared with SDPA. _validated: set[tuple[int, torch.dtype, int]] = set() -_transient_failures: Counter[int] = Counter() +_cache_failures: Counter[int] = Counter() _announced_devices: set[int] = set() @@ -131,11 +134,13 @@ def _kernel( without saying why; neither has been measured here, so upstream's choice stands. From the Windows build come the sm100 kernel and the choice by the CUDA version torch was built for: the FP16 accumulation inside the FP8 kernels needs CUDA 12.8 to compile, and below that the build replaces it with a trap. Every kernel is called with - ``smooth_k`` off, which ``sageattn`` always leaves on and has no argument to change. Smoothing subtracts K's mean over the sequence before quantizing; in the first - block of Qwen-Image and Krea-2, where one channel of Q and K sits near +600 for every image token, that leaves the - INT8 product Q.K no precision for the rest: cosine 0.18 and 0.53 against a float32 reference, 0.997 and 0.988 with - smoothing off. Everywhere else measured it changes nothing, and off is 2-11 % faster (RTX 4090). Only sm_89 has been - measured in InvokeAI. + ``smooth_k`` off, which ``sageattn`` always leaves on and has no argument to change. + + Smoothing subtracts K's mean over the sequence before quantizing; in the first block of Qwen-Image and Krea-2, + where one channel of Q and K sits near +600 for every image token, that leaves the INT8 product Q.K no precision for + the rest: cosine 0.18 and 0.53 against a float32 reference, 0.997 and 0.988 with smoothing off. Everywhere else + measured it changes nothing, and off is 2-11 % faster (RTX 4090). Only sm_89 has been measured in InvokeAI; the + sm86 Triton kernel was checked for accuracy on it. """ major, minor = capability recent_cuda = cuda >= (12, 8) @@ -171,7 +176,7 @@ def _cuda_index(query: torch.Tensor, key: torch.Tensor, value: torch.Tensor) -> single-device install with `device: cuda:1` does not. """ device = query.device - if device.type != "cuda" or device.index is None or key.device != device or value.device != device: + if device.type != "cuda" or key.device != device or value.device != device: return None return device.index @@ -254,20 +259,11 @@ def _check_first_result( _validated.add((index, query.dtype, query.shape[-1])) -def _transient(error: Exception) -> bool: - """Whether a failure says nothing about the kernel: memory, or a file Triton could not write. - - A CUDA fault that poisons the context (illegal address, a trap) is not among them; torch's fallback then raises the - same error, and a restart is the only way out. - """ - return isinstance(error, OSError) or "out of memory" in str(error).lower() - - -def _transient_failure(index: int) -> bool: - """Count a transient failure on this device; True once there were too many to keep retrying.""" +def _cache_failure(index: int) -> bool: + """Count a Triton cache failure on this device; True once it failed too often in a row to keep retrying.""" with _lock: - _transient_failures[index] += 1 - return _transient_failures[index] >= _MAX_TRANSIENT_FAILURES + _cache_failures[index] += 1 + return _cache_failures[index] >= _MAX_CACHE_FAILURES def _disable(index: int, error: Exception, query: torch.Tensor, key: torch.Tensor) -> None: @@ -374,6 +370,8 @@ def sage_scaled_dot_product_attention( usage = _scope.get() if usage is not None: reason = _ineligible(query, key, value, attn_mask, dropout_p, is_causal, enable_gqa, bool(args or kwargs)) + if reason is None and usage.memory_short: + reason = "memory" if reason is None: index = _cuda_index(query, key, value) kernel = kernels.get(index) if index is not None and index not in _disabled_devices else None @@ -393,14 +391,26 @@ def sage_scaled_dot_product_attention( if (index, query.dtype, query.shape[-1]) not in _validated: _check_first_result(index, original, query, key, value, scale, out) except Exception as e: - # SageAttention's quantized copies did not fit, or Triton could not load or cache a kernel: - # this call uses SDPA, which needs less memory, and the next tries SageAttention again. - if _transient(e) and not _transient_failure(index): - reason = "transient" + if isinstance(e, RuntimeError) and is_oom_error(e): + # SageAttention's quantized copies did not fit. SDPA needs less; the rest of this scope + # uses it rather than flushing the allocator's cache on every call, and the next + # generation tries SageAttention again. A CUDA fault that poisons the context is no OOM: + # it retires the device below, and SDPA then fails the same way. + usage.memory_short = True + reason = "memory" + logger.info( + f"SageAttention ran out of memory on cuda:{index} for query {tuple(query.shape)}; " + "the rest of this generation uses PyTorch SDPA." + ) + elif isinstance(e, OSError) and not _cache_failure(index): + reason = "cache" else: _disable(index, e, query, key) reason = "error" else: + if _cache_failures[index]: + with _lock: + _cache_failures.pop(index, None) usage.served += 1 _announce(index, version, kernel) return out diff --git a/tests/backend/util/test_sage_attention.py b/tests/backend/util/test_sage_attention.py index d670f085d1d..ca1ae56a509 100644 --- a/tests/backend/util/test_sage_attention.py +++ b/tests/backend/util/test_sage_attention.py @@ -120,7 +120,7 @@ def torch_spy( monkeypatch.setattr(sage, "_cuda_index", lambda q, k, v: 0) monkeypatch.setattr(sage, "_disabled_devices", set()) monkeypatch.setattr(sage, "_validated", _all_variants(capabilities)) - monkeypatch.setattr(sage, "_transient_failures", Counter()) + monkeypatch.setattr(sage, "_cache_failures", Counter()) monkeypatch.setattr(sage, "_announced_devices", set()) monkeypatch.setattr(torch.cuda, "device", lambda index: contextlib.nullcontext()) return module, torch_calls @@ -402,16 +402,35 @@ def test_a_kernel_failure_falls_back_warns_once_and_retires_only_that_device(sel [ torch.OutOfMemoryError("CUDA out of memory. Tried to allocate 1.50 GiB"), RuntimeError("Triton Error [CUDA]: out of memory"), - PermissionError("[WinError 5] Access is denied: 'triton\\cache\\tmp.pid_123'"), ], - ids=["torch-oom", "triton-oom", "triton-cache-race"], + ids=["torch-oom", "triton-oom"], ) - def test_a_transient_failure_falls_back_for_that_call_only(self, installed, caplog, error): - """Memory or a file Triton could not write says nothing about the kernel: SDPA serves the call, the next one - tries SageAttention again, and nothing is logged as a failure.""" + def test_running_out_of_memory_moves_the_rest_of_the_scope_to_torch_only(self, installed, caplog, error): + """SDPA needs less memory. Retrying every call would flush the allocator's cache each time; retiring the + device would cost every later, smaller generation SageAttention.""" module, torch_calls = installed module.error = error q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) + caplog.set_level(logging.INFO, logger=sage.__name__) + + with sage_attention_scope(): + out = F.scaled_dot_product_attention(q, k, k) + module.error = None + F.scaled_dot_product_attention(q, k, k) + assert len(module.calls) == 1 and len(torch_calls) == 2, "after the OOM, this scope stayed on torch" + torch.testing.assert_close(out.float(), _reference(q, k, k), atol=2e-3, rtol=2e-3) + + with sage_attention_scope(): + F.scaled_dot_product_attention(q, k, k) + assert len(module.calls) == 2, "the next scope tries SageAttention again" + assert any("ran out of memory" in r.message for r in caplog.records if r.levelno == logging.INFO) + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + + def test_a_triton_cache_failure_falls_back_for_that_call_only(self, installed, caplog): + """On Windows two GPUs compiling the same kernel can race for its cache file; that says nothing about it.""" + module, torch_calls = installed + module.error = PermissionError("[WinError 5] Access is denied: 'triton\\cache\\tmp.pid_123'") + q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) caplog.set_level(logging.WARNING, logger=sage.__name__) with sage_attention_scope(): @@ -421,21 +440,29 @@ def test_a_transient_failure_falls_back_for_that_call_only(self, installed, capl torch.testing.assert_close(out.float(), _reference(q, k, k), atol=2e-3, rtol=2e-3) assert len(module.calls) == 2 and len(torch_calls) == 1 - assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + assert not caplog.records - def test_a_device_that_keeps_failing_transiently_is_retired(self, installed, caplog): - """A Triton cache that can never be written would otherwise recompile on every call.""" + def test_only_cache_failures_in_a_row_retire_the_device(self, installed, caplog): + """A cache that can never be written would recompile on every call; one that failed now and then is fine.""" module, torch_calls = installed - module.error = PermissionError("Access is denied") q, k = _q(1, 2, QUERY_LEN, 64), _q(1, 2, KEY_LEN, 64) caplog.set_level(logging.WARNING, logger=sage.__name__) - with sage_attention_scope(): - for _ in range(sage._MAX_TRANSIENT_FAILURES + 2): + def call(error): + module.error = error + with sage_attention_scope(): F.scaled_dot_product_attention(q, k, k) - assert len(module.calls) == sage._MAX_TRANSIENT_FAILURES - assert len(torch_calls) == sage._MAX_TRANSIENT_FAILURES + 2 + for error in [PermissionError("denied")] * (sage._MAX_CACHE_FAILURES - 1) + [None]: + call(error) + for _ in range(sage._MAX_CACHE_FAILURES - 1): + call(PermissionError("denied")) + assert not caplog.records and 0 not in sage._disabled_devices, "the success in between reset the count" + + call(PermissionError("denied")) + call(None) + assert 0 in sage._disabled_devices + assert len(module.calls) == 2 * sage._MAX_CACHE_FAILURES, "the last call no longer reached the kernel" assert any("cuda:0" in r.message for r in caplog.records if r.levelno == logging.WARNING) def test_the_scope_closes_when_its_block_raises(self, installed): @@ -657,16 +684,23 @@ class TestOnNvidiaHardware: """Real kernels: needs CUDA, `sageattention` >= 2.2 and compute capability 8.0+, and about 10 GB of free VRAM for the float32 references. Measured on an RTX 4090.""" - @pytest.fixture - def kernel(self, monkeypatch): + @pytest.fixture(params=["own", "sm86-triton"]) + def kernel(self, request, monkeypatch): + """The kernel this GPU gets, or the Triton kernel an sm86 card gets: it runs on any Ampere or newer GPU, so this + one stands in for an sm86 card that is not here.""" import sageattention capability = torch.cuda.get_device_capability(0) if capability < (8, 0): pytest.skip("SageAttention 2 needs compute capability 8.0 or newer") + if request.param == "sm86-triton": + pick = sage._kernel + monkeypatch.setattr(sage, "_kernel", lambda module, _capability, cuda: pick(module, (8, 6), cuda)) monkeypatch.setattr(F, "scaled_dot_product_attention", TORCH_SDPA) monkeypatch.setattr(sage, "_disabled_devices", set()) monkeypatch.setattr(sage, "_validated", set()) + monkeypatch.setattr(sage, "_cache_failures", Counter()) + monkeypatch.setattr(sage, "_announced_devices", set()) install_sage_attention() if not getattr(F.scaled_dot_product_attention, sage._SENTINEL, False): pytest.skip("the installed sageattention is not one InvokeAI uses (see the startup warning)") @@ -674,9 +708,10 @@ def kernel(self, monkeypatch): assert cuda is not None return sage._kernel(sageattention, capability, cuda[:2]) - def test_every_kernel_takes_the_arguments_it_is_given(self, kernel): + def test_every_kernel_takes_the_arguments_it_is_given(self, kernel, monkeypatch): import sageattention + monkeypatch.undo() # the real table, not the fixture's stand-in for capability in ((8, 0), (8, 6), (8, 9), (9, 0), (12, 0)): assert sage._unaccepted_arguments(sage._kernel(sageattention, capability, (13, 0))) == [] @@ -686,8 +721,10 @@ def test_every_kernel_takes_the_arguments_it_is_given(self, kernel): ((1, 24, 4608, 128), (1, 24, 4608, 128), torch.bfloat16, False), # FLUX.1 1024^2 ((2, 10, 4096, 64), (2, 10, 4096, 64), torch.float16, False), # SDXL 1024^2, level 1 ((1, 48, 4608, 128), (1, 12, 4608, 128), torch.bfloat16, True), # Krea-2 GQA + # Lengths that are no multiple of a tile, and cross-attention to a shorter key. + ((1, 24, 4429, 128), (1, 24, 845, 128), torch.bfloat16, False), ], - ids=["flux", "sdxl", "krea2-gqa"], + ids=["flux", "sdxl", "krea2-gqa", "partial-tile-cross"], ) def test_output_stays_close_to_a_float32_reference(self, kernel, shape_q, shape_kv, dtype, gqa): g = torch.Generator(device="cuda").manual_seed(0) @@ -699,6 +736,8 @@ def make(b, h, s, d): # real activations arrive as [B, S, H, D] storage viewed with sage_attention_scope(): out = F.scaled_dot_product_attention(q, k, v, enable_gqa=gqa) + # SDPA would pass the bounds below too; the kernel must have served the call. + assert torch.equal(out, kernel(q, k, v, tensor_layout="HND", is_causal=False, sm_scale=shape_q[-1] ** -0.5)) ref = _reference(q, k, v) assert torch.isfinite(out).all() cos = F.cosine_similarity(out.float().flatten(2), ref.flatten(2), dim=-1) @@ -706,20 +745,6 @@ def make(b, h, s, d): # real activations arrive as [B, S, H, D] storage viewed assert cos.min() > 0.998 assert (out.float() - ref).norm() / ref.norm() < 0.06 - def test_the_triton_kernel_chosen_for_sm86_stays_close_to_a_float32_reference(self, kernel): - """No sm86 GPU here; the Triton kernel itself runs on any Ampere or newer GPU, so its numbers are checked.""" - import sageattention - - g = torch.Generator(device="cuda").manual_seed(0) - q, k, v = (torch.randn(1, 4608, 24, 128, device="cuda", generator=g).bfloat16().transpose(1, 2) for _ in "qkv") - triton_kernel = sage._kernel(sageattention, (8, 6), (13, 0)) - - out = triton_kernel(q, k, v, tensor_layout="HND", is_causal=False, sm_scale=128**-0.5) - - ref = _reference(q, k, v) - assert torch.isfinite(out).all() - assert (out.float() - ref).norm() / ref.norm() < 0.06 - def test_the_outlier_channel_of_qwen_images_first_block_stays_accurate(self, kernel): """Real activations (see the fixture's metadata). With K smoothing on, as `sageattn` runs it, the relative error here is 1.62; with it off, 0.10."""