Repository navigation
feat(model-manager): load ComfyUI single-file T5-XXL encoders - #9799
Open
Pfannkuchensack wants to merge 3 commits into
Open
Pfannkuchensack wants to merge 3 commits into
Pfannkuchensack wants to merge 3 commits into
Conversation
Identify t5xxl_fp16 / t5xxl_fp8_e4m3fn(_scaled) as T5 encoder checkpoints; refuse UMT5, non-XXL and int8/nvfp4/mxfp8 builds at install. fp8 builds keep their Linear weights in fp8 on capable GPUs (~4.9 instead of ~9.5 GB), always dequantized per forward. Share T5 config inference and the feed-forward dtype fix with the GGUF loader.
6 of 7 tasks
lstein
added a commit
that referenced
this pull request
Oct 15, 2026
…9800) ## Summary ComfyUI distributes the CLIP text towers FLUX.1 and SD 3 use as bare safetensors files, `clip_l` and `clip_g`. InvokeAI loaded CLIP only as a Diffusers folder, so these installed as unknown models. They now install as CLIP Embed models (variants L and G). Along the way, two existing SD 3 bugs with separately selected CLIP encoders are fixed. **Identification** New configs: `CLIPEmbed_Checkpoint_L_Config` / `_G_Config` (tags `clip_embed.checkpoint.any.large` / `.gigantic`). The variant comes from width and depth, matched against vendored configs. The record discriminator now includes the variant for checkpoint CLIP records as it already did for Diffusers ones. Files that are not recognized: - full CLIP models with a vision tower - other text-tower sizes Files that are refused at install, with the reason shown: - Long-CLIP (a longer position table) - quantized weights - a CLIP-G without `text_projection`, which SD 3 needs **Loading** A new loader, `CLIPSingleFileLoader`, works like this: - **CLIP-L** is built as `CLIPTextModel`, which FLUX.1 requires. The `text_model.` prefix is stripped, because transformers ≥5.6 flattened that class. - **CLIP-G** is built as `CLIPTextModelWithProjection`. - **Tokenizer:** `openai/clip-vit-large-patch14`'s tokenizer (MIT) and the two text configs are vendored in `invokeai/backend/clip/`, gzip-compressed like the Qwen tokenizers. It gives the same ids as the original. **SD 3 fixes** - **A separately selected CLIP-G** was requested as `TextEncoder2`, which no CLIP loader provides, so every such generation failed. It is now requested as `TextEncoder`. - **The pooled output** was `output[0]`. That is `last_hidden_state` for a plain `CLIPTextModel`, the class a separately selected CLIP-L loads as. It is now `text_embeds` for `CLIPTextModelWithProjection`, else `pooler_output`. The second is exact for CLIP-L, because SD 3's CLIP-L projection is the identity (max abs diff 0.0 in the SD 3 Medium Diffusers weights). - **A Diffusers CLIP-G folder** now loads with its projection instead of dropping it. A folder without one is refused instead of getting a randomly initialised projection. **FLUX.1 takes CLIP-L only** Installing ComfyUI's `text_encoders` folder now brings in a CLIP-G as well, so the FLUX CLIP selection is restricted to CLIP-L: - the loader node field (`ui_model_variant`) - the webv2 Generate slot - the Upscale auto-pick and picker A CLIP-G wired into a workflow gets a clear error instead of an `AssertionError`. ## Related Issues / Discussions Closes #9736 together with the T5 single-file PR (#9799). ## QA Instructions **Automated (Windows, CPU):** - `uv run --no-sync pytest tests/model_identification tests/backend/architectures/test_variants.py tests/backend/model_manager/configs tests/backend/model_manager/load tests/app/invocations tests/backend/util/test_bundled_tokenizer.py -n 4`: 4336 passed, 158 skipped, 6 xfailed. - New tests: - Identification through the whole config factory from header-only files written from the captured ComfyUI headers. Covers L, G, extras, vision tower, OpenCLIP-H size, Long-CLIP, a G without projection, quantized weights, and the stored-record round trip per variant. - Single-file loader with tiny models. - Diffusers loader class per variant, including the missing-projection refusal. - SD 3 pooled output with tiny real `CLIPTextModel` / `CLIPTextModelWithProjection`. - The SD 3 model loader's submodel choice. - `pnpm -C invokeai/frontend/webv2 lint` passes (format, oxlint, tsc, architecture). - `pnpm -C invokeai/frontend/webv2 test`: 10068 passed, 3 failed. The three are in untouched files (`queue/ui/modelCacheStore.test.ts`, `workbench/image-map/*`) and fail only under a German Windows locale, which formats `1,500` as `1.500`. - New webv2 tests cover the FLUX Generate slot and the Upscale auto-pick/sync rejecting a CLIP-G; both were mutation-checked. - `ruff` clean. `make frontend-openapi frontend-typegen` regenerated; `pnpm -C invokeai/frontend/api lint` passes. **Manual (RTX 4090, CUDA, Windows):** - **SD3.5 Medium, 1024², 28 steps, same seed, each run twice without the invocation cache:** separately selected `clip_l`, `clip_g`, or both produce bit-identical images to the pipeline's own encoders (PSNR ∞, max diff 0). The log confirms the single files loaded as `CLIPTextModel` / `CLIPTextModelWithProjection`. Before this PR the same selection failed for CLIP-G. - **FLUX.1 dev (int8), 768², 20 steps:** `clip_l` against the bf16 Diffusers CLIP-L folder gives 33.2 dB (fp16 vs bf16 weights). Reproducible. - **webv2 in the browser:** - The Model Manager lists `clip_l` / `clip_g` as Checkpoint with their variant. - The SD 3 CLIP L / CLIP G pickers offer the matching file. - A generation with both started from the UI succeeded. - The FLUX CLIP Embed picker lists only CLIP-L models. Screenshots: _attach Model Manager detail (clip_g), SD 3 components with clip_l/clip_g, FLUX CLIP picker._ ## Review Material findings resolved: - Long-CLIP-L installed as CLIP-L and failed at generation; it is now refused at install. - FLUX.1 could be given a CLIP-G, which this PR made easy to install; it is now restricted to CLIP-L in backend and webv2. - A Diffusers CLIP-G without a projection would have been initialised randomly; it is now refused. - The SD 3 pooled-output change and the Diffusers CLIP-G class had no failing-on-regression tests; both are now added. Remaining limitations: - The workflow editor still ignores `ui_model_variant`, as before. The FLUX text encoder now names the problem instead. - Only CUDA was exercised on hardware. ## Compatibility / Rollout - **Discriminator:** it now includes `variant` for checkpoint CLIP records. No such records existed before, and Diffusers CLIP records parse exactly as before. - **Generated contracts:** `openapi.json` / `schema.ts` regenerated, additively (new config classes, `ui_model_variant` on the FLUX loader's CLIP field). - **Packaging:** the new vendored files are covered by a package-data entry for `invokeai.backend.clip`. - **Stored webv2 selections:** they carry the model's variant. An Upscale selection that lacks one is dropped and re-picked. ## 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)_
…xt-encoders # Conflicts: # docs/src/content/docs/Users Guide/03.Models/03.model-format-support.mdx # docs/src/content/docs/Users Guide/03.Models/Local Models/sd-3.mdx # invokeai/frontend/api/schema.ts
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ComfyUI distributes T5-XXL as bare safetensors files:
t5xxl_fp16,t5xxl_fp8_e4m3fnandt5xxl_fp8_e4m3fn_scaled. InvokeAI loaded T5 only as a Diffusers folder, GGUF, SDNQ or bnb int8, so these files installed as unknown models. They now install as T5 encoders and can be selected for FLUX.1 and SD 3.Identification
A new config,
T5Encoder_Checkpoint_Config(tagt5_encoder.checkpoint.any), reads only the safetensors header. It requires:Files that are not recognized:
Files that are refused at install, with the reason shown:
Loading
A new loader,
T5EncoderSingleFileLoader, works like this:fp8_computeon. Quantizing T5's activations measurably hurts (see QA).reserve_for_loadbefore anything is widened. The tokenizer submodel is sized 0, so loading it no longer evicts models to make room for the whole file.Shared T5 code
infer_t5_encoder_configandmake_t5_feed_forward_safemove from the GGUF T5 loader intoinvokeai/backend/t5/t5_encoder.py. The feed-forward workaround now also keeps activations out of a float8wo. GGUF behaviour is unchanged.FLUX text encoder
The FLUX text encoder accepts the new format. LayerPatcher already routes LoRA patches on float8 layers through sidecars.
Related Issues / Discussions
Part of #9736 (the T5 half). CLIP-L / CLIP-G single files are #9800.
QA Instructions
Automated (Windows, CPU):
uv run --no-sync pytest tests/model_identification tests/backend/architectures/test_variants.py tests/backend/model_manager/configs tests/backend/t5 tests/backend/model_manager/load tests/app/invocations -n 4: 4335 passed, 158 skipped, 6 xfailed.ruff check/ruff format --checkclean.make frontend-openapi frontend-typegenregenerated;pnpm -C invokeai/frontend/api lint testpasses.Manual (RTX 4090, CUDA, Windows):
Installs: all three files install as T5 Encoder / Checkpoint. They show 4.67 GB VRAM for the fp8 builds in the model cache log (
kept 168 weight(s) in fp8).Prompt embeddings: compared against the bf16 Diffusers T5 folder. Three prompts at 512 tokens; cosine per valid token:
t5xxl_fp16t5xxl_fp8_e4m3fn_scaledt5xxl_fp8_e4m3fnThe raw file stores its norms in fp8 too, so this loss is the file's own; it is documented. The bundled tokenizer gives the same ids as the folder's.
fp8 matmul on the scaled build: worst/mean token cosine 0.459 / 0.984, against 0.874 / 0.996 with storage only. This is why kept T5 weights never take the fp8 matmul. With
fp8_computeon, the result is now identical to storage only.FLUX.1 dev (int8), 768², 20 steps, same seed, each run twice without the invocation cache: reproducible. PSNR vs the folder encoder: fp16 28.1 dB, scaled 20.2 dB, raw 19.1 dB. All images are coherent.
SD3.5 Medium, 1024², 28 steps, same seed: reproducible. PSNR vs the pipeline's own fp32 T5: fp16 31.0 dB, scaled 26.7 dB. The bf16 T5 folder reaches 26.3 dB.
Encode working memory: +0.57 GiB in every case. Dequantizing per forward needs no extra estimate.
Review
Material findings resolved:
fp8_computeon, kept T5 Linears ran the fp8 matmul; it was measured and is now disabled for this loader.encoder.embed_tokenswith a scale would have lost the scale; the key and any scale are now renamed together.Remaining limitations:
Compatibility / Rollout
openapi.json/schema.tsregenerated.Checklist
What's Newcopy (if doing a release after this PR)