From f69c6bbd0eba9059f29bb3ea13dad2692f9d85f4 Mon Sep 17 00:00:00 2001 From: Don Kackman Date: Sun, 11 Oct 2026 18:20:54 -0500 Subject: [PATCH 1/3] test(disk-caches): the traversal route test ignores the job history's SQLite sidecars b02cf898 checked the parent for losses, but jobs.sqlite-shm and -wal are removed when the history connection closes, so CI still saw a loss. The parent comparison now leaves out the server's own workspace files (the SQLite sidecars and outputs/), which the route never touches. Co-Authored-By: Claude Fable 5.1 --- tests/test_disk_caches.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/test_disk_caches.py b/tests/test_disk_caches.py index a7028917..77a8c9e4 100644 --- a/tests/test_disk_caches.py +++ b/tests/test_disk_caches.py @@ -344,6 +344,18 @@ def tree(root): return sorted(str(p.relative_to(root)) for p in root.rglob("*")) +def parent_tree(tmp_path): + """What is under tmp_path, minus the server's own workspace files: the + job history's SQLite -shm/-wal sidecars come and go with its connection, + and outputs/ is created on the server's schedule, so a traversal test + checks the parent for losses among the rest, not for equality.""" + return { + p + for p in tree(tmp_path) + if not p.startswith("jobs.sqlite") and not p.startswith("outputs") + } + + class TestCacheRoutes: def test_list_reports_both_caches(self, api, tmp_path): root, (total, count) = marked_root(tmp_path) @@ -436,17 +448,14 @@ def test_traversal_name_touches_nothing(self, api, tmp_path, status): victim.parent.mkdir() victim.write_bytes(b"x" * 8) before_root = tree(root) - before_parent = tree(tmp_path) + before_parent = parent_tree(tmp_path) response = api.delete("/api/caches/..%2Fx") assert response.status_code == status assert tree(root) == before_root assert victim.read_bytes() == b"x" * 8 - # The server's own workspace lives under tmp_path too (outputs, the - # job history's -shm/-wal), and it adds files there on its own - # schedule, so the parent is checked for losses, not for equality - assert set(before_parent) <= set(tree(tmp_path)) + assert before_parent <= parent_tree(tmp_path) # ------------------------------------------------------- quantized entries From a9a1298ca69a6d4b28c604be75c47dbb767add35 Mon Sep 17 00:00:00 2001 From: Don Kackman Date: Sun, 11 Oct 2026 18:47:18 -0500 Subject: [PATCH 2/3] fix(attention): a component's attention_backend pin no longer becomes the worker's default diffusers' set_attention_backend writes the backend onto the component's processors and then makes it the process-wide active backend. The persistent worker outlives the job, so an H3 transformer pinned to sage_hub routed every later model through sage - Music3's head_dim-256 audio decoder failed with "Unsupported head_dim: 256", and LTX's attention mask is refused the same way. The pin now restores the active backend after setting it; the pinned processors pass their own backend. .env.example and ACCELERATION.md say to leave DIFFUSERS_ATTN_BACKEND native and pin sage per workflow. Co-Authored-By: Claude Opus 5.5 --- .env.example | 5 ++++- docs/ACCELERATION.md | 2 ++ dw/pipeline_processors/components.py | 22 +++++++++++++++++++++- tests/test_modular_pipeline.py | 19 +++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index c249ae7a..b249d9b9 100644 --- a/.env.example +++ b/.env.example @@ -102,7 +102,10 @@ # The attention backend for every model that sets none (default native). A # workflow's per-component "attention_backend" overrides it. Same values as -# that setting: native, flash_hub, sage_hub, _native_cudnn, ... +# that setting: native, flash_hub, sage_hub, _native_cudnn, ... Leave it +# native: it reaches every model on the server, and sage cannot serve all of +# them (Music3's head_dim-256 decoder, LTX's attention mask). Pin sage_hub per +# workflow instead - the MiniMax-H3 templates take attention_backend=sage_hub. # DIFFUSERS_ATTN_BACKEND=native # Refuse any pipeline or component that would load custom code from the Hub. diff --git a/docs/ACCELERATION.md b/docs/ACCELERATION.md index d6c12192..1d64c59c 100644 --- a/docs/ACCELERATION.md +++ b/docs/ACCELERATION.md @@ -160,6 +160,8 @@ A component can also pin its backend persistently instead, via `set_attention_ba Prefer the pinned form for a compiled component - the per-call context manager switches implementations under the compiled graph and forces a recompile on every run. +Either form stays on what it names. diffusers' `set_attention_backend` also makes its backend the process-wide default, which the persistent worker would keep for every later model; dw restores the default after pinning, so an H3 `sage_hub` pin cannot reach a Music3 or LTX job that follows it. The server-wide default is `DIFFUSERS_ATTN_BACKEND` in `.env` - leave it `native`, since it reaches every model and sage cannot serve all of them. + ## Attention Slicing ```json diff --git a/dw/pipeline_processors/components.py b/dw/pipeline_processors/components.py index a0aeb238..b0a82a91 100644 --- a/dw/pipeline_processors/components.py +++ b/dw/pipeline_processors/components.py @@ -129,7 +129,7 @@ def configure_components(pipeline, configuration, default_device, reused_compone logger.info( f"Setting {component_name} attention backend: {component_attention_backend}" ) - component.set_attention_backend(component_attention_backend) + set_component_attention_backend(component, component_attention_backend) # Compile last - the graph must capture final dtypes, adapters, # quantization, and offload hooks @@ -143,6 +143,26 @@ def configure_components(pipeline, configuration, default_device, reused_compone ) +def set_component_attention_backend(component, backend): + """Pin one component's attention backend without changing anyone else's. + + diffusers' set_attention_backend writes the backend onto the component's own + processors, then also makes it the process-wide active backend - the default + for every model that pins none. The persistent worker outlives the job, so an + H3 transformer pinned to sage_hub would route every later model through sage, + and models sage cannot serve (Music3's head_dim-256 audio decoder, LTX's + attention mask) crash. Restoring the active backend keeps the pin on the + component it was asked for; its processors pass their own backend explicitly. + """ + from diffusers.models.attention_dispatch import _AttentionBackendRegistry + + active_backend = _AttentionBackendRegistry._active_backend + try: + component.set_attention_backend(backend) + finally: + _AttentionBackendRegistry.set_active_backend(active_backend) + + def enable_tiling(component, component_name, component_configuration): """Turn on tiled decoding for a component whose configuration asks for it. diff --git a/tests/test_modular_pipeline.py b/tests/test_modular_pipeline.py index 8d02e301..14de67bf 100644 --- a/tests/test_modular_pipeline.py +++ b/tests/test_modular_pipeline.py @@ -679,6 +679,25 @@ def test_attention_backend_is_pinned_on_the_component(self): pipeline.transformer.set_attention_backend.assert_called_once_with("flash_hub") + def test_attention_backend_pin_leaves_the_process_default_alone(self): + # diffusers' set_attention_backend also sets the process-wide active + # backend, which the persistent worker keeps for every later model - + # an H3 sage_hub pin crashed Music3's head_dim-256 decoder that way + from diffusers.models.attention_dispatch import _AttentionBackendRegistry + + def leaky_pin(backend): + _AttentionBackendRegistry.set_active_backend(backend) + + pipeline = MagicMock() + pipeline.transformer.set_attention_backend.side_effect = leaky_pin + before = _AttentionBackendRegistry._active_backend + try: + self.configure({"transformer": {"attention_backend": "sage_hub"}}, pipeline) + + assert _AttentionBackendRegistry._active_backend == before + finally: + _AttentionBackendRegistry.set_active_backend(before) + def test_component_device_override_governs_the_mps_skip(self, caplog): # The step may pin this component to another device than the pipeline's pipeline = MagicMock() From eccf993136922f434e6d318b5c3ae779895d9a9a Mon Sep 17 00:00:00 2001 From: Don Kackman Date: Sun, 11 Oct 2026 19:01:16 -0500 Subject: [PATCH 3/3] docs(releasing): the 0.12.0 notes name the attention_backend pin fix Co-Authored-By: Claude Opus 5.5 --- docs/RELEASING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 80877c3c..0135a33b 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -68,6 +68,12 @@ hallucinations as lyrics. so `triton` stays clearable after the first run (#837). - `deploy.sh` restarts a `dw.serve` run as a loaded launchd daemon on macOS. +- A component's `attention_backend` pin stays on that component. diffusers + also made it the process-wide default, so after an H3 run with + `attention_backend=sage_hub` the worker sent every later model through + sage, and Music3 failed with `Unsupported head_dim: 256` (LTX's attention + mask is refused the same way). Music3 and LTX templates need no pin of + their own. ### 0.11.1