Skip to content

fix(flux2): remove inert guidance UI, add Klein 4B Base variant, fix metadata recall#8995

Open
Pfannkuchensack wants to merge 26 commits intoinvoke-ai:mainfrom
Pfannkuchensack:fix/flux2-klein-guidance-and-metadata
Open

fix(flux2): remove inert guidance UI, add Klein 4B Base variant, fix metadata recall#8995
Pfannkuchensack wants to merge 26 commits intoinvoke-ai:mainfrom
Pfannkuchensack:fix/flux2-klein-guidance-and-metadata

Conversation

@Pfannkuchensack
Copy link
Copy Markdown
Collaborator

@Pfannkuchensack Pfannkuchensack commented Mar 26, 2026

Summary

Several independent fixes around FLUX.2 Klein model support plus a SDXL DoRA + partial-loading regression fix:

FLUX.2 Klein guidance & metadata cleanup

  • All current Klein variants (4B, 4B Base, 9B, 9B Base) report guidance_embeds=false in their HF transformer config, or ship with absent/zeroed guidance projection weights. The scalar therefore has no effect on output for any Klein model currently on HuggingFace.
  • util.py: Set guidance_embed=False for all four Klein variants (Klein9BBase was previously True, but the loader already zeroed the weights — cosmetic drift that misled readers).
  • Hide the guidance slider from the generation settings accordion for every Flux2 Klein variant. It was previously shown for klein_9b_base where it appeared to do something but didn't.
  • Stop writing guidance into the flux2_denoise node and into Flux2 image metadata.
  • Update denoise docstrings and the flux2_denoise invocation field description to reflect the inert behaviour.
  • Node Editor power users keep the full guidance, cfg_scale, and negative_text_conditioning fields on flux2_denoise for experimentation — only the linear UI gating changes.

FLUX.2 Klein 4B Base variant recognition

  • New Flux2VariantType.Klein4BBase enum entry alongside the existing Klein9BBase.
  • Filename heuristic ("base" in name.lower()) extended to Klein 4B, mirroring the existing 9B Base logic. FLUX.2-klein-base-4B (https://huggingface.co/black-forest-labs/FLUX.2-klein-base-4B) is now detected as its own variant on import.
  • Qwen3 encoder compatibility check, VAE extraction, qwen3_source_model fallback, and default settings extended to include Klein4BBase (Qwen3_4B encoder, 28-step default).
  • UI behaviour for 4B Base stays identical to distilled 4B — no extra surfaces.

QA Instructions

  1. FLUX.2 Klein 4B (distilled): Import / load a FLUX.2-klein-4b diffusers or safetensors model. Linear UI advanced panel shows steps + scheduler; no guidance slider. Generate — output identical to prior behaviour.
  2. FLUX.2 Klein 4B Base: Import FLUX.2-klein-base-4B (diffusers or a safetensors file whose name contains "base"). Verify it is identified as klein_4b_base in the model manager (FLUX.2 Klein 4B Base). Selecting it loads the Qwen3-4B encoder (not Qwen3-8B). Generate — single-pass image, no guidance slider, no CFG slider.
  3. FLUX.2 Klein 9B / 9B Base: Guidance slider is hidden for both. Generation works. Image metadata no longer contains guidance.
  4. Old images with guidance metadata: Recalling an older Flux2 image that was generated with the previous build should still work — guidance is silently ignored, no errors.
  5. FLUX.1 Dev / Schnell: No regressions. Guidance slider still works, metadata still includes guidance.
  6. Node Editor: flux2_denoise still exposes guidance, cfg_scale, and negative_text_conditioning as editable fields. Wiring a negative text encoder + cfg_scale > 1.0 still produces the two-pass CFG output that worked before.

Merge Plan

No special ordering required. Backend enum change is additive (Klein4BBase is a new value, not a rename), so older clients reading the schema will simply not see the new variant.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Tests added / updated (if applicable)
  • ❗Changes to a redux slice have a corresponding migration — no slice changes
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

@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 labels Mar 26, 2026
@lstein lstein added the v6.13.x label Apr 7, 2026
@lstein lstein moved this to 6.13.x Theme: MODELS in Invoke - Community Roadmap Apr 7, 2026
Copy link
Copy Markdown
Collaborator

@JPPhoto JPPhoto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some changes and recommendations:

  1. Klein 9B Base guidance still is not written to image metadata, so the main recall/regeneration fix is incomplete. In buildFLUXGraph.ts, the FLUX.2 metadata object only includes model, steps, and scheduler; it never adds guidance, even though the denoise node now receives it. The parser in parsing.tsx looks for metadata.guidance, so FLUX.2 images still will not display or recall guidance.

  2. The metadata viewer will show duplicate VAE entries for FLUX.2 images. ImageMetadataActions.tsx already renders the generic VAEModel handler, and this change adds KleinVAEModel too. I think both handlers parse the same metadata.vae key in parsing.tsx, and both accept FLUX.2, so the UI will end up with two recall controls for the same VAE.

  3. More automated coverage is needed for this feature. I did not find new tests covering the added FLUX.2 guidance path, scheduler metadata path, or the new metadata recall entries. Consider targeted unit/integration coverage: negative cases for guidance being omitted for distilled Klein variants and outside klein_9b_base, plus positive cases for guidance and scheduler being persisted into graph metadata and for the new metadata fields to parse and recall correctly. This PR changes graph construction and metadata handling, and tests at those seams would help lock the behavior down.

… Base, and fix metadata recall

Klein 4B and 9B (distilled) have guidance_embeds=False, while Klein 9B Base
(undistilled) has guidance_embeds=True. This commit:
- Sets guidance_embed=False for Klein 4B/9B and adds Klein9BBase with True
- Adds guidance parameter to Flux2DenoiseInvocation (used by Klein 9B Base)
- Passes real guidance value instead of hardcoded 1.0 in flux2/denoise.py
- Hides guidance slider for distilled Klein models, shows it for Klein 9B Base
- Shows Flux scheduler dropdown for all Flux2 Klein models
- Passes scheduler to Flux2 denoise node and saves it in metadata
- Adds KleinVAEModel and KleinQwen3EncoderModel to recall parameters panel
…all dedupe

Add a mock-based harness for buildFLUXGraph that locks in the FLUX.2
orchestration: guidance is written to metadata and the flux2_denoise
node only for klein_9b_base, distilled variants (klein_9b, klein_4b)
omit it, the FLUX scheduler is persisted into both metadata and the
denoise node, and separately selected Klein VAE / Qwen3 encoder land
in metadata.

Add parsing tests for the metadata recall handlers: KleinVAEModel and
KleinQwen3EncoderModel only fire when the current main model is FLUX.2,
and the generic VAEModel handler now bails out for flux2 / z-image so
the metadata viewer no longer renders duplicate VAE rows next to the
dedicated Klein / Z-Image handlers.
@Pfannkuchensack Pfannkuchensack force-pushed the fix/flux2-klein-guidance-and-metadata branch from de68d29 to edbc705 Compare April 7, 2026 23:25
@JPPhoto JPPhoto self-requested a review April 9, 2026 17:08
Copy link
Copy Markdown
Collaborator

@JPPhoto JPPhoto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. I think we should ask other people to specifically test the undistilled model since I can't really run that easily on my end.

…adata' into fix/flux2-klein-guidance-and-metadata

# Conflicts:
#	invokeai/frontend/web/src/features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion.tsx
@skunkworxdark
Copy link
Copy Markdown
Contributor

I hope I haven't misunderstood the intention of the PR, I tested it but I couldn't get it to work as I expected.

What I tested and what failed:

I started by testing the FLUX.2-klein-base-9B model. The UI updates look great, and I verified in the node editor that the graph correctly passes the new guidance values to the backend (I tested 2.0 and 5.0). However, the generated images came out exactly identical.

I tried manually editing the model's config.json to "guidance_embeds": true and restarting the server. Even with that change, the backend still produced identical images, suggesting the model wasn't processing the guidance tensor.

What I tried next and what I found:

Since the Guidance parameter wasn't affecting the output, I wondered if the model might respond to traditional CFG instead. I bypassed the linear UI and used the Node Editor to feed different cfg_scale values (2.0 and 5.0) along with a negative prompt directly into the FLUX2 Denoise node.

This worked! The model responded to the CFG changes and produced completely different, properly guided images. I also went back and tested the FLUX.2-klein-base-4B model and found it responds to CFG Scale adjustments as well, though the UI currently hides the sliders when the 4B Base variant is selected.

Testing with Kontext Conditioning:

I also tested using reference images via Kontext Conditioning. I initially ran into an issue where the output would be a pure black image when using a CFG scale > 1.0. However, I realised this was due to an incompatible VAE. When I ensured the specific flux2-vae was selected, or the diffusers version, it worked perfectly! The math in denoise.py handles reference images and CFG scales beautifully as long as the correct VAE is used.

What I think should be done:

Based on these tests, it looks like the Klein Base models rely on standard CFG rather than guidance embeddings. Since the backend denoise.py already processes the CFG math correctly when a negative prompt is provided, I think we just need to adjust the frontend UI mapping and metadata handling:

  • Hide the Guidance slider for all FLUX.2 Klein models.
  • Unhide the CFG Scale slider and Negative Prompt box for the Base variants (klein_9b_base and klein_4b_base).
  • Update the metadata and recall to use CFG Scale value instead of Guidance for these base models.
  • Keep the CFG locked to 1.0 (or hidden) for the Distilled variants.

Recognize FLUX.2-klein-base-4B on import via filename heuristic.
The variant shares Klein4B's architecture (Qwen3-4B encoder,
context_in_dim=7680) and reports guidance_embeds=False in its HF
config, consistent with Klein 9B Base. UI behavior stays identical
to distilled Klein4B until CFG support is wired up in a follow-up.
@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 21, 2026

  • Please verify that this is the correct behavior; invokeai/frontend/web/src/features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion.tsx:116-121, invokeai/frontend/web/src/features/modelManagerV2/models.ts:268-277, invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx:15-38, invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFLUXGraph.ts:179-196, invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFLUXGraph.ts:247-264, invokeai/app/invocations/flux2_denoise.py:99-115, invokeai/app/invocations/flux2_denoise.py:280-381, invokeai/app/invocations/flux2_denoise.py:469-485
    The linear FLUX.2 UI still exposes the wrong control surface for the Base variants. The backend node accepts cfg_scale and optional negative_text_conditioning for all FLUX.2 runs, and its denoiser applies external CFG when those are present. But the frontend still treats all flux2 models as "no negative prompt / no CFG": SUPPORTS_NEGATIVE_PROMPT_BASE_MODELS excludes flux2, Prompts.tsx therefore never shows the negative prompt UI, and GenerationSettingsAccordion.tsx hides ParamCFGScale for every FLUX.2 model while only showing ParamGuidance for klein_9b_base. The graph builder then follows that assumption: the FLUX.2 path only wires positive conditioning plus optional guidance, and never sends negative conditioning or a non-default CFG path into flux2_denoise. This means klein_4b_base has no usable guidance mechanism in the standard UI at all, and klein_9b_base cannot reach the backend CFG path that the PR comment thread reports is the one that actually changes outputs. The PR comment is consistent with this code: guidance changes were a no-op, while Node Editor CFG changes worked. To expose this issue, add a test that builds the standard FLUX.2 graph for klein_4b_base and klein_9b_base and proves the linear UI path can supply cfg_scale and negative conditioning to flux2_denoise for Base variants, while keeping that path unavailable for distilled variants.

  • invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFLUXGraph.test.ts:84, invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFLUXGraph.test.ts:216-369, invokeai/frontend/web/src/features/metadata/parsing.test.tsx:62-128
    The new automated coverage still does not defend the variant-specific FLUX.2 control contract that this PR changes. buildFLUXGraph.test.ts exercises Qwen3 source-model selection and basic node presence, but the only guidance occurrence is the mocked selector value at line 84; there are no assertions for FLUX.2 Base-vs-distilled control wiring, no assertion that Base variants get a usable guidance mechanism, and no assertion around cfg_scale or negative conditioning. parsing.test.tsx only covers metadata-handler gating for VAE/Qwen3 recall. That is why the current tests stay green while the standard UI/graph path for Klein Base remains incomplete. To expose this issue, add a test that asserts the generated FLUX.2 graph and recalled metadata differ by variant: distilled models must omit CFG/negative paths, while Base models must persist and recall the actual control path used by generation.

  • invokeai/backend/flux/util.py:138-184 and invokeai/backend/model_manager/configs/main.py:867-868 still encode conflicting assumptions about Klein Base guidance embeddings. If the shipped checkpoints really have guidance_embeds=False, the current klein_9b_base guidance-only UX is not just incomplete, it is conceptually wrong.

@Pfannkuchensack
Copy link
Copy Markdown
Collaborator Author

  • Please verify that this is the correct behavior; invokeai/frontend/web/src/features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion.tsx:116-121, invokeai/frontend/web/src/features/modelManagerV2/models.ts:268-277, invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx:15-38, invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFLUXGraph.ts:179-196, invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFLUXGraph.ts:247-264, invokeai/app/invocations/flux2_denoise.py:99-115, invokeai/app/invocations/flux2_denoise.py:280-381, invokeai/app/invocations/flux2_denoise.py:469-485
    The linear FLUX.2 UI still exposes the wrong control surface for the Base variants. The backend node accepts cfg_scale and optional negative_text_conditioning for all FLUX.2 runs, and its denoiser applies external CFG when those are present. But the frontend still treats all flux2 models as "no negative prompt / no CFG": SUPPORTS_NEGATIVE_PROMPT_BASE_MODELS excludes flux2, Prompts.tsx therefore never shows the negative prompt UI, and GenerationSettingsAccordion.tsx hides ParamCFGScale for every FLUX.2 model while only showing ParamGuidance for klein_9b_base. The graph builder then follows that assumption: the FLUX.2 path only wires positive conditioning plus optional guidance, and never sends negative conditioning or a non-default CFG path into flux2_denoise. This means klein_4b_base has no usable guidance mechanism in the standard UI at all, and klein_9b_base cannot reach the backend CFG path that the PR comment thread reports is the one that actually changes outputs. The PR comment is consistent with this code: guidance changes were a no-op, while Node Editor CFG changes worked. To expose this issue, add a test that builds the standard FLUX.2 graph for klein_4b_base and klein_9b_base and proves the linear UI path can supply cfg_scale and negative conditioning to flux2_denoise for Base variants, while keeping that path unavailable for distilled variants.

I will address this in another PR.
But i need to check huggingface/diffusers#13420 (comment) first.

@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 22, 2026

@Pfannkuchensack Request a re-review from me when you and @skunkworxdark are satisfied with the state of things, please.

@skunkworxdark
Copy link
Copy Markdown
Contributor

My comments remain the same at the moment. With a slight difference, I don't think we need to have the negative prompt visible now.

The current guidance setting doesn't create a different image output at all. I still believe that it should be cfg_scale that is used instead, but this requires a negative prompt to be passed. This neg prompt in the diffusers pipeline is internally defaulted to "", but in the Invoke implementation, it needs to be passed manually.

However, using a cfg_scale > 1 will double the generation time.

In my testing, I don't believe that exposing the negative prompt to the user would provide any benefit over it being automatically set to "".

As for the rest of the PR I have done some research into guidance_embeds=true for the base model and I can't find any reference that states this. Infact the BFL base model for both 4B and 9B base have the transformer/config.json containing guidance_embeds=false. For me, this should probably be the source of truth on this matter. Unless there is a paper or repo that states otherwise.

All current FLUX.2 Klein variants (4B, 4B Base, 9B, 9B Base) report
guidance_embeds=false in their HF transformer config (or have zeroed
projection weights), so the guidance scalar has no effect on output.
The linear UI previously exposed a guidance slider for klein_9b_base
and wrote the value into metadata, which misled users into thinking
it was steering generation.
@Pfannkuchensack Pfannkuchensack changed the title Fix(Flux2): Correct guidance_embed, add guidance support for Klein 9B Base, and fix metadata recall fix(flux2): remove inert guidance UI, add Klein 4B Base variant, fix metadata recall Apr 22, 2026
@Pfannkuchensack
Copy link
Copy Markdown
Collaborator Author

OK Updated the PR.

@Pfannkuchensack Pfannkuchensack requested a review from JPPhoto April 22, 2026 23:20
@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 23, 2026

@Pfannkuchensack Did you mean to check something in? I don't see any recent commits.

@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 23, 2026

invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx:45-48, invokeai/frontend/web/src/features/metadata/parsing.tsx:377-388: Old FLUX.2 images with saved guidance are not actually "silently ignored" on recall. The metadata viewer still always renders the generic Guidance handler, and that handler unconditionally parses metadata.guidance and dispatches setGuidance(value) on recall. The trigger is recalling a pre-change FLUX.2 image that still carries guidance metadata. In this branch, the linear FLUX.2 UI no longer exposes guidance and buildFLUXGraph no longer writes it into new FLUX.2 graphs, so the recalled value becomes hidden state rather than an active FLUX.2 control. That contradicts the PR's QA contract for old images and can leak stale guidance into later model switches, because guidance is still the shared global parameter used by FLUX.1. The evidence chain is direct: ImageMetadataActions always includes ImageMetadataHandlers.Guidance, Guidance.parse() accepts any metadata.guidance, and Guidance.recall() always dispatches setGuidance(value) with no FLUX.2 gating. To expose this issue, add a test that recalls legacy FLUX.2 metadata containing guidance while a FLUX.2 main model is selected and verifies the value is ignored rather than written back into the shared guidance state.

The generic Guidance metadata handler unconditionally parsed
`metadata.guidance` and dispatched `setGuidance(value)` into the
shared params slice. For images generated before the Klein guidance
cleanup, this still fired — silently writing a stale guidance value
into the global state, which then leaked back into FLUX.1 on model
switch.

Gate the handler on `metadata.model.base`: reject parsing when the
image was generated with a FLUX.2 model. The handler is then skipped
for both display and recall on legacy FLUX.2 metadata, matching the
"silently ignored" contract stated in the PR.

- parsing.tsx: check metadata.model.base in Guidance.parse()
- parsing.test.tsx: three new cases covering FLUX.2 gating,
  FLUX.1 pass-through, and back-compat for metadata without a
  model field
Copy link
Copy Markdown
Collaborator

@JPPhoto JPPhoto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to merge!

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 frontend PRs that change frontend files invocations PRs that change invocations python PRs that change python files v6.13.x

Projects

Status: 6.13.x Theme: MODELS

Development

Successfully merging this pull request may close these issues.

5 participants