Skip to content

feat(wan): load ComfyUI int8 convrot checkpoints - #10185

Merged
lstein merged 6 commits into
invoke-ai:mainfrom
Pfannkuchensack:feat/wan-int8-convrot
Nov 8, 2026
Merged

lstein merged 6 commits into
invoke-ai:mainfrom
Pfannkuchensack:feat/wan-int8-convrot

Conversation

@Pfannkuchensack

@Pfannkuchensack Pfannkuchensack commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Summary

ComfyUI int8_tensorwise (convrot) single-file Wan 2.2 transformers now load and generate. Before, they installed and then failed at load with "this loader has no int8 branch".

  • WanCheckpointModel keeps marked layers as Int8ConvrotLinear: both A14B experts and TI2V-5B. condition_embedder, which reads its dtype from a parameter, is decoded dense at load.
  • LoRA on an int8 expert now uses sidecar patching. The old check compared only format == GGUFQuantized, so an int8 expert would have been patched directly into its int8 buffers. The decision is now made per loaded expert with requires_sidecar_patching, so mixed pairs work.
  • Files the loader cannot decode are refused at install (_raise_for_undecodable_int8_weights), including int4 builds and files marked only {"convrot": true}. Animate and Wan 2.1 stay unrecognized.
  • The refusal text in reject_unmarked_int8_weights no longer claims a marked file has no marker.

File sources: Comfy-Org only publishes Wan Animate in int8, which Invoke does not run. T2V, I2V and TI2V-5B builds are community uploads. Note that FartingBackwards/wan2.2-t2v-14B-int8-convrot ships the same file twice: its low_noise file is byte-identical to high_noise (same LFS hash). It loads, but runs the high expert for both phases.

Related Issues / Discussions

Follow-up: Wan's RAM use sits above max_cache_ram_gb. The main cause for int8 is the expert swap, which holds both experts in memory; casting formats (fp16, scaled fp8) also double while loading, and Diffusers folders pin their fp32 source. These affect every Wan format, not only int8, and are unchanged here. #10192 fixes them. Measured below.

QA Instructions

Local, RTX 4090, Windows, clean scratch root, use_cache=false, cache emptied between models. T2V at 832x480, 17 frames; TI2V-5B at 832x480, 25 frames.

Decode against dense weights (sampled layers per file, float64): cosine 0.99996, relative error 0.9 %; without derotation cosine 0.06. This also confirms high and low map to transformer and transformer_2.

One forward per expert through the real loader, int8 against bf16, same random input: cosine 0.9985-0.9996, relative error 3-5.5 %, 400 of 400 layers int8.

Time per clip after warmup (blocks of 1 warmup + 3 clips; TI2V-5B dense measured before and after int8):

bf16 int8 int8 + int8_compute
T2V A14B, 8 steps 65 s 45 s 30 s
T2V A14B, 20 steps 128 s 96 s not measured
TI2V-5B, 12 steps 12.5 s 13.5 s 8.2 s

int8 saves memory. A14B is faster here because it fits in 24 GB and bf16 does not; where both fit (TI2V-5B), plain int8 is about 8 % slower.

Clip PSNR against bf16, same seeds, mean over frames:

seed 101 seed 202 seed 303
TI2V-5B int8, 12 steps 30.6 dB 30.2 dB 21.4 dB
T2V int8, 8 steps 19.9 dB 24.4 dB 21.5 dB
T2V int8, 20 steps 19.1 dB 18.5 dB 16.3 dB
T2V Q8_0 GGUF, 20 steps (control) 21.4 dB 25.8 dB 18.3 dB

The A14B numbers are low for Q8_0 too: sampling diverges in motion and position while subject and composition stay. int8 sits 2-7 dB below Q8_0. At 20 steps both clips are clean; at 8 steps (undersampled, artifacts on both sides) int8 showed more of them in seed 101. Two dense runs with the same seed are bit-identical, so the difference comes from the weights. int8 is not pixel-faithful to bf16 on A14B.

Lightning LoRA (4 steps) on the int8 pair: 20 s per clip, clean and sharp, confirming the sidecar path on real weights. PSNR against dense + LoRA is 13.5-18.4 dB; the two apply the LoRA differently (sidecar against merged).

Peak RAM (working set) with both experts and UMT5 cached: int8 41 GB. With max_cache_ram_gb: 16, int8 still runs at about 58-70 s per clip with a 30 GB peak, because the swap holds both experts; see the follow-up above. Windows commit reads about 20 GB higher in both cases (64 and 46 GB) because VRAM counts toward it. An earlier version of this description gave commit figures as process memory.

Focused suites after rebase pass (about 7100 tests): tests/backend/model_manager/load, configs, tests/backend/quantization, pid, qwen3_5, tests/app/invocations.

Not run: I2V-A14B on a GPU (key mapping and unit tests only; it differs in the dense patch_embedding), int8_compute together with LoRA, AMD, Linux, the docs build, the family E2E.

Review

Resolved: the install guard mirrors what the loader discards, so all-in-one files with a bundled int8 encoder still install; refusal wording; docs for int4 (R), DoRA and int8_compute; added regression coverage for prefixed header-only files, sidecar drop and asymmetric expert pairs.

Remaining limits:

  • DoRA LoRAs do not run on an int8 transformer and fail when applied, with a message naming bitsandbytes. Documented; no pre-flight refusal yet.
  • The install guard reads markers, not tensors: a bad group size or a blockwise scale passes install and is refused at load with a clear message.

Compatibility / Rollout

No migration. Generated API files change only by a docstring. _ExpertSwapper loses its high_is_quantized and low_is_quantized parameters; it has no callers outside the two Wan denoise invocations.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Meaningful regression coverage added / updated where needed; obsolete tests/code removed
  • Persisted-state and API changes include required migrations / compatibility validation
  • Relevant performance/efficiency opportunities considered; material claims have evidence
  • Material review findings resolved and relevant checks rerun
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

Keep marked layers in int8 for A14B experts and TI2V-5B and refuse undecodable files at install.
Choose LoRA sidecar patching per loaded expert, so int8 experts are never patched directly.
@github-actions github-actions Bot added python PRs that change python files invocations PRs that change invocations backend PRs that change backend files python-tests PRs that change python tests docs PRs that change docs labels Nov 6, 2026

@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.

Approving at f147243. I reviewed the install guard, the int8 load path and the per-expert LoRA sidecar decision, and the focused tests pass (loader, int8 keys, expert swapper, checkpoint config: 163 tests). I also broke the key map, skip patterns, header read and RAM reservation one at a time, and in each case the tests failed. Three low-severity notes, none blocking:

  1. int8 branch skips _strip_quantization_metadata (load/model_loaders/wan.py:475). drop_unconsumed_quantization_sidecars removes only .comfy_quant and input scales. A stray top-level scaled_fp8 key, or a .weight_scale beside a dense weight, therefore reaches Wan's fatal unexpected-key check. The error then blames an Animate/S2V variant: RuntimeError ... (modules: scaled_fp8). This is a Wan variant with extra conditioning branches. The fp8 branch drops the same keys silently. Suggest also stripping STRAY_METADATA_KEYS and weight-scale keys whose layer has neither an int8 marker nor an fp8 weight.
  2. A mixed int8 + fp8_scaled file passes install and fails at load. The guard (configs/main.py, _raise_for_undecodable_int8_weights) only looks at int8 .weight tensors, so it does not see fp8 weights with scales. reject_foreign_quantization_scales then refuses the file at first render, with a clear message. No known Wan build does this, and the other int8 configs share the gap.
  3. The install guard doesn't validate the marker layout. A marker with convrot_groupsize: 512 (not a power of 4) or 0 installs and is refused at load. This is the shared helper's behaviour and is already listed under "Remaining limits".

Not checked: GPU runs, real released checkpoints, I2V-A14B, the family E2E.

A top-level scaled_fp8 flag or a weight scale beside a dense weight no longer fails the load as an
unsupported Wan variant; the int8 branch now drops them like the fp8 branch does.
@Pfannkuchensack

Copy link
Copy Markdown
Member Author

Thanks for the review, and especially for the mutation pass.

  1. Stray fp8 metadata on the int8 path: fixed in b4f44bc. After install_int8_convrot_layers, the int8 branch now drops every scale or marker key whose module isn't one of the surviving int8 layers. That covers scaled_fp8 and a .weight_scale beside a dense weight, matching what the fp8 branch drops. The drop runs after the install on purpose: reject_foreign_quantization_scales must still see a scale on an fp8 weight and refuse it, and the existing mixed int8 + fp8 test still passes. New test: test_stray_fp8_metadata_is_dropped_as_on_the_fp8_path. It failed before the fix with the Animate/S2V message you quoted.
  2. Mixed int8 + fp8_scaled passes install: agreed. Since the guard is shared by every int8 config, I'd rather tighten it in one place for all of them in a follow-up than special-case Wan here. Until then the file is refused at first load with a clear message, and no known Wan build mixes the two.
  3. Marker layout not validated at install: same shared helper, already listed under "Remaining limits". It belongs in the same follow-up as 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lstein
lstein enabled auto-merge November 8, 2026 13:47
@lstein
lstein added this pull request to the merge queue Nov 8, 2026
Merged via the queue into invoke-ai:main with commit e4eeea1 Nov 8, 2026
21 checks passed
lstein added a commit to Pfannkuchensack/InvokeAI that referenced this pull request Nov 8, 2026
Resolved the import conflict in model_loaders/wan.py with invoke-ai#10185 by keeping
both sides' imports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pfannkuchensack added a commit to Pfannkuchensack/InvokeAI that referenced this pull request Nov 9, 2026
Since invoke-ai#10185 a marked int8 file takes the int8 branch and stays int8, and an unmarked one is refused,
so no int8 weight reaches the fold any more. The int8 branch reserves before its split and has its
own reservation test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend PRs that change backend files docs PRs that change docs invocations PRs that change invocations python PRs that change python files python-tests PRs that change python tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants