Skip to content

Feat: flux2 dev support#9234

Open
Pfannkuchensack wants to merge 21 commits into
invoke-ai:mainfrom
Pfannkuchensack:feature/flux2-dev-support
Open

Feat: flux2 dev support#9234
Pfannkuchensack wants to merge 21 commits into
invoke-ai:mainfrom
Pfannkuchensack:feature/flux2-dev-support

Conversation

@Pfannkuchensack

@Pfannkuchensack Pfannkuchensack commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds end-to-end support for FLUX.2 [dev] alongside the existing FLUX.2 Klein implementation. Dev uses a Mistral Small 3 text encoder (hidden 5120 → joint_attention_dim 15360, hidden states sampled at layers 10/20/30) instead of Klein's Qwen3. It shares the 32-channel AutoencoderKLFlux2 VAE and the 4D-RoPE sampling backend with Klein, so most existing infrastructure is reused — only the Mistral encoder loaders/configs and the Dev graph wiring are new.

Two Mistral encoder packagings are auto-detected: the 40-layer Mistral that ships in the black-forest-labs/FLUX.2-dev diffusers repo, and the 30-layer cow-mistral3-small distillation (Comfy-Org safetensors, gguf-org GGUFs). Single-file and GGUF encoders embed the Tekken tokenizer, so no separate tokenizer download is needed.

What works

  • Generation modes: txt2img, img2img, inpaint, outpaint (canvas)
  • Multi-reference image editing via the FLUX Kontext ref-image UI (same as Klein)
  • LoRAs for FLUX.2 [dev]
  • Model formats: diffusers folder, single-file safetensors, and GGUF transformers; Mistral encoder as diffusers / single-file (bf16 / fp8 / fp4) / GGUF, with standalone VAE + encoder selection in Advanced Settings
  • Metadata recall / Remix: Dev VAE + Mistral encoder restore correctly, disambiguated from Klein's VAE / Qwen3 slice
  • Starter models for the Dev transformer, VAE, and Mistral encoders

Limitations / notes

  • Memory: the Mistral encoder is large (fp8 ~18 GB, bf16 ~35 GB) — plan VRAM/RAM accordingly.
  • Encoder precision matters: very low-bit GGUF Mistral encoders (e.g. Q2/Q3) noticeably weaken prompt adherence; prefer fp8 / bf16 or Q6/Q8.
  • Non-BFL Mistral: upstream Mistral Small 3.1 / 3.2 (40-layer, non-BFL) is accepted but off-distribution for FLUX.2 and logs a load-time warning; it is not offered as a starter model.
  • Tokenizer fallback: encoders that don't embed Tekken (older cow GGUFs, bare diffusers folders without tokenizer/) fall back to fetching black-forest-labs/FLUX.2-dev:tokenizer from HF; offline with no HF cache this errors with documented workarounds.
  • License: FLUX.2 [dev] is Non-Commercial (flagged in isNonCommercialMainModelConfig).
  • Applying a Klein LoRA on a Dev model logs a variant-mismatch warning (no crash).

How to test

Automated:

# backend (fast, no model load)
uv run --extra cuda pytest tests/test_imports.py tests/model_identification/test_identification.py

pyproject.toml gained mistral-common; uv.lock is regenerated (uv lock --locked passes).

End-to-end:

  1. In the Model Manager install a Dev transformer (a gguf-org/flux2-dev-gguf quant — ~20 GB for Q4_K_M — or the full black-forest-labs/FLUX.2-dev diffusers folder), the FLUX.2 VAE, and a Mistral encoder (Comfy-Org fp8 recommended, or a Q6/Q8 cow GGUF).
  2. Select the Dev transformer. Advanced Settings should show FLUX.2 [dev] VAE and FLUX.2 [dev] Mistral Encoder dropdowns (not Klein's Qwen3 selector). A missing VAE/encoder should block queueing with a Dev-specific message.
  3. Defaults: 28 steps, guidance 3.5, CFG 1.0, 1024×1024. Generate — the first run with Comfy / cow files logs Loaded embedded Tekken tokenizer from <file> (no HF fetch).
  4. Exercise img2img, inpaint / outpaint on canvas, and a multi-reference edit.
  5. Apply a FLUX.2 [dev] LoRA and generate.
  6. Recall: open a generated Dev image → Remix; Dev VAE + Mistral encoder should restore. Repeat with a Klein image and confirm Klein VAE + Qwen3 still recall (no cross-contamination).

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
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

Closes #8668

Adds end-to-end support for FLUX.2 [dev] alongside the existing Klein
implementation. Dev uses Mistral Small 3.1 (24B) as its sole text encoder
instead of Klein's Qwen3, with joint_attention_dim=15360 and the
guidance-distilled 32B transformer.

Backend
- taxonomy: Flux2VariantType.Dev, ModelType.MistralEncoder,
  ModelFormat.MistralEncoder, MistralVariantType
- configs: probe dev via context_in_dim=15360 (main + LoRA); new
  mistral_encoder.py with Diffusers / Checkpoint / GGUF configs;
  Main_Diffusers_Flux2_Config accepts Flux2Pipeline class name
- loaders: new mistral_encoder.py (AutoModel for Diffusers folder,
  MistralModel for single-file + GGUF with llama.cpp key conversion).
  Existing Klein transformer loaders are generic enough for dev
- ModelRecordChanges.variant union extended with MistralVariantType

Invocations
- flux2_dev_model_loader, flux2_dev_text_encoder (Mistral chat-template
  with FLUX2_DEV_SYSTEM_MESSAGE and layer-stacking 10/20/30),
  flux2_dev_lora_loader (+ collection variant)
- MistralEncoderField on model.py; flux2_denoise / flux2_vae_decode /
  flux2_vae_encode reused unchanged (already model-agnostic)

Frontend
- types/hooks/selectors for MistralEncoder, isFlux2DevMainModelConfig,
  selectFlux2DevDiffusersModels, useMistralEncoderModels
- params slice fields flux2DevVaeModel / flux2DevMistralEncoderModel /
  flux2DevSourceModel + reducers, selectIsFlux2Dev / selectIsFlux2Klein
- ParamFlux2DevModelSelect component, wired into AdvancedSettingsAccordion
- buildFLUXGraph dev branch with full txt2img / img2img / inpaint /
  outpaint + multi-reference image editing (same flux_kontext +
  collect chain as Klein, since Flux2RefImageExtension is model-agnostic)
- addFlux2DevLoRAs helper for dev LoRA wiring
- zModelType / zModelFormat / zFlux2VariantType extended for
  mistral_encoder / mistral_small_3_1 / dev
- OpenAPI schema regenerated, TS types updated

Starter models
- FLUX.2 [dev] Diffusers (bf16 + NF4), three GGUFs (Q4/Q6/Q8), Mistral
  encoder (bf16 + NF4)
Follow-up fixes after first end-to-end run with FLUX.2 [dev] GGUF +
Mistral 3.x GGUF + standalone FLUX.2 VAE.

Frontend
- buildFLUXGraph: wire dev model loader's vae into both flux2_denoise
  (required for BN statistics / inpaint) and flux2_vae_decode; missing
  edge was raising RequiredConnectionException at runtime
- readiness.ts: variant-aware FLUX.2 readiness check — dev requires
  flux2DevVaeModel + flux2DevMistralEncoderModel (or a Dev diffusers
  source); Klein keeps Qwen3/VAE check. Threads
  hasFlux2DevDiffusersSource through generate + canvas tabs and updates
  buildGenerateTabArg / buildCanvasTabArg test helpers
- en.json: noFlux2DevVaeModelSelected, noFlux2DevMistralEncoderModelSelected

Mistral encoder loader (GGUF / single-file)
- Fix "Cannot copy out of meta tensor": llama.cpp conversion produced
  `model.*` keys but loader instantiated bare MistralModel (no `model.`
  prefix). Add _convert_for_bare_mistral_model to strip the prefix and
  drop lm_head before load_state_dict
- _materialize_remaining_meta_tensors: after load_state_dict, replace any
  still-meta parameters (norms→ones, others→zeros) and buffers so the
  cache→VRAM move can't fail on partial state dicts, with a warning
  listing what was missing
- llama.cpp converter: map attn_q_norm/attn_k_norm (Mistral 3.x qk-norm
  variants), with ordering before attn_q/attn_k to avoid bad rewrites

Tokenizer / processor fallback
- _load_processor_with_offline_fallback walks a list of sources
  (black-forest-labs/FLUX.2-dev tokenizer subfolder, then
  mistralai/Mistral-Small-3.1-… and 3.2-…), trying AutoProcessor then
  AutoTokenizer for each, cache-first then online. Final error spells
  out the three workarounds (install Diffusers folder, set HF_ENDPOINT,
  pre-cache the tokenizer)
- flux2_dev_text_encoder: try multimodal `[{type, text}]` chat template
  first (PixtralProcessor / Mistral3Processor), fall back to plain
  string content (AutoTokenizer), then to manual [INST]…[/INST]

Qwen3 encoder probe strictness
- _get_qwen3_variant_from_state_dict and _get_variant_from_config now
  return None / raise NotAMatchError for unknown hidden_size instead of
  silently defaulting to qwen3_4b. The old fallback meant any llama.cpp
  GGUF causal LM (Mistral, Llama, …) was wrongly classified as Qwen3 —
  visible when the Mistral 3.x GGUF was identified as a Qwen3-4B encoder
- Checkpoint / GGUF / Diffusers loaders propagate the strictness
@github-actions github-actions Bot added python PRs that change python files invocations PRs that change invocations backend PRs that change backend files services PRs that change app services frontend PRs that change frontend files labels May 25, 2026
@lstein lstein self-assigned this May 27, 2026
@lstein lstein added the 6.14.x label May 27, 2026
@lstein lstein moved this to 6.14.x Theme: USER EXPERIENCE in Invoke - Community Roadmap May 27, 2026
…andlers

Upstream Mistral Small 3.1/3.2 (40 layers) produces off-distribution embeddings
under FLUX.2's static (10, 20, 30) hidden-state extraction. The joint attention
was actually trained against BFL's 30-layer cow-mistral3-small distillation —
both Comfy-Org's safetensors and gguf-org's cow GGUFs ship the same 30-layer
weights, just packaged differently.

- Probing (configs/mistral_encoder.py) now rejects non-cow Mistrals across all
  three formats (Diffusers / Checkpoint / GGUF) with a clear error.
- Loader (load/model_loaders/mistral_encoder.py) extracts the embedded Tekken
  tokenizer from the `tekken_model` U8 (safetensors) / fp16-per-byte (cow GGUF)
  tensor via mistral_common, falling back to the BFL HF tokenizer. Removes the
  INVOKEAI_MISTRAL_TOKENIZER_SOURCE env var.
- Starter models: drop upstream Mistral 3.x entries, add Comfy-Org bf16/fp8/fp4
  variants alongside the cow GGUFs.
- MistralVariantType: drop Small3_1, keep only Cow.
- pyproject.toml: add mistral-common dependency.

Frontend recall:
- Add Flux2DevVAEModel + Flux2DevMistralEncoderModel handlers, disambiguating
  Klein vs dev via presence of `mistral_encoder` / `qwen3_encoder` metadata
  fields (both bases are `flux2`).
- Wire both into the Recall Parameters panel (hardcoded list was missing them).
- Add `metadata.mistralEncoder` i18n key + colocated tests.
@github-actions github-actions Bot added Root python-deps PRs that change python dependencies labels Jun 6, 2026
…encoders

After studying ComfyUI's `Flux2Tokenizer` / `Mistral3_24BModel` reference
implementation, align the FLUX.2 [dev] text-encoder path with their setup:

- Probing now accepts both 30-layer (cow distillation) and 40-layer (Mistral
  Small 3, BFL canonical / upstream) Mistrals. Re-adds `MistralVariantType.Mistral24B`
  alongside `Cow`. All three configs (Diffusers / Checkpoint / GGUF) updated.

- Loaders strip `model.norm` (replace with Identity) when the loaded weights
  are the 30-layer cow distillation. Matches Comfy's `final_norm=False` for
  the pruned variant; for transformers' `MistralModel` the final RMSNorm is
  always built but the cow was trained against the raw post-layer-29 state.

- 40-layer loads now log a clear warning that upstream Mistral 3.1 / 3.2 is
  NOT what FLUX.2's joint attention was trained against and recommends the
  Comfy-Org bf16/fp8/fp4 or gguf-org cow GGUF variants. BFL's canonical
  bundled text_encoder is also 40-layer so we don't hard-reject; the warning
  is opt-in self-discipline.

- Text encoder invocation switches from `apply_chat_template(messages, ...)`
  to a raw text template `[SYSTEM_PROMPT]{sys}[/SYSTEM_PROMPT][INST]{prompt}[/INST]`
  fed straight to the tokenizer — byte-for-byte matches Comfy's
  `Flux2Tokenizer.llama_template.format(text)`. System prompt now includes
  the literal `\n` between "object" and "attribution" Comfy ships.

- `_TekkenChatTemplateAdapter` renamed to `_TekkenRawTextAdapter` and exposes
  a `__call__(text, padding_side='left', ...)` interface that Tekken-encodes
  the raw string (BOS=1, no EOS) and left-pads with token id 11. Matches
  Comfy's `pad_left=True` / `pad_token=11` settings.

Frontend types extended for the new `mistral3_24b` variant
(zMistralVariantType, MODEL_VARIANT_TO_LONG_NAME, schema.ts).

@lstein lstein left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Frontend build is failing. pytests are failing.

lstein and others added 5 commits July 9, 2026 20:52
Knip reported 6 unused exports. Each was dead code rather than incomplete
wiring, verified against the actual consumers:

- Drop the vestigial `flux2DevSourceModel` param end-to-end (state field,
  default, migration, reducer, action, selector, test). The FLUX graph
  builder auto-picks the diffusers source itself and never read this param;
  no UI set it. Mirrors how the Klein path already works.
- Delete `selectIsFlux2Klein`; the graph builder computes this locally and
  only `selectIsFlux2Dev` is consumed.
- Un-export `zMistralVariantType`; used only in the local `zAnyModelVariant`
  union, like `zQwenImageVariantType`.
- Delete `selectMistralEncoderModels`; components use the
  `useMistralEncoderModels` hook instead.
- Un-export `isFlux2DevMainModelConfig`; used only within types.ts, like its
  `isFluxDevMainModelConfig` / `isFlux2Klein9BMainModelConfig` siblings.
The dev-vs-Klein VAE recall keyed off the presence of a mistral_encoder
metadata field, but that field is only written when a standalone Mistral
encoder is selected. A FLUX.2 [dev] image whose encoder came from a
Diffusers source has a vae field but no mistral_encoder, so its VAE was
silently recalled into the Klein slice.

Resolve the image's own main model and check variant === 'dev' instead —
the same signal the graph builder uses. Add regression coverage for the
mistral_encoder-absent dev case, and add the missing modelManager.flux2Dev*
i18n keys so the [dev] VAE/encoder labels are translatable.
The diffusers FLUX.2-dev text encoder loads a PixtralProcessor, whose
first positional __call__ parameter is `images`, not `text`. Passing the
prompt positionally routed it into `images`, breaking the diffusers
encoder path (only single-file/GGUF encoders, which use a text-first
adapter, had been exercised). Pass text= explicitly.
The diffusers FLUX.2-dev text encoder loads a PixtralProcessor, whose
first positional __call__ parameter is `images`, not `text`. Passing the
prompt positionally raised "Incorrect image source", breaking the
diffusers encoder path entirely. Only single-file/GGUF encoders (text-first
adapter) had been exercised. Verified against transformers 5.5.4.

fix(flux2): emit Tekken special tokens in the embedded-tokenizer adapter

_TekkenRawTextAdapter used mistral_common's raw Tekkenizer.encode, which
runs with SpecialTokenPolicy.IGNORE and BPE-encodes the FLUX.2 markers
([SYSTEM_PROMPT], [/SYSTEM_PROMPT], [INST], [/INST]) as literal text — 54
tokens instead of 36, corrupting the prompt structure fed to FLUX.2 on the
single-file and GGUF paths. Resolve the marker ids from the tokenizer's
special vocab and splice them in; output is now byte-identical to the
reference PixtralProcessor.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.14.x backend PRs that change backend files frontend PRs that change frontend files invocations PRs that change invocations python PRs that change python files python-deps PRs that change python dependencies Root services PRs that change app services

Projects

Status: 6.14.x Theme: USER EXPERIENCE

Development

Successfully merging this pull request may close these issues.

[enhancement]: Support for Flux.2 model

2 participants