fix(ui): recall the right VAE and text encoder per model base - #9489
fix(ui): recall the right VAE and text encoder per model base#9489Pfannkuchensack wants to merge 16 commits into
Conversation
Several bases write their standalone VAE / text encoder into the shared `metadata.vae` and `metadata.qwen3_encoder` fields but recall them into their own params slot. The generic handlers did not account for that: - `VAEModel` excluded z-image, flux2 and krea-2 but not anima, so an Anima image rendered a duplicate VAE row and additionally wrote its VAE into the shared `params.vae` slot, which no Anima graph reads. - `Qwen3EncoderModel` had no base gate at all while dispatching hard into the Z-Image slots, so Anima and FLUX.2 Klein images pushed their encoder into `zImageQwen3EncoderModel` and cleared `zImageQwen3SourceModel` on the way. - The Anima handlers gated only on the currently selected base, never on the image's own `metadata.model.base`, so a Krea-2 image recalled while Anima was selected wrote its VAE into the Anima slot. Replace the growing `base !== 'x' && ...` chain with a documented `BASES_WITH_DEDICATED_VAE_HANDLER` set, rename `Qwen3EncoderModel` to `ZImageQwen3EncoderModel` and gate it on z-image, and harden both Anima handlers with `assertMetadataModelBase` (plus a base assert on the VAE, which is base-identified - the encoder is variant-identified, so it deliberately gets none). The same slot mismatch existed for FLUX.1, whose VAE lives in `params.fluxVAE`: its recall silently landed in `params.vae` and had no effect on generation. Add a dedicated `Flux1VAEModel` handler for it. Finally, `AnimaVAEModel`, `AnimaQwen3EncoderModel`, `Flux1VAEModel` and the three Z-Image handlers were missing from IMAGE_METADATA_ACTION_HANDLERS, so those parameters had neither a row nor a per-parameter recall button. List them, and add a guard test that diffs the UI list against the handler registry with an explicit allowlist, so the list cannot drift behind again.
Workflows only carry recallable parameters if the graph builds the metadata itself. `metadata_linked.py` offers two ways to do that: chaining Metadata Item Linked nodes, or a denoise node that writes its own metadata - but the latter existed only for SD, FLUX.1 and Z-Image. Add `anima_denoise_meta`, mirroring `z_image_denoise_meta`, with two Anima specifics: - The CFG value is recorded as `cfg_scale`, matching both the Anima graph builder and the UI's recall handler. Z-Image writes `guidance`, which for Anima would land in the FLUX guidance param instead. - The VAE and Qwen3 encoder are exposed as optional inputs. Anima loads both standalone, so unlike SD/FLUX they cannot be derived from the transformer field, and without them a recalled Anima image would be missing exactly the two components that are hardest to pick by hand. Unconnected inputs write nothing rather than a null the recall handlers would fail to resolve.
JPPhoto
left a comment
There was a problem hiding this comment.
Merge blockers:
-
invokeai/frontend/web/src/features/metadata/parsing.tsx:1546-1552:Flux1VAEModellacks image-provenance gating. A Z-Image image using a FLUX VAE now exposes this newly registered row and recalls it intoparams.fluxVAE. Test: current baseflux, metadata model basez-image, VAE baseflux; parsing must reject. -
invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx:77-78: Newly exposed Z-Image VAE and Qwen3 handlers check only selected base, notmetadata.model.base. Foreign Flux/Anima metadata can populatezImageVaeModelorzImageQwen3EncoderModeland clear the source slot. Test: current basez-image, parse shared fields from non-Z-Image metadata; both must reject. -
invokeai/frontend/web/src/features/metadata/parsing.tsx:1855-1856: Rejects FLUX VAEs, althoughinvokeai/app/invocations/anima_model_loader.py:56-68explicitly permits them and the new metadata node records any connected VAE atinvokeai/app/invocations/metadata_linked.py:812-864. Valid Anima workflows using a FLUX VAE cannot recall that VAE. Test: Anima metadata withmodel.base="anima"andvae.base="flux"; Anima VAE parsing must succeed.
Suggestions:
-
Instead of selected-base-only checks, require metadata model provenance for every shared-field handler.
-
Consider accepting all VAE bases supported by Anima, or reject those inputs in the metadata node.
Notes:
-
The first two are real, concrete UI bugs. They affect per-parameter recall when viewing one model's image while another model base is selected. The PR newly adds those handlers to the UI, making the paths reachable.
Recall allis safer becauseMainModelruns first. These share one fix: requiremetadata.model.baseprovenance checks in the Flux/Z-Image handlers. -
The third is also real, but narrower. Anima's backend node explicitly permits a FLUX VAE fallback, while the new Anima parser rejects it. A valid Anima workflow can therefore record a VAE that cannot be recalled.
VAE and text-encoder handlers that read a shared metadata field (`vae`, `qwen3_encoder`, `qwen3_source`) checked only the currently selected base, not where the image came from. Viewing one model's image while another base was selected could write a foreign model into the live slot, and in the Z-Image case silently clear the user's Qwen3 source selection. All six affected handlers now assert `metadata.model.base` in addition to the selected base. Also widen the Anima VAE slot to FLUX VAEs. The Anima model loader carries no `ui_model_base` on its VAE input and both `anima_l2i` and `anima_i2l` branch explicitly on `FluxAutoEncoder`, so a workflow-built Anima image may legitimately record one. This needs a separate `isAnimaCompatibleVAEModelConfig`: `isAnimaVAEModelConfig` also feeds Krea-2's VAE pool and must stay base-driven.
JPPhoto
left a comment
There was a problem hiding this comment.
Fix:
invokeai/frontend/web/src/services/api/types.ts:330andinvokeai/frontend/web/src/features/metadata/parsing.tsx:1870reject valid 16-channel Wan VAEs, although Anima accepts them. Workflows usingVAE_Checkpoint_Wan_Configcan record one throughanima_denoise_meta, but recall silently leaves the stale VAE. Effect: wrong VAE or generation failure. Likelihood: Medium. Recovery: manually reselect the VAE in the workflow. Test: add Wan-16 acceptance and Wan-48 rejection cases toinvokeai/frontend/web/src/features/metadata/parsing.test.tsxandtypes.test.ts.
Suggestions:
- Consider centralizing Anima VAE compatibility around backend geometry: Anima, Flux, and Wan with
latent_channels=16; reject Wan-48.
The Anima model loader takes any VAE the backend can decode with — an Anima-base (Wan/QwenImage) VAE, a FLUX VAE, or a plain Wan VAE in the 16-channel A14B geometry. The frontend gated on base alone, so a workflow-built Anima image recording a Wan VAE could not recall it and recall silently left the stale VAE in place. Gate on the backend geometry instead: isAnimaCompatibleVAEModelConfig now also accepts Wan VAEs with latent_channels === 16, and rejects the 48-channel Wan2.2-VAE (TI2V-5B), which is the same AutoencoderKLWan class but a different latent space. Submodels stay out — a main model's bundled VAE carries no latent_channels, so its geometry is unverifiable. Since latent_channels is lost in ModelIdentifierField, AnimaVAEModel now gates on the full model config: parseModelConfig is split out of parseModelIdentifier, which becomes a thin wrapper over it. The resolution order (key, then hash, then name/base) and its fallthrough behaviour are unchanged. isAnimaVAEModelConfig stays base-driven — Krea-2 draws its own VAE pool from it and must not be offered FLUX or Wan VAEs.
JPPhoto
left a comment
There was a problem hiding this comment.
Close! This should be fixed:
- parsing.tsx: Metadata rows are parsed only on
[metadata, handler, store]; selected base changes do not trigger reparse, and recall does not revalidate. After opening Anima metadata while another model is selected, recalling MainModel leaves Anima VAE/encoder rows hidden. Conversely, switching away after a row appears leaves a stale button that can mutate an inactive slot. Likelihood: normal workflow. Test: switch base after mountingImageMetadataActions; assert rows update and stale clicks do nothing.
Suggestions:
- Subscribe to selected base in the metadata datum hook and reparse on changes; also validate the base again inside each recall callback.
Addresses review 4987208275 on invoke-ai#9489. Metadata handlers gate on the currently selected base, which they read imperatively via `selectBase(store.getState())` inside `parse` — they have to, because parsing also happens outside React, in recall-all and in hotkeys. The datum hooks depended only on `[metadata, handler, store]`, so a row carried the verdict of whichever base was selected when it mounted, and the metadata viewer stays open across model switches. Opening Anima metadata under another model and then recalling MainModel left the Anima VAE and encoder rows hidden; switching away after a row appeared left a live recall button pointed at a slot no longer in play. The three hooks now subscribe to the selected base and reparse when it changes. That reparse is asynchronous, so a click can still land while a stale row is on screen. Recalls therefore re-run the handler's own gate at click time: `recallIfStillValid` parses again and dispatches only if the gate still admits the metadata. It recalls the value the row displays, not the reparsed one — a collection row owns a single item, while `parse` returns the whole set. Fixing the hooks alone would have changed nothing: `SingleMetadataParsed` and `CollectionMetadataParsed` called `handler.recall` / `handler.recallOne` directly and resolved the store themselves, and the hooks' `recall`, `recallAll` and `recallOne` had no callers anywhere. The row components now take the hook's validating callback instead, which is the only recall path left outside `parsing.tsx`. Tested through `recallIfStillValid`, which is exported as a plain function for that purpose: it recalls under a matching base, does nothing after the base moves on, does nothing when provenance fails, and passes the row's own value through. The reparse half is not covered — the project has no testing-library or DOM environment and does not do UI tests, so it would need a new dev dependency to assert.
The FLUX.1 and Z-Image VAE slots were auto-filled from the wider flux+flux2 pool while their pickers and recall handlers accept FLUX.1 only, and every VAE recall handler rejected the main-model VAE submodels its picker offers - so the VAE row silently disappeared instead of recalling. - default `params.fluxVAE` and `params.zImageVaeModel` from `isFlux1VAEModelConfig` - share `parseVAEModelIdentifier` across the FLUX.1 / FLUX.2 / Z-Image / Anima handlers, each gating on its own picker's guard - prefer a native Anima VAE over the FLUX/Wan fallbacks when defaulting the slot - call type guards with one argument in `buildModelsSelector`; `Array#filter` was passing the index as `excludeSubmodels` - surface a toast when a revalidated recall is skipped - collapse the three metadata datum hooks into one and export the verdict function so the base-change reparse is testable
JPPhoto
left a comment
There was a problem hiding this comment.
Another thing to fix:
invokeai/frontend/web/src/features/metadata/parsing.tsx:1908: Anima recall checks onlytype === 'qwen3_encoder; it does not requirevariant === 'qwen3_06b'like the Anima picker. Anima metadata containing a 4B/8B encoder therefore poisons state; next generation expects 1024-wide embeddings and can fail. Effect: invalid Anima recall. Likelihood: rare. Test: reject non-qwen3_06bconfigs. Recovery: clear and select the 0.6B encoder.
Suggestions:
- Instead of only checking encoder type, resolve the full config and apply
isAnimaQwen3EncoderModelConfig.
selectFluxVAEModels lost its last consumer when the FLUX.1 and Z-Image VAE defaults moved to selectFlux1VAEModels. Removing the selector also leaves isFluxVAEModelConfig unused, which knip flags in turn - both go.
Qwen3 encoders are split by `variant`, not by base: Anima uses the 0.6B (hidden_size 1024), Z-Image and Klein the 4B/8B (2560/4096). A ModelIdentifierField carries no variant, so all three recall handlers accepted each other's encoders and left the slot with one whose embeddings the transformer cannot consume. Resolve the full config and apply the guard each slot's picker is built from - isAnimaQwen3EncoderModelConfig for Anima, isQwen3EncoderModelConfig for Z-Image and Klein.
JPPhoto
left a comment
There was a problem hiding this comment.
-
invokeai/frontend/web/src/features/parameters/components/Advanced/ParamAnimaModelSelect.tsx:27-58: Anima now accepts Flux and Wan VAEs, butmodelsLoaded.ts:75-100,196-218never validatesparams.animaVaeModel. Removing the selected VAE leaves stale state and later Anima generation can fail. Effect: stale model selection and generation failure. Likelihood: plausible after model removal/refetch. Recovery: manually clear/reselect VAE. Test: refresh model list without selected VAE; assert Anima VAE state clears. -
docs/src/content/docs/features/Workflows/community-nodes.mdx:421-441: new Anima metadata node is absent from Metadata Linked Nodes documentation. Effect: users cannot discover its Anima-specific metadata inputs. Likelihood: normal for documentation users. Recovery: inspect node registry/source. Test: build docs and verifyAnima Denoise + Metadataappears.
Edge case:
invokeai/frontend/web/src/features/metadata/parsing.tsx:1522-1530, 1897-1902: with no active main model, generic VAE recall dispatches to inactiveparams.vae; dedicated Flux/Z-Image/Flux.2/Krea-2/Anima slots stay unchanged. Effect: individual VAE recall silently fails. Likelihood: plausible during startup or with no main model. Recovery: select or recall main model first. Test: click VAE metadata action withparams.model = null; assert dedicated slot update.
Summary
Model-specific VAEs and text encoders were recalled into the wrong Redux slot, or not recallable at all, depending on the model base. This fixes the gating, wires the missing handlers into the recall UI, and adds the Anima denoise node needed to produce that metadata from a workflow.
Several bases keep their standalone components in dedicated params slots (
params.fluxVAE,params.animaVaeModel, …) but record them in the sharedmetadata.vae/metadata.qwen3_encoderfields. Whether a handler may fire therefore depends on two axes: where the image came from (metadata.model.base) and which base is currently selected (which decides which slot is live). Neither axis was checked consistently.What was broken
params.vaeslotVAEModelexcludedz-image,flux2,krea-2— but notanimazImageQwen3SourceModelQwen3EncoderModelhad no base gate at all, yet dispatched hard into the Z-Image slotsparams.fluxVAE, the generic handler writesparams.vae— no handler ever dispatchedfluxVAESelectedIMAGE_METADATA_ACTION_HANDLERSmetadata_linked.pyhad a self-recording denoise node for SD, FLUX.1 and Z-Image, but not for AnimaThe first three were partly self-healing — the
modelSelectedlistener clears foreign slots on the next model switch — but the panel stayed wrong and a user's Z-Image encoder selection was silently dropped.Changes
features/metadata/parsing.tsxBASES_WITH_DEDICATED_VAE_HANDLERset replaces thebase !== 'x' && …chain inVAEModel, documenting per base which slot and handler own it.qwen-imageandwanare deliberately absent — they writeqwen_image_vae/wan_vae_modeland never collide.Flux1VAEModelhandler dispatchingfluxVAESelected.Qwen3EncoderModel→ZImageQwen3EncoderModel, gated onbase === 'z-image'. A variant-based check would not work here:isQwen3EncoderModelConfigis defined asvariant !== 'qwen3_06b', which Klein encoders satisfy too — and the slot choice is a property of the main model, not of the encoder.assertMetadataModelBase. The VAE additionally assertsparsed.base === 'anima'(meaningful, sinceisAnimaVAEModelConfigis base-driven); the encoder deliberately gets no base assert, since Anima encoders are identified byvariant— with a comment saying so, to stop a future cleanup from "fixing" it.ImageMetadataViewer/ImageMetadataActions.tsx— listFlux1VAEModel,ZImageVAEModel,ZImageQwen3EncoderModel,ZImageQwen3SourceModel,AnimaVAEModel,AnimaQwen3EncoderModel. ListingFlux1VAEModelis mandatory, not cosmetic: without it, addingfluxto the exclusion set would remove the FLUX.1 VAE row outright.invocations/metadata_linked.py— newanima_denoise_metanode ("Denoise - Anima + Metadata"), mirroringz_image_denoise_metawith two Anima specifics: the CFG value is recorded ascfg_scale(Z-Image writesguidance, which for Anima would land in the FLUX guidance param), and the standalone VAE / Qwen3 encoder are optional inputs, since Anima cannot derive them from the transformer field. Unconnected inputs write nothing rather than a null the recall handlers would fail to resolve.schema.tsregenerated.Tests
parsing.test.tsx: gating forAnimaVAEModel(incl. rejecting Krea-2 provenance and metadata with nomodelat all),AnimaQwen3EncoderModel(incl. pinning that any encoder base parses),ZImageQwen3EncoderModel,Flux1VAEModel; the genericVAEModelmatrix extended byfluxandanima.ImageMetadataActions.test.tsx: atoContaincheck for the six newly listed handlers, plus a guard test that diffs the whole registry against the UI list. Its allowlist is split into "hidden by design" (CreatedBy,ImageSize) and a commented snapshot of pre-existing gaps (HiDiffusion, Wan, Qwen-Image sub-models, Gemini/OpenAI/Seedream, Z-Image seed variance) — meant to shrink, not grow.test_denoise_noise_inputs.py: the seed-selection case matching the FLUX / Z-Image tests, plus one that pins the metadata keys against the recall contract (cfg_scaleset,guidanceabsent, model / VAE / encoder recorded) and one that checks unconnected component inputs are omitted.pnpm lint,pnpm lint:tsc, the full frontend suite (1809 tests / 144 files),ruff, and the touched pytest modules all pass.Out of scope
T5EncoderModelhas the same missing base gate and writes into the FLUX.1 T5 slot. Left alone deliberately.*_denoise_metacounterpart either; only Anima is added here.model(withbase: "anima") in the metadata for the Anima VAE / encoder rows to recall. The new node always writes it; a hand-built Metadata Item Linked chain that omits it will not recall those two fields.Manual verification