Repository navigation
feat(krea2): add edit mode with reference images for ai-toolkit edit LoRAs - #9916
Conversation
…LoRAs The Krea-2 prompt reads up to three reference images through the Qwen3-VL vision tower, loaded as its own cache entry only when images are given, and the denoise conditions on their latents at timestep 0 as ai-toolkit trains edit LoRAs. A reference K/V-cache mode serves LoRAs trained with isolated references.
The mocked context now serves conditioning with no reference images, which the denoise node checks.
The mmproj install refusal and the GGUF encoder description now say that edit reference images need an encoder with vision weights.
…erences # Conflicts: # tests/app/invocations/test_krea2_denoise.py
lstein
left a comment
There was a problem hiding this comment.
Reviewed at 791d76f. No blockers; the items below are follow-ups, roughly most important first.
1. Vision-tower lock doesn't respect the encoder's working-memory reservation — invokeai/app/invocations/text_encoder/krea2_text_encoder.py:175
The encoder is locked with working_mem_bytes (:142), then the tower is locked with the default reserve (device_working_mem_gb). The tower's ~0.8 GB of weights can land in the headroom reserved for the encoder's forward, so on a card with little spare VRAM an edit-mode encode can OOM, or the tower partially loads. Passing the same working_mem_bytes to the tower lock, or adding the tower's size to the encoder's reservation, would fix it.
2. Edit-mode prompts are not length-capped — invokeai/backend/krea2/text_encoding.py, tokenize_krea2_prompt_with_images
The text-only path truncates the body to KREA2_BODY_MAX_LENGTH; the image path calls the processor without truncation. Encoder working memory grows with T², so a very long prompt asks for a very large reservation, and together with (1) it's the realistic way to hit the OOM. Is the missing cap intentional, i.e. does ai-toolkit not truncate here?
3. Negative-prompt reference count is checked against the unclipped CFG schedule — invokeai/app/invocations/krea2/krea2_denoise.py:406-409
_validate_reference_counts checks self.cfg_scale, but do_cfg (:547-553) uses the schedule clipped to [start_idx:end_idx]. Example: cfg_scale=[4,4,1,1,1,1,1,1], denoising_start=0.5, and a negative prompt encoded without references. The node refuses even though the negative prompt never runs. It's only over-strict and never produces wrong output.
4. webv1 zSubModelType is missing 'vision_encoder' — invokeai/frontend/webv1/src/features/nodes/types/common.ts:180
common.test-d.ts:51 asserts equality with the shared schema's SubModelType, which now includes vision_encoder, so the webv1 typecheck fails. CI doesn't run webv1, so this wouldn't show there. It's a one-line addition after 'processor', as was done for Qwen-Image-2.1.
5. Reference checks only compare counts
_validate_reference_countscomparesnum_reference_imagesonly. A prompt encoded with [depth, pose] and a denoise given [pose, depth], or two other images, is accepted silently, although the docs and error text tell users to keep the same order. Either say in the docs that the order isn't checked, or record identities inKrea2ConditioningInfo.- Several unmasked positive prompts, each encoded with the same references, put the "Picture N" vision tokens into the sequence twice, which the LoRA wasn't trained on. Nothing refuses this or tests it. Consider refusing more than one conditioning when references are connected.
6. Smaller items
- A reference the processor rejects (an extreme aspect ratio) fails only after both the tower and the ~8 GB language tower have been loaded. Tokenizing with the processor before loading the encoder would fail fast.
- Only
reference_kv_cacheis refused for subclasses that override_install_attention_processors(krea2_denoise.py:386-394). In joint mode, a subclass whose processors are sized for[text | target]will get[text | target | refs]. - Docs (
krea-2.mdx:184) say each reference is scaled to about 384×384, butprepare_vlm_referenceonly downscales; smaller images keep their size and the processor raises them to its pixel floor. - Test gap: switching the vision-side resize in
prepare_vlm_referencetoantialias=Falsepasses every test, because the tests check output size only, not pixel values.
Checked and found sound
- Lending the tower: detached in a
finally, both entries locked for the whole window, no double counting of the cache size, LoRA patches applied before attach and removed after detach. - K/V cache: equivalent to the joint pass with the isolating mask; sharing it between cond and uncond is correct; cleared on error and on exit; built after LoRA patching.
- Compatibility: old pickled
Krea2ConditioningInfoloads with a count of 0, existing workflows and the non-referenceqwen_image_i2lpath are unchanged, andopenapi.jsonregenerates identically. - Tests: the new and changed tests pass on CPU (580 passed, 2 skipped). Most of the mutations I tried against the key lines were caught; the antialias change above was the one that got through.
Not checked: GPU runs, real Krea-2 Diffusers checkpoints, the VRAM figures, schema.ts regeneration.
… refuse ambiguous references The vision tower locks with the encoder's working memory, the processor tokenizes before either tower loads, and the negative prompt's reference count is checked only when its CFG steps run. Prompt collections and custom attention processors are refused with reference latents; docs state what is checked and how references are sized.
|
Thanks for the review. Addressed in d9dfcd6:
Checks: the Krea-2, Qwen3-VL tower and node-type tests pass on CPU (1528 passed), ruff is clean, and |
lstein
left a comment
There was a problem hiding this comment.
Re-reviewed d9dfcd67d6. Every item from the earlier review is addressed or explained, and I found no regressions. Approving.
Verified
- Vision-tower lock: the tower now locks with the encoder's
working_mem_bytes. I agree that this is right and that adding the tower's size to the encoder's reservation would count it twice. - Uncapped edit prompts: understood. They follow ai-toolkit, and the reservation uses the real token count.
- Negative-prompt check: validation and
_run_diffusionnow share_step_windowand_active_cfg_scale. The window comes fromself.steps, which is the countbuild_sigmasuses. Withoutdenoising_startordenoising_endthe behavior is unchanged. - Other checks:
- Prompt collections are refused when reference latents are connected.
- A subclass that installs its own attention processors is refused for any reference latents.
- The processor tokenizes before either tower loads, and the text-only path is unchanged.
- Docs: the sizing text and the note that only the count is checked match the code.
- Checks run:
- ruff check and format on the changed files: clean.
- Krea-2 denoise, text-encoder and backend tests: 259 passed, 2 skipped on CPU.
- Mutations: tests caught each of these four reverted on its own: the tower reservation, the clipped CFG window, the refusal of several prompts and the refusal of subclasses.
Note
- webv1's
zSubModelTypestill lacksvision_encoder, so the webv1 typecheck fails locally. CI doesn't run it, so this is fine as a known gap.
Not checked: GPU runs, real Krea-2 checkpoints and the VRAM figures.
#9923 is stacked on this PR, so this one should merge first.
## Summary Krea-2 edit LoRAs (ai-toolkit pose/depth "ControlNets") need reference images. #9916 added them to the backend and the workflow editor; this PR brings them to Generate and Canvas. - Krea-2 reference cards get a **Mode** switch: **Style** (the existing training-free Style Reference, with its strength) or **Edit** (an edit reference for an edit LoRA). Up to three edit references; they are sent in card order to the prompt (Qwen3-VL vision tower) and as reference latents to the denoise. - A new Krea-2 reference starts as Style when it is the only one, and as Edit when it joins others (multi-select, drop of several images, "Use as Reference Image", recall, switching back from another model). Existing cards never change mode. - **Reference K/V cache** under Advanced, for edit LoRAs trained with isolated references (e.g. `thedeoxen/Krea-2-pose-controlnet`); the Edit hint points to it. Recorded in metadata and recalled. - Invoke is blocked with a reason for combinations the backend refuses: more than one active style reference, style and edit references together, edit references with a GGUF Qwen3-VL encoder (no vision weights). Edit references with Canvas regional guidance are refused when the canvas graph is compiled. A style reference at strength 0 counts as off everywhere. ## Related Issues / Discussions Depends on #9916 (backend). Follows the request for Krea-2 pose/depth ControlNet support. ## QA Instructions RTX 4090, Krea-2 Turbo int8 + `qwen3vl_4b_fp8_scaled`, `thedeoxen/Krea-2-pose-controlnet` at 1.0, 10 steps, CFG 1, seed 42: - Generate panel: DWPose skeleton as reference, Mode Edit, Reference K/V cache on. The queued graph carries the image in `krea2_text_encoder.reference_images`, one `qwen_image_i2l` with `reference: true` collected into `reference_latents`, and `reference_kv_cache: true`. The image is **bit-identical** to the API-level run from #9916 with the same settings, and follows the pose. - A second reference added beside an edit reference starts as Edit; Style + Edit blocks Invoke ("Switch the style reference to Edit, or turn off one kind."); two style references block it ("Turn off all but one, or switch all of them to Edit."). - Recall Metadata from the result restores the edit reference and the K/V switch. - Not run live: Canvas generation with edit references, CFG > 1 (Raw), a GGUF encoder (none installed); covered by unit tests. Checks: `pnpm lint` (format, oxlint, tsc, architecture:check); `pnpm test` on generation, image-actions, hints, workbench state, canvas widgets and invocation (2355 passed); `pnpm test:browser` for the reference images and Advanced fields; `pytest tests/backend/architectures tests/app/invocations/test_frontend_graph_node_types.py` (capabilities fixture and node-type snapshot); ruff clean. ## Review Resolved: several images added at once all became Style and blocked Invoke (now typed as a set by one owner, also for recall and model switches); a zero-strength style reference blocked edit references although the graph skips it; the validation messages now name the fix that keeps all references; the Edit hint points to the K/V switch; docs say "enabled references, in card order". Remaining: the Canvas regional-guidance conflict surfaces at Invoke time (like the other canvas-only checks), not as a disabled Invoke button; a LoRA file does not say whether it needs the K/V cache. ## Compatibility / Rollout Additive: new reference type `krea2_edit_reference_image`, new optional setting `krea2ReferenceKvCache` (default off) and metadata key `krea2_reference_kv_cache`; old documents, presets and image metadata load unchanged. An older build drops edit references it does not know, as with earlier new reference types. Krea-2's `max_reference_images` facet goes from 1 to 3 (capabilities fixture regenerated). No migration. ## Checklist - [x] _The PR has a short but descriptive title, suitable for a changelog_ - [x] _Meaningful regression coverage added / updated where needed; obsolete tests/code removed_ - [x] _Persisted-state and API changes include required migrations / compatibility validation_ - [x] _Relevant performance/efficiency opportunities considered; material claims have evidence_ - [x] _Material review findings resolved and relevant checks rerun_ - [x] _Documentation added / updated (if applicable)_ - [ ] _Updated `What's New` copy (if doing a release after this PR)_ <!-- codesmith:footer --> --- <a href="https://app.blacksmith.sh/invoke-ai/codesmith/InvokeAI/pr/9923?autoLogin=true&ref=codesmith_pr_footer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v3.svg"><img alt="View with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"></picture></a> <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1794942179&installation_model_id=441795&pr_number=9923&ref=codesmith_pr_footer&repository=invoke-ai%2FInvokeAI&return_to=https%3A%2F%2Fgithub.com%2Finvoke-ai%2FInvokeAI%2Fpull%2F9923&signature=be320b620ce076cd0f5d5888b2813a19d6d5b567bfcbc620f63634eff27a4d14"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a> <sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you need.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer -->
Summary
The Krea-2 "ControlNets" people ask for (pose, depth) are ai-toolkit edit LoRAs: they only work when the prompt is encoded together with reference images by the Qwen3-VL vision tower and the reference latents ride along in the transformer at timestep 0. Invoke dropped the vision tower and had no reference path, so these LoRAs did nothing useful.
This adds Krea-2's edit mode to the backend and the workflow editor:
reference_images(up to 3). They are read by the Qwen3-VL vision tower, which is now its own model-cache entry (SubModelType.VisionEncoder) loaded only when images are given and lent to the text encoder for that forward. Text-only encoding is unchanged and loads nothing new. Supported encoders: the Krea-2 Diffusers model's own encoder and Qwen3-VL single files/folders that include the vision weights; GGUF encoders are refused with a clear message.reference_latents(up to 3): reference tokens appended after the target with their own position ids, modulated at t=0, output limited to the target tokens. A reference count that differs from the prompt's is refused; combining references with Style Reference or regional prompts is refused.reference_kv_cache, off by default) for LoRAs trained with ai-toolkit'skv_cacheoption (references attend only to each other): the reference K/V are computed once per run at t=0 and appended in every pass. It is also faster.referencemode with ai-toolkit's Krea-2 reference sizing (about 1 MP, sides rounded to 16, bilinear, RGB).Related Issues / Discussions
Follows the request for Krea-2 pose/depth ControlNet support; the Generate/Canvas UI follows in a separate PR.
QA Instructions
RTX 4090, Krea-2 Turbo (int8 single file) +
qwen3vl_4b_fp8_scaled, 10 steps, CFG 1, 1024², LoRA weight 1.0:cicalooo/krea2_unidepth_depth, depth map as reference, joint mode, 3 seedsthedeoxen/Krea-2-pose-controlnet, DWPose skeleton, joint modeTiming per step: 1 reference 2.8 s joint vs 1.5 s cached; 3 references 6.5 s joint vs ~2.0 s cached. Peak VRAM 9.7 GB with 3 references.
Numerical parity on real weights: the transformer forward with references is
torch.equalto the Ostris/ComfyUI formulation (Turbo Q4_K_M); the encoder istorch.equalto ai-toolkit'sencode_krea_promptlayout when both get the same pixels (CPU vs GPU bicubic resizing alone moves bf16 image tokens by a similar amount as ±2.4e-5 pixel noise does). On CPU: the split block equals the stock block, the no-reference forward equals the stock forward, the cached path equals a joint pass with ai-toolkit's isolating mask, lending the tower equals a model that never lost it.env -u HF_ENDPOINT uv run --no-sync pytest -n 4 tests/backend/krea2 tests/backend/model_manager tests/backend/qwen3_vl tests/backend/architectures tests/model_identification tests/app: 11714 passed; the one failure (test_model_cache_vram_accounting) measured real free VRAM while another process was using the GPU and passes on its own. Ruff clean.Review
Resolved: the rebalance and seed-variance nodes now carry the reference count (they would have reset it and blocked every edit graph); clear errors for encoders without vision weights and for references the processor rejects; the negative prompt's count is checked only when CFG is above 1; the K/V-cache attention-score estimate prices the real (non-square) peak; docs and field descriptions corrected. Decided: references drop alpha (as in training) instead of compositing over white; the tower loads before the encoder so a GGUF encoder is refused before the language tower is read. Remaining: the Krea-2 facet's
max_reference_imagesstays at the single style reference until the UI PR; the encoder working-memory estimate reuses Qwen3-VL-8B measurements; a LoRA file does not say whether it needs the K/V cache.Compatibility / Rollout
New
SubModelType.VisionEncodervalue and new optional fields onkrea2_text_encoder(1.2.0),krea2_denoise(1.4.0) andqwen_image_i2l(1.2.0); existing workflows keep their behaviour.Krea2ConditioningInfogainsnum_reference_images(default 0, so stored conditionings load unchanged).openapi.json/schema.tsregenerated. No database migration.Checklist
What's Newcopy (if doing a release after this PR)Need help on this PR? Tag
@codesmith-botwith what you need.