feat(Model Support): add Krea-2-Turbo model + LoRA support (WIP)#9304
feat(Model Support): add Krea-2-Turbo model + LoRA support (WIP)#9304Pfannkuchensack wants to merge 19 commits into
Conversation
Integrate Krea-2-Turbo (krea/Krea-2-Turbo) text-to-image per
NEW_MODEL_INTEGRATION.md: Krea2Transformer2DModel (single-stream MMDiT)
+ Qwen3-VL text encoder (12-layer hidden-state tap, 4D prompt_embeds)
+ reused Qwen-Image VAE + FlowMatchEulerDiscrete scheduler.
Backend:
- taxonomy: BaseModelType.Krea2, ModelType/ModelFormat.Qwen3VLEncoder,
Krea2VariantType (Turbo = "krea2_turbo" to avoid Z-Image collision)
- config probes: Main_Diffusers/Checkpoint_Krea2, Qwen3VLEncoder,
LoRA_LyCORIS_Krea2 (text_fusion/time_mod_proj signature; excluded
from the Qwen-Image probe to avoid double-match)
- loaders for the diffusers pipeline + standalone Qwen3-VL encoder,
with runtime workarounds for the HF model's version mismatches
(AutoTokenizer, extra_special_tokens={}, rope_parameters->rope_scaling)
- native sampling (pack/unpack, position_ids, linear-mu shift) and
hand-written Euler denoise loop; reuses qwen_image l2i/i2l
- invocations: model_loader, text_encoder, denoise, lora_loader, plus
two ecosystem enhancers (conditioning rebalance, seed variance)
- LoRA conversion for diffusers PEFT (lora_transformer- prefix)
Frontend:
- 'krea-2' base + qwen3_vl_encoder type/format across model maps,
buildKrea2Graph, addKrea2LoRAs, graph-builder denoise/base lists,
optimal dimension 1024, regenerated schema.ts
Fixes:
- estimate transformer working memory in krea2_denoise so the cache
reserves activation headroom and offloads more model under partial
loading; fixes fp8 + LoRA OOM at 1024 (model was placed before LoRA
patches were applied, leaving no room for their activations)
WIP: requires diffusers main (>=0.39 dev) for Krea2Transformer2DModel;
pyproject.toml temporarily pins diffusers to git main.
|
Amazing! I was just thinking of working on this myself and you did it for me! |
Allow non-diffusers Krea-2 transformers (GGUF/fp8) to run with standalone single-file VAE + Qwen3-VL encoder, fixing several blockers found in testing. - buildKrea2Graph: drop the hard "requires Diffusers-format" assert; instead require both a VAE and a Qwen3-VL encoder to be selected when the transformer is not diffusers (mirrors readiness.ts). - Qwen3-VL encoder remap: handle both single-file key conventions — implicit (model.layers.*) and explicit (model.language_model.*). The old blind model.* -> language_model.* turned the bf16 file's keys into language_model.language_model.* (398 meta tensors -> "Cannot copy out of meta tensor" crash). Both files now load 0 missing / 0 unexpected / 0 meta. - Qwen3-VL tokenizer/config: broaden the offline-cache fallback from OSError to Exception so a partial HF cache (config present, vocab missing) re-fetches instead of dying with TypeError. - Qwen3-VL encoder fp8: keep an fp8 source checkpoint fp8-resident with per-layer upcast (storage float8_e4m3fn, compute bf16) instead of dequantizing to bf16. Halves resident VRAM (~8.9GB -> ~4.4GB), avoiding partial-load thrashing alongside a large transformer. Auto-enabled for fp8 sources on CUDA; bf16 files stay bf16. - Qwen-Image VAE: a native-layout qwen_image_vae single file is classified with the Anima base and loaded as AutoencoderKLWan, but the qwen l2i/i2l nodes need AutoencoderKLQwenImage. Add backend/krea2/vae_compat.py::as_qwen_image_vae to reinterpret a Wan VAE as AutoencoderKLQwenImage (state dicts are identical, 194/194 keys); both qwen VAE nodes use it. Idempotent for real QwenImage VAEs.
|
Can you add it to the list of supported models here https://github.com/invoke-ai/InvokeAI/blob/main/README.md?plain=1#L61 |
An upstream merge reintroduced the AutoencoderKLQwenImage isinstance asserts in the qwen VAE nodes (without the import → F821) and dropped the adapter in the i2l path. A native-layout qwen_image_vae single file is classified with the Anima base and loaded as AutoencoderKLWan, so the asserts fail at runtime. - qwen_image_latents_to_image: drop the reintroduced pre-device assert (the as_qwen_image_vae adapter inside model_on_device already handles the class). - qwen_image_image_to_latents: restore the as_qwen_image_vae import + adapter call, remove both asserts. - estimate_vae_working_memory_qwen_image only reads tensor shape + element size, so it runs correctly on either VAE class before the adapter.
…l_loader
krea2_model_loader was added to MainModelLoaderNodes but not to
isMainModelWithoutUnet, and the guard wasn't a type predicate — so it never
narrowed modelLoader. OutputFields of the loader union collapses to the common
'vae' field, making g.addEdge(modelLoader, 'unet', ...) in addInpaint/addOutpaint
fail to type-check ('unet' not assignable to 'vae').
Redefine the guard as a type predicate keyed on the inverse (only
main_model_loader/sdxl_model_loader expose a unet), so every transformer-based
loader is treated as unet-less automatically and the negated branch narrows to
the unet-bearing loaders.
zKrea2VariantType was only used within common.ts (in zAnyModelVariant) and never referenced externally, so knip flagged it as an unused export. Every sibling variant enum avoids this by being asserted in common.test-d.ts; add the missing Krea2VariantType assertion, which both uses the export and verifies the manual zod enum matches the generated S['Krea2VariantType'].
diffusers 0.39.0 is the first stable release containing Krea2Pipeline / Krea2Transformer2DModel (plus the Qwen-Image VAE and Qwen3-VL text encoder Krea-2 relies on). Replace the temporary git-main dependency with the pinned release and update the lockfile's diffusers entry (version, sdist, wheel, specifier) to the official PyPI 0.39.0 artifacts.
Close the remaining gaps against docs/new-model-integration for Krea-2. Metadata recall (§7): buildKrea2Graph now records the standalone VAE, Qwen3-VL encoder, and both conditioning enhancers (seed-variance + rebalance) to image metadata, and parsing.tsx adds the matching recall handlers (base-guarded to 'krea-2'), so a Krea-2 image's VAE/encoder/enhancer settings restore on recall — important for reproducing single-file/GGUF generations. Starter models: add Krea-2 Raw (Base variant, full pipeline), Krea-2 Turbo GGUF Q4_K_M / Q8_0 (vantagewithai/Krea-2-Turbo-GGUF) with Qwen-Image VAE + Qwen3-VL encoder dependencies, and a standalone Qwen3-VL 4B encoder (Qwen/Qwen3-VL-4B- Instruct). The VAE dependency reuses the existing diffusers qwen_image_vae starter; krea2_turbo gains its explicit Turbo variant. Tests: add config-probe unit tests for Krea-2 variant detection (name heuristic, _has_krea2_keys, GGUF/checkpoint/diffusers variant, default settings) and for the single-file Qwen3-VL encoder probe (visual-tower vs. text-only Qwen3). 31 tests.
# Conflicts: # invokeai/frontend/web/src/services/api/schema.ts
|
@Pfannkuchensack Here's a list of things that should probably to be addressed:
|
|
I fixed a few of the light effort issues:
|
…-model coverage Backend: - test_krea2_state_dict_utils.py: cover the pure loader transforms (prefix strip, native<->diffusers conversion, scaled-fp8 dequant, Qwen3-VL key remap) and _reject_incomplete_load parametrized over the single-file/GGUF/encoder call sites (rejects meta-tensor partial loads, names the missing params) - test_krea2_denoise.py: _prepare_cfg_scale (broadcast/length/type), the per-step cfg list vs. img2img-clip regression, _validate_inputs happy path, and _get_noise determinism/shape - test_starter_models.py: Krea-2 bundle registration, diffusers+GGUF+standalone membership, and GGUF entries declaring their VAE + Qwen3-VL dependencies Frontend: - modelSelected.test.ts: Krea-2 standalone-component defaulting (auto-select on GGUF, anima-VAE fallback, no-overwrite, diffusers clears overrides, clear on switch away) - parsing.test.tsx: Krea2 VAE/encoder + enhancer recall gating (parses only for krea-2, never clobbers otherwise) - buildKrea2Graph.test.ts: CFG negative-conditioning gating, enhancer node insertion/chaining, non-diffusers standalone-model assertion, metadata - ImageMetadataActions.test.tsx: require all eight Krea2 recall handlers Fix: exclude krea-2 from the generic VAEModel metadata handler (it has a dedicated Krea2VAEModel handler), matching the existing z-image/flux2 exclusions.
|
Got these in as well:
|
…4-review # Conflicts: # invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.test.tsx # invokeai/frontend/web/src/features/metadata/parsing.test.tsx # invokeai/frontend/web/src/features/metadata/parsing.tsx
JPPhoto
left a comment
There was a problem hiding this comment.
Current state. I'll work on these:
-
invokeai/backend/model_manager/load/model_loaders/krea2.py:409loads standalone directory-format Qwen3-VL encoders without copyingrope_parametersintorope_scaling. The bundled Diffusers and single-file paths explicitly perform this compatibility fix because the current Transformers implementation can otherwise crash. Apply the same normalization here. Add a standalone-directory loader test whererope_scalingis absent andrope_parametersis present. -
invokeai/backend/patches/lora_conversions/krea2_lora_conversion_utils.py:85discards a layer's stored.alphaand.dora_scalewhenever the layer uses PEFTlora_A/lora_Bkeys. This changes explicit LoRA scaling and turns DoRA weights into ordinary LoRA weights. Preserve both values during normalization. Add conversion tests asserting that per-layer alpha is retained and that a PEFT DoRA layer produces a DoRA patch with its magnitude tensor. -
invokeai/app/invocations/krea2_lora_loader.py:69andinvokeai/app/invocations/krea2_lora_loader.py:122assume the transformer and encoder LoRA lists are synchronized. If a LoRA is already attached to only one field, the single loader raises before repairing the other field, while the collection loader's globaladded_loraslist silently skips both. The result can be a transformer patch without its Qwen3-VL patch, or the reverse. Deduplicate independently per output field. Test both asymmetric starting states against both loader invocations. -
invokeai/backend/model_manager/configs/lora.py:904still requirestext_fusionortime_mod_projkeys to classify a LoRA as Krea-2. A legitimate transformer-block-only Krea-2 LoRA is indistinguishable from Qwen Image automatically, but it also cannot be installed through an explicit Krea-2 selection because the same probe rejects it. Provide a deliberate override path for ambiguous LoRAs. Test automatic ambiguity plus successful explicit Krea-2 installation fortransformer.transformer_blocks.*.lora_A/Bweights. -
invokeai/app/invocations/krea2_denoise.py:196enables CFG globally when any entry in a list exceeds1.0, andinvokeai/app/invocations/krea2_denoise.py:354then applies the CFG formula at every active step. A schedule such as[4.0, 1.0, 0.5]therefore applies unconditional blending at the1.0and0.5steps even though the invocation documents values<= 1as disabling CFG. Decide CFG per step, using the conditional prediction directly when that step's scale is<= 1. Test mixed schedules, including clipped schedules whose active window contains no value above1.0. -
invokeai/app/invocations/krea2_denoise.py:245rounds fractional denoising bounds independently. A valid narrow interval can round to identical indices, andinvokeai/app/invocations/krea2_denoise.py:278silently returns undenoised latents. Without input latents this returns raw noise for subsequent VAE decoding; with input latents it silently performs a no-op or noise blend. Reject intervals that produce zero effective steps, or explicitly define safe zero-step behavior. Test low step counts with bounds such as0.0to0.01. -
invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts:326andinvokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts:580treat a missing model-config cache entry as a non-Diffusers Krea-2 model. During startup, metadata recall, or direct dispatch, selecting a Diffusers model beforegetModelConfigsis populated can auto-select standalone components thatbuildKrea2Graph.tslater passes as stale overrides. The listener does not rerun when the cache arrives. Defer component changes until the format is known, or obtain the selected model's individual cached configuration. Test selection with an initially absent list cache followed by a Diffusers configuration response. -
invokeai/backend/model_manager/configs/factory.py:424now considers any rootconfig.jsonan unambiguous model marker and bypasses the general-purpose-directory file-count guard.config.jsonis generic, so a large application or data directory can now be hashed and registered as an unknown model instead of being rejected. Restrict the bypass to recognized model configuration content ormodel_index.json. Test a large non-model directory containing an unrelatedconfig.json. -
invokeai/backend/model_manager/configs/qwen3_vl_encoder.py:46classifies a standalone Qwen3-VL directory solely from its config class. It does not require encoder weights or tokenizer assets, althoughQwen3VLEncoderLoaderuseslocal_files_only=Truefor both. An incomplete directory can therefore install successfully, satisfy readiness, and fail only during generation. Validate the required weight index/files and tokenizer files for direct and nested layouts. Add rejection tests for config-only, weights-only, and tokenizer-only directories. -
The current focused tests do not exercise actual full Diffusers Krea-2 loading, standalone Qwen3-VL directory loading, single-file or GGUF model construction, prompt encoding with LoRA patches, CFG numerical behavior, or complete img2img/inpaint/outpaint execution. Add small fixture-based loader and invocation tests that reach model construction and forward boundaries rather than stopping at state-dict utilities and graph shape assertions.
-
The upgrade to
diffusers==0.39.0changes a shared dependency used by existing FLUX, Qwen Image, Z-Image, SD, and Anima loaders, but this PR contains no representative compatibility coverage for those paths. Add smoke coverage that constructs or loads one representative configuration from each affected family against the pinned version.
Summary
Integrate Krea-2 text-to-image per
NEW_MODEL_INTEGRATION.md: both Krea-2-Turbo (krea/Krea-2-Turbo, distilled) and Krea-2-Raw (krea/Krea-2-Raw, undistilled Base). Architecture:Krea2Transformer2DModel(single-stream MMDiT, ~12B) + Qwen3-VL text encoder (12-layer hidden-state tap → 4Dprompt_embeds) + reused Qwen-Image VAE (AutoencoderKLQwenImage) +FlowMatchEulerDiscreteScheduler.is_distilled=true): fixedmu=1.15, 8 steps, CFG off (cfg 1.0).is_distilled=false): resolution-awaremu, ~28 steps, CFG ~4.5. Variant is read from the pipelineis_distilledflag (single-file/GGUF fall back to a filename heuristic).Formats: full Diffusers pipeline, single-file checkpoint (incl. ComfyUI scaled fp8), and GGUF (Q2–Q8). Single-file/GGUF ship only the transformer, so a standalone Qwen-Image VAE + Qwen3-VL encoder are selected in the loader UI (enforced by readiness before enqueue). NVFP4 intentionally skipped (needs Blackwell FP4 kernels).
VRAM: fp8 layerwise-cast weight storage for the transformer (diffusers + single-file) and for the fp8 Qwen3-VL encoder (~8.9 GB bf16 → ~4.4 GB resident), keeping 1024² + LoRA within 24 GB.
diffusers dependency (resolved)
Krea2Transformer2DModel/Krea2Pipelinelanded in diffusers 0.39.0 (stable).pyproject.tomlnow pinsdiffusers[torch]==0.39.0anduv.lockis updated accordingly — the previous git-main blocker is gone.Notable extras
isMainModelWithoutUnetis now a proper type predicate covering all transformer-based loaders).QA Instructions
uv sync --extra cuda(pulls diffusers 0.39.0); confirmpython -c "from diffusers import Krea2Transformer2DModel, Krea2Pipeline".Krea-2-Turboand aKrea-2-Rawfolder. Confirm they probe asmain / diffusers / krea-2with variantkrea2_turbo/krea2_base, and the bundled encoder asqwen3_vl_encoder. On model select, Turbo defaults to 8 steps / cfg 1.0, Raw to ~28 steps / cfg 4.5.vantagewithai/Krea-2-Turbo-GGUF) + the standalone Qwen-Image VAE + Qwen3-VL encoder. Confirm the loader UI requires the VAE + encoder before enqueue and that generation succeeds. The fp8 encoder logsFP8 layerwise casting enabled for Qwen3-VL encoder.lora.lycoris.krea-2(not qwen-image), applies, and that 1024² + LoRA + fp8 does not OOM.Tests
_has_krea2_keys, GGUF/checkpoint/diffusers variant, default settings) and the single-file Qwen3-VL encoder probe (visual-tower vs. text-only Qwen3). Run:pytest tests/backend/model_manager/configs/test_krea2_main_config.py tests/backend/model_manager/configs/test_qwen3_vl_encoder_config.py.Merge Plan
diffusers blocker is resolved (pinned to stable 0.39.0). No DB schema changes.
paramsSlicegains Krea-2 fields with a corresponding migration.Out of scope / follow-ups
Checklist
What's Newcopy (if doing a release after this PR)