fix(deploy): stop the attention backend report claiming restrictions that do not apply - #11
Conversation
…t apply
_log_attention_backends annotated the Wan shared-core line with
"(CUDA fp16/bf16 only; torch_sdpa otherwise)" for every value except "torch".
ATTENTION_IMPLEMENTATION is not a closed set: initialize_attention_priority()
lowercases DIFFSYNTH_ATTENTION_IMPLEMENTATION but does not otherwise check it,
unlike WAM_ATTENTION_IMPL, which is validated against _BACKEND_MAP. Since
attention_forward runs any value it does not recognize through torch_sdpa, a
plain-SDPA run was reported as half-precision-CUDA-only:
DIFFSYNTH_ATTENTION_IMPLEMENTATION=sdpa
Wan shared core : sdpa (CUDA fp16/bf16 only; torch_sdpa otherwise)
The ActionDiT line had the opposite problem: no qualifier at all, though its
closures have always fallen back to SDPA off CUDA, and since OpenWAM-Official#9 fall back on
the same device-and-dtype condition as the Wan paths.
Replace both ad-hoc conditionals with a single _qualify_backend() whitelist and
apply it to all three lines, so an unlisted backend is reported as-is.
wayrise
left a comment
There was a problem hiding this comment.
Both problems are real and I reproduced them exactly as described.
On main, with the env var set to a value attention_forward does not recognize:
$ DIFFSYNTH_ATTENTION_IMPLEMENTATION=sdpa
Wan shared core : sdpa (CUDA fp16/bf16 only; torch_sdpa otherwise)
$ DIFFSYNTH_ATTENTION_IMPLEMENTATION=bogus_backend
Wan shared core : bogus_backend (CUDA fp16/bf16 only; torch_sdpa otherwise)
$ DIFFSYNTH_ATTENTION_IMPLEMENTATION=
Wan shared core : (CUDA fp16/bf16 only; torch_sdpa otherwise)
and with flash-attn installed and no override, the ActionDiT line was indeed the odd one out:
ActionDiT : _flash2
Video DiT : flash_attention_2 (CUDA fp16/bf16 only; torch_sdpa otherwise)
Wan shared core : flash_attention_2 (CUDA fp16/bf16 only; torch_sdpa otherwise)
All four cases read correctly on this branch. The WAM_ATTENTION_IMPL / DIFFSYNTH_ATTENTION_IMPLEMENTATION asymmetry you point to is real too — the former raises ValueError against _BACKEND_MAP, the latter takes any string.
| Claim | Result |
|---|---|
| 15/15 on this branch | Confirmed |
3/3 fail against unpatched server.py |
Confirmed |
| Full suite | Confirmed — 1971 passed, 20 skipped |
ruff check / ruff format |
Clean |
Collapsing three ad-hoc conditionals into one whitelist is the right shape, and test_action_dit_backend_names_match_components is a good catch — pinning the naming convention rather than the closure objects is the only thing that works when CI has none of the libraries installed.
One nit, non-blocking
The whitelist merges two namespaces into one flat set, so a name from one can match an entry meant for the other. Since ATTENTION_IMPLEMENTATION is an open set — which is the premise of this PR — the ActionDiT closure names are reachable through it:
$ DIFFSYNTH_ATTENTION_IMPLEMENTATION=_flash2
Wan shared core : _flash2 (CUDA fp16/bf16 only; torch_sdpa otherwise)
>>> resolve_implementation(q) # '_flash2'
>>> attention_forward(q, q, q) # runs fine on cpu/fp32 — it is plain SDPA
That is the same defect this PR fixes, just entering through the other namespace's names. Nobody sets the env var to an ActionDiT internal, so it is not worth blocking on — but if you want it airtight, giving _qualify_backend the namespace (two small sets consulted separately, or a namespace argument) closes it without changing anything else.
Related, and purely cosmetic: DIFFSYNTH_ATTENTION_IMPLEMENTATION= still logs Wan shared core : with a blank name. Strictly an improvement over annotating that blank with a restriction, and arguably the honest "report as-is" answer, but something like torch_sdpa (unrecognized: "") would read better in a log. Entirely your call.
Neither of these needs to hold up the merge. Approving.
_qualify_backend consulted one flat table holding both the Wan spellings
(ATTENTION_IMPLEMENTATION) and the ActionDiT closure names (_BACKEND_MAP keys).
Since ATTENTION_IMPLEMENTATION is an open set, an ActionDiT name reaches the Wan
path through the env var and was annotated as though it were a fused backend,
while attention_forward ran it as plain SDPA:
DIFFSYNTH_ATTENTION_IMPLEMENTATION=_flash2
Wan shared core : _flash2 (CUDA fp16/bf16 only; torch_sdpa otherwise)
That is the same defect the previous commit fixes, entering through the other
namespace's names.
Split the table in two and pass the calling subsystem's own namespace, so a name
is annotated only where it means something. Values are the restriction note
itself, which lets the drift tests assert the note rather than mere membership.
Also adds the Wan-side counterpart of the ActionDiT drift pin: fused_backend_name()
and initialize_attention_priority() are the two sources of the Wan spellings, and
neither was pinned, so a backend added to either would silently lose its qualifier.
|
Thanks for the thorough reproduction, and the nit is a good catch — I've taken it rather than leaving it, because it turned out to be the same defect class the PR is about and the fix also closed a gap I'd found on my own side. Pushed as Namespaces split. The table values are now the restriction note itself rather than set membership, which also lets the drift tests assert which restriction a backend carries — a backend classified into the wrong one used to pass. And the counterpart pin you didn't ask for. Writing the split made obvious something I should have caught earlier: Both new tests are mutation-tested: flattening the two tables back into one fails On the blank-name cosmetic — I left it as-is. Verified on Python 3.10.20, torch 2.7.1+cu128, Sorry to move it under an approval you'd already given — re-review whenever suits, or say the word and I'll drop the second commit if you'd rather land the first as approved. |
wayrise
left a comment
There was a problem hiding this comment.
Re-reviewed c5b6b81. No need to apologise for the dismissed approval — taking the nit was the right call over leaving it.
The namespace split does what it says. My case, on this head:
$ DIFFSYNTH_ATTENTION_IMPLEMENTATION=_flash2
Wan shared core : _flash2
$ DIFFSYNTH_ATTENTION_IMPLEMENTATION=_xformers
Wan shared core : _xformers
Both plain now, and the ordinary path is unchanged — with flash-attn installed and no override, all three lines still carry their qualifier.
Making the table values the restriction note rather than set membership is a real improvement on its own: classifying a backend into the wrong bucket used to pass, and now it cannot.
I re-ran your two mutations rather than take them on trust, and both hold:
| mutation | result |
|---|---|
drop sage_attention from _WAN_BACKENDS |
1 failed |
| flatten the two tables back into one | 2 failed |
| unmutated control | 17 passed |
| check | result |
|---|---|
tests/test_attention_backend_dispatch.py |
17 passed |
| full suite | 1973 passed, 20 skipped |
ruff check / ruff format --check |
clean |
On the blank name — agreed, leave it. A function whose job is to stop the report claiming things is the wrong place to invent a name for a value it was handed, and "" is the honest answer to "what is set". If it ever bothers anyone in a log it can be dealt with where the env var is read, which is the layer that actually knows the value was junk.
Approving.
|
Thanks for the re-review, and for re-running the mutations rather than taking them on trust. Closing the one thing I left open: I said I'd add your The blank name is a symptom of
Nothing further from me on this PR — it's ready whenever you are. |
Follow-up to #9. Two small things in
_log_attention_backendsthat the new guards left slightly out of step — including the one @KraHsu flagged in review as worth a follow-up.1. The shared-core line can claim a restriction that does not apply
ATTENTION_IMPLEMENTATIONis not a closed set —initialize_attention_priority()lowercasesDIFFSYNTH_ATTENTION_IMPLEMENTATIONbut does not otherwise check it:which is the opposite of how the sibling variable is treated —
WAM_ATTENTION_IMPLis validated against_BACKEND_MAPand raisesValueErroron an unknown value.attention_forward'selsebranch correctly runs any value it does not recognize throughtorch_sdpa, but the diagnostics then annotate that SDPA run as half-precision-CUDA-only:That run is plain SDPA on every device and dtype. The empty string is reachable the same way — the lookup guards on
is not None, not truthiness — and printed the qualifier attached to no backend name at all. Since #9's purpose was to stop the report claiming a backend the run is not using, this seemed worth closing out.2. The ActionDiT line carries no qualifier
_flash3/_flash2/_sagehave always fallen back to SDPA off CUDA, and since #9 they fall back on exactly the same device-and-dtype condition as the Wan paths;_xformersis CUDA-only. The other two lines say so and this one does not, which reads as if the ActionDiT were the odd one out.What changed
Both ad-hoc conditionals are replaced by one
_qualify_backend()whitelist, applied to all three lines. Anything unlisted is reported as-is, which covers plain SDPA and any unrecognized env value.The normal case is unchanged: with flash-attn installed and no env override, all three lines read
flash_attention_2 (CUDA fp16/bf16 only; torch_sdpa otherwise)/_flash2 (…)as before.Because the whitelist has to name the ActionDiT closures (
_flash2, …) alongside theATTENTION_IMPLEMENTATIONspellings, there is a test pinning those names againstcomponents._BACKEND_MAP, so a backend added there later cannot silently drift out of the report.Tests
Three cases added to
tests/test_attention_backend_dispatch.py. Against unpatchedserver.pythey fail 3/3, and the file is 15/15 on this branch.Verified on Python 3.10.20, torch 2.7.1+cu128,
flash_attn2.8.3.post1, 4×H100 80GB: