Skip to content

feat(controlnet): add FLUX.2 [dev] Fun-ControlNet-Union with canvas control layers - #9924

Open
Pfannkuchensack wants to merge 11 commits into
invoke-ai:mainfrom
Pfannkuchensack:feat/flux2-fun-controlnet
Open

Pfannkuchensack wants to merge 11 commits into
invoke-ai:mainfrom
Pfannkuchensack:feat/flux2-fun-controlnet

Conversation

@Pfannkuchensack

@Pfannkuchensack Pfannkuchensack commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Summary

FLUX.2 [dev] had no ControlNet in Invoke. alibaba-pai's FLUX.2-dev Fun-ControlNet-Union (Canny, Depth, Pose, HED, MLSD, Scribble, Gray, plus inpainting) uses the same VACE-style branch as the Qwen-Image union from #9914, so this PR adds it on the same hook helper.

  • FLUX.2 [dev] ControlNet control layers on the Canvas (adapter type "FLUX.2 [dev] ControlNet", weight 0.75 by default, one per generation) and a FLUX.2 [dev] ControlNet node for the workflow editor. In Canvas inpaint and outpaint the layer also receives the composite and the mask, as the model was trained for.
  • The four control blocks run as copies of FLUX.2's double blocks with their own text stream; their hints are added to the image stream of double blocks 0, 2, 4 and 6. FLUX.2 reference images keep working (their tokens get zero control context, as in VideoX-Fun).
  • The control context follows VideoX-Fun: VAE encode, 2×2 patchify, BatchNorm-normalised with the VAE's statistics, mask channels at latent resolution; absent parts stay zero.
  • Klein models are refused with a clear message in the UI and in the node. The installer recognises the union file as a FLUX.2 ControlNet; the starter model is the CFG-distilled …-2602 build (FLUX [dev] Non-Commercial License).
  • The control image and inpaint encodes inside Denoise - FLUX.2 now get the same out-of-memory → tiled retry as Image to Latents - FLUX.2.

Related Issues / Discussions

Builds on #9914 (shared Fun-ControlNet helper); merge after it.

QA Instructions

RTX 4090, FLUX.2 dev fp8mixed (33.8 GB, partially loaded) + Mistral fp8 encoder, FLUX.2-dev-Fun-Controlnet-Union-2602, 28 steps, guidance 4, scale 0.75, ~1 MP:

Run Result
Pose (VideoX-Fun example skeleton and prompt), seeds 43–47 every seed takes the skeleton's pose (hand on hip, other arm down); without control the pose varies
Canny (leopard), Depth (living room) follow the edges / depth layout
Scale 0 vs no control, same seed bit-identical
Interleaved A/B, 3 seeds each 5.2 s/step with control vs 4.1 s/step without; peak VRAM 22.1 GB
Canvas: gallery image → Resized Control Layer → Invoke layer defaults to FLUX.2 [dev] ControlNet with the union model, graph wires flux2_control → denoise_latents.control, output follows the skeleton

Numerical checks on CPU: the hook path equals a restatement of VideoX-Fun's stacked formulation (torch.equal, randomly initialised tiny model, with reference tokens); the context builder equals VideoX-Fun's context code; the node's context wiring is checked against independently computed control-only and inpaint-only contexts; per-step gating covers Euler, Heun (two forwards per step) and the manual-Euler path.

The 8.2 GB branch runs mostly offloaded next to the partially loaded transformer on a 24 GB card. Two runs right after a server start hit the driver's system-memory fallback (VRAM full, 12–18 s/step); this did not recur in the interleaved A/B. A run with a FLUX.2 reference image in the graph reserves more working memory and ran at ~9 s/step.

Checks: focused pytest (fun_controlnet, FLUX.2 control/denoise/VAE, configs, loaders, fp8 capability, frontend graph contracts, model identification): passed; pnpm lint; webv2 tests for control validation, adapters, canvas migration, layer checks, canvas compile and ControlLayerSettings browser test: passed (2 unrelated failures under a German locale). Ruff clean.

Review

Resolved: the node's control-context wiring is now covered by value-level tests (swapped images, inverted mask or missing normalisation each fail); the control encodes gained the tiled retry the docs promised; the docs name the 2602 build and why; the Klein error is readable. Remaining: the +4 activation term reuses Z-Image's coefficient and is not measured for FLUX.2; the model cache logs a "VRAM budget short" warning for the branch on every partially loaded [dev] run (expected, also seen for Qwen/Z-Image); Klein still lists the control kind because capabilities are per base, and the layer then shows the refusal.

Compatibility / Rollout

flux2_denoise 1.6.0 → 1.7.0 with an optional control input; new flux2_control node; new config tag controlnet.checkpoint.flux2; new control kind flux2_control in capabilities (fixture regenerated). The shared context builder is renamed build_fun_control_context (not yet on main). openapi.json/schema.ts regenerated. Existing workflows and canvases load unchanged; no migration.

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.
@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 18, 2026
@Pfannkuchensack

Copy link
Copy Markdown
Member Author
flux2_control_sheet

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

7.0.0 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

Status: 7.0 Theme: Modular Design

Development

Successfully merging this pull request may close these issues.

3 participants