Skip to content

fix(z-image): run Fun ControlNets through the shared hooks, as trained - #9943

Open
Pfannkuchensack wants to merge 12 commits into
invoke-ai:mainfrom
Pfannkuchensack:feat/z-image-fun-control-hooks
Open

Pfannkuchensack wants to merge 12 commits into
invoke-ai:mainfrom
Pfannkuchensack:feat/z-image-fun-control-hooks

Conversation

@Pfannkuchensack

@Pfannkuchensack Pfannkuchensack commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Summary

Z-Image's Fun ControlNets ran through Invoke's own copy of the transformer forward, which did not match how alibaba-pai trained them: the hints sat after the wrong layers for the Lite and first Union builds, the 2.x refiner hints were missing (their weights were dropped as unexpected keys), 2.0 refined its control through the wrong blocks, the missing x_pad_token got a random value, and regional prompting was lost on control steps. In practice Lite and 2.0 produced noisy images and the first Union ignored the control image.

This PR runs the Z-Image ControlNets through the same hook helper as the Qwen-Image (#9914) and FLUX.2 (#9924) unions, on the diffusers forward:

  • The helper gains stages: a ControlNet can hook into more than one part of the forward and hand its control stream on (Z-Image: the image refiner, then the main layers). Qwen-Image and FLUX.2 are unchanged.
  • Each published build is recognised from its file: Union 1.0, 2.0, 2.1 (incl. Tile) and 2.1 Lite, each with VideoX-Fun's layer map and refiner behaviour (2.0 is told apart by its untrained, all-zero refiner projections).
  • Control latents are encoded deterministically and the context matches VideoX-Fun ([control | 0 | 0]); pad rows come from the base model's x_pad_token.
  • Regional prompting now also applies on control steps; SageAttention also covers the control blocks.
  • The custom control forward, adapter and patchify helpers are removed.

The node, its fields and the UI are unchanged; the control is now stronger at the same weight for the builds that were wrong before.

Related Issues / Discussions

Builds on #9924 (and #9914). GGUF Z-Image bases need #9942 (every GGUF Z-Image generation with 2-D pad tokens failed before that fix, with or without control).

QA Instructions

RTX 4090, Z-Image Turbo int8 + Qwen3-4B + FLUX VAE, 8 steps, scale 0.9 (Tile 0.85), same seeds, old code vs this PR on the same server root:

Build / case Before After
Union 2.1, pose and canny, 3 seeds follows control follows control (refiner hints now active)
Union 2.1 Lite, pose noisy, texture artefacts clean, follows the pose
Union 2.0, pose noisy clean, follows the pose
Union 1.0, pose ignores the pose follows the pose
Tile 2.1 (blurred photo) different face keeps the source identity
No ControlNet — bit-identical to before
Regional prompts + control — regions apply with control
Heun, CFG 2 + negative, GGUF base (with #9942) — run

Working memory, measured on the hooked forward at 1024 px (hint-sized tensors above no control vs the n + 4 charge): 1.0 1.78 / 10, Lite 0.89 / 7, 2.0 11.61 / 19, 2.1 11.56 / 19 — the coefficient stays, the measured points in the tests are updated.

CPU: per-build parity against an independent restatement of VideoX-Fun's forward on a tiny randomly initialised ZImageTransformer2DModel (torch.equal; layer maps restated from the VideoX configs; wrong maps, dropped refiner hints, wrong pad rows and a missing caption join each fail it); lifetime tests that the control stream is released before the last control block; loader tests on the real headers (1.0, 2.1, Lite). Focused pytest over the Fun-ControlNet, Z-Image, Qwen-Image and FLUX.2 control/denoise/working-memory/loader tests: passed. Ruff clean.

Review

Resolved: the shared chain and the stage hand-over kept control tensors alive longer than needed (now owned and released, with lifetime tests; this also trims one tensor per forward for Qwen-Image and FLUX.2); the parity tests read the layer maps from the implementation (now restated independently); the working-memory points described the removed forward (re-measured); docs no longer claim inpainting through a Z-Image ControlNet. Remaining: regional prompting with several regions is not bit-reproducible run to run (the collected region order varies; unrelated to this change); inpaint inputs on the Z-Image control node are not wired (future work).

Compatibility / Rollout

No schema or persisted-data changes; existing workflows and canvases run unchanged but produce different (corrected) images for the affected builds. The calibration script's control option is now --control-variant.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Meaningful regression coverage added / updated where needed; obsolete tests/code removed
  • Persisted-state and API changes include required migrations / compatibility validation
  • Relevant performance/efficiency opportunities considered; material claims have evidence
  • Material review findings resolved and relevant checks rerun
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

…trol layers

Add alibaba-pai's VACE-style control branch for Qwen-Image: identification, loader, the qwen_image_control node and control/inpaint conditioning in the denoise, injected through forward hooks on the diffusers transformer. The canvas gets a Qwen Image ControlNet layer kind with inpaint/outpaint feeding and a grayscale filter; the denoise now reserves working memory for its activations.
…ontrol layers

Loads alibaba-pai's FLUX.2-dev union as a FLUX.2 ControlNet, runs its control blocks on the shared hook helper and adds a Canvas control kind with inpaint wiring.
The control encodes inside Denoise - FLUX.2 retry tiled after running out of memory, like Image to Latents - FLUX.2.
…trings

The Qwen-Image and FLUX.2 [dev] ControlNet strings add 0.5 KB to the locale file every route loads; request counts are unchanged.
The control blocks now hook into the diffusers forward, with each build's own layer map and the 2.x refiner hints, so regional prompting also works on control steps.
Lite, 2.0 and the first Union now follow the control image instead of producing noise or ignoring it; the old custom control forward is removed.
@github-actions github-actions Bot added python PRs that change python files invocations PRs that change invocations backend PRs that change backend files frontend PRs that change frontend files python-tests PRs that change python tests docs PRs that change docs labels Oct 19, 2026
@Pfannkuchensack

Copy link
Copy Markdown
Member Author
z_ab_other z_ab_pose

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

backend PRs that change backend files docs PRs that change docs frontend PRs that change frontend files invocations PRs that change invocations python PRs that change python files python-tests PRs that change python tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants