Skip to content

fix(h3): don't refuse a render when SageAttention could not be inspected - #75

Open
tmttodd wants to merge 1 commit into
BlackMixture:mainfrom
tmttodd:fix/remote-sage-attention-probe
Open

tmttodd wants to merge 1 commit into
BlackMixture:mainfrom
tmttodd:fix/remote-sage-attention-probe

Conversation

@tmttodd

@tmttodd tmttodd commented Sep 24, 2026 •

Copy link
Copy Markdown

What this fixes

Fixes #74.

MiniMax H3 asks for SageAttention by default, and the route gate refused with 409 h3_sage_attention_unavailable whenever probeSageAttention() reported ready !== true. That probe inspects a ComfyUI Python environment on this machine's filesystem — but Mix Studio supports talking to a ComfyUI that runs in a container or on another host. There the check can never succeed, so every H3 render submitted with SageAttention is refused, including requests that never mention it.

Root cause in one sentence: the gate treated "could not be inspected" as "inspected and unusable".

Reproduction on a live containerised engine

A controlled comparison: same engine, same tunnel, same scratch recipe, same prompt-free requests — with upstream main (9bd99ad, this branch's own parent) on one side and this branch on the other. The fix's delta is the only variable.

POST /api/animate (no prompt) main @ 9bd99ad this branch
{"engine":"h3","sageAttention":true} 409, code=h3_sage_attention_unavailable (11.2 ms) 400 {"error":"Describe the motion first"} (17.9 ms)
{"engine":"h3"} — no attention field at all, the default path 409 (9.6 ms) 400
{"engine":"h3","sageAttention":false} 400, byte-identical body 400, byte-identical body

Row 3 is the discriminator: with SageAttention off the request clears the gate and lands on ordinary prompt validation, so the 409 is the SageAttention gate rather than the missing prompt. The same pair measured against the released v1.2.4 gives the same 409 → 400, and there the 409 body's own sageAttention.nodeReady is true — the server asserting that the patch node is present while refusing. On a fixed tree a prompt-bearing SageAttention request was deliberately not sent, since it would clear the gate and submit a real render.

GET /api/meta reports this, verbatim, on this branch:

{"ready":false,"installable":false,"pythonReady":false,"verifiable":false,
 "reason":"Mix Studio has no local ComfyUI Python environment to inspect, so SageAttention cannot be verified on this machine. This is expected when ComfyUI runs on another host.",
 "packageReady":false,"nodeReady":true}

On the baseline side — upstream main at 9bd99ad — the same object reads "reason":"Choose the local ComfyUI folder so Mix Studio can verify its Python environment." and carries no verifiable field at all, with the corroborating fields naming the blocked half ("missing": {"h3sage": []}, "blockedBy":"sage-runtime") — the node half passing while the runtime half can never be checked. ?refresh=1 returns the identical object, so it is not a cached verdict.

Nothing was queued on either side: the engine's /queue and /history were unchanged across every request (0 running / 0 pending; history 12 → 12 in both trees), which the sub-20 ms latencies already imply.

The change

lib/sage-attention.js — the probe now separates "never measured" from "measured and unusable":

  • the no-interpreter early return reports verifiable: false, and no longer tells the user to choose a local ComfyUI folder, which cannot help when the engine is remote;
  • publicProbe and the catch branch (interpreter present, probe ran, report unavailable) report verifiable: true;
  • new export h3SageAttentionDecision(sageRuntime, nodeReady), evaluated nodeReady → verifiable === false → ready === true, and defaulting to block for any absent or unknown shape.

server.js — the gate consults the helper (:9706-9741). On an unverifiable report it rewrites the derived attention options as a pair through the same factory that built them (:9738) and re-reads the boolean from them (:9740), so the backend name and the boolean cannot disagree into the job record at :10234-10236. h3Attention / h3SageAttention become assignable at :9505 / :9511; h3SlaAttention stays const and its gate at :9742-9758 is untouched.

What still refuses, unchanged

situation result
connected ComfyUI lacks the KJNodes patch node 409 — same message, code, component
probe ran and found SageAttention unusable 409 — same message, code, component
client sent sageAttention: false proceeds, standard
probe verified SageAttention on this machine proceeds, sageattention
attentionBackend: "sla" selected unaffected — the sage leg never runs for it (probe-call counts: sage 0, sla 1) and SLA's own gate still applies

The decision matrix was exercised end to end — the five topologies above plus the legacy "sage" alias leg — against the real probe and the real report shape. A control run using main's own lib/ revision still returns the original 409 and the original message string, so the harness discriminates rather than agreeing with itself.

Known limitation: this does not unblock the default H3 UI

h3sage remains in /api/meta's missingComponents (that list keys off capabilities.sageAttention?.ready !== true at server.js:1894, which stays false), so #generateBtn still diverts into the setup dialog and never posts. The 409 was reachable by direct API callers — which is how we hit it — and the fix is aimed there. Making the UI path work means deciding how a remote engine should report verifiability, which is a larger question than this gate, so the badge semantics are deliberately untouched.

Tests

node --test on the pinned v1.2.4 checkout: 1134 → 1144. Rebased on current main: 1327 → 1337, zero failures — delta exactly +10, with the new tests demonstrably collected by the default invocation (test/sage-attention-h3-gate.test.js, plus 3 in test/sage-attention.test.js). Verified under Node 22.23.2 (the quality.yml matrix version) as well as Node 26, with identical counts on both. The OS legs are yours to run — the Quality workflow on this PR is queued as action_required, pending approval for a fork.

CONTRIBUTING's three commands (node --check server.js, node --check public/app.js, node --test) all pass; no dependency is added; no real data/ directory is touched (every run used a scratch data directory); and the decision logic lives in the focused lib/ module with its own tests, as the file prefers. A CHANGELOG entry is included under Unreleased.

Out of scope, reported rather than fixed

lib/h3-sla-attention.js:155-162 has the same shape — the un-actionable folder message on its no-local-interpreter path — and a remote engine that has the pinned SLA node would surface it, since server.js:9745 takes the !nodeReady branch first and the node-absent case never reaches it. (Our own live SLA 409 is node-justified and does not demonstrate this; the correction in #74 records what it does and does not show.) Porting this fix there needs its own decision — degrade, or refuse with the platform reason that actually applies — so it is deliberately not in this PR.

MiniMax H3 defaults SageAttention on (server.js:8110), and the route gate refused
with 409 h3_sage_attention_unavailable whenever probeSageAttention() reported
ready !== true. That probe inspects a ComfyUI Python environment on *this*
machine's filesystem. When ComfyUI runs in a container or on another host — a
supported configuration, since the app talks to it over a URL — there is no local
interpreter to inspect, so the gate refused every h3 render, including requests
that never asked for SageAttention.

The probe now distinguishes "never measured" from "measured and unusable": it
reports verifiable: false on the no-interpreter path, and verifiable: true in
publicProbe and in the catch branch where it had an interpreter and ran. A new
h3SageAttentionDecision() reads that distinction. An unverifiable report degrades
the render to standard attention instead of refusing it — ready stays false, so
the job record is honest about which backend ran — while a missing KJNodes patch
node and a probe that ran and found SageAttention broken still return exactly the
same 409 as before. Unknown or absent report shapes fail safe and block.

Verified: node --test goes 1134 -> 1144 (10 tests added, none removed), 0
failures, and the new tests are collected by the default invocation. The decision
matrix was exercised with the real probe and the real report shape across five
topologies, with a pre-fix negative control that still returns the original 409.

Refs: BlackMixture#74
@tmttodd
tmttodd force-pushed the fix/remote-sage-attention-probe branch from 8c33cb3 to 188027a Compare September 24, 2026 00:37

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

H3 SageAttention gate 409s every render when ComfyUI runs in a separate container

1 participant