YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
About this repository
Just to be clear, this VAE currently has a lower result than the original VAE; you can read more details on HF. Also, this conversion was experimental and may not be working as it should.
This is a copy of iamkaikai/MiniMax-H3-Single-Frame-VAE-500K. All model weights, the training work and the results are by the original author; nothing here was retrained or improved. This mirror exists only to add two things:
- Loader nodes that read the author's original file, unmodified, inside ComfyUI.
- ComfyUI-ready
.safetensorsβ the same decoder, converted to the key layout ComfyUI expects, for anyone who prefers a single file over two inputs.
You do not need the converted files. With the
MiniMax-H3 Single-Frame VAE Loader node you can point straight at
minimax_h3_single_frame_decoder_500k.safetensors downloaded from the original repository β the node
pairs it with the official H3 VAE and does the key translation in memory. The converted files are a
convenience, not a requirement.
If you use diffusers, ignore this repo and use the original β the author's load_decoder.py is all you need.
Why a conversion is needed at all
The published checkpoint is decoder-only (585 tensors: decoder.* + post_quant_conv.*) and uses
diffusers naming. That is correct for the author's intended flow, which loads a complete H3 VAE and
replaces two submodules:
vae.decoder.load_state_dict(decoder, strict=True)
vae.post_quant_conv.load_state_dict(post_quant_conv, strict=True)
ComfyUI has no equivalent "replace one submodule" step β its VAELoader builds a VAE from the file.
Worse, detection in comfy/sd.py requires an encoder key:
elif "decoder.transformer_blocks.0.scale1" in sd and "encoder.down.5.block.0.conv1.weight" in sd:
A decoder-only file fails that test, so ComfyUI logs No VAE weights detected, VAE not initalized,
leaves first_stage_model = None and falls back to Stable Diffusion geometry β 4 latent channels and
8x upscale, against H3's 24 channels and 32x. The result is blocky garbage. It is not a bug in
ComfyUI and not a defect in the checkpoint; the two simply have different contracts.
What the conversion does
Renames diffusers β ComfyUI, and fills in what a decoder-only file cannot carry (encoder.*,
quant_conv, latents_mean/std, mask_token) from the official H3 VAE. That graft is exactly what
the author's own script does, and it is sound because the author froze the encoder during training β
the official encoder is the one this decoder was trained against.
Two of the transforms are not derivable from the names, and getting either wrong produces wrong output with no loading error. Both were settled by measuring correlation against the official VAE:
| transform | evidence |
|---|---|
to_qkv is interleaved per head (32 heads, 3, 64 dim_head), not stacked [q;k;v] |
matches qkv.view(B, S, -1, 3*dim_head) in comfy/ldm/minimax/vae.py; correlation 0.998 |
ff.w1 halves are swapped relative to diffusers (ComfyUI reads gate first) |
correlation: straight β0.013, swapped +2.00 |
Validation. Same latent, raw decoder call on both sides β the author's diffusers path with the original file, against the converted file in ComfyUI: 72.92 dB PSNR, i.e. fp16 noise. Key check: 0 missing, 0 unexpected.
The tile size matters more than anything else
ComfyUI's MiniMaxH3VideoVAE tiles internally with a constructor default of tile_size=256, and
the stock VAELoader does not expose that field. 256 is the worst value. Measured on a 1056x640 image,
where "seam" is edge energy at tile boundaries relative to the image average (1.0 = invisible):
| tile_size | PSNR | seam |
|---|---|---|
| 256 (ComfyUI default) | 22.17 dB | 1.49 β visible |
| 512 | 26.00 dB | 0.91 β invisible |
| 768 | 23.27 dB | 1.71 |
| 1024 | 21.35 dB | 2.42 |
512 is not arbitrary: the author trained 475k of the 500k images at β€512px (250k@256 + 150k@384 + 75k@512), so 512-wide tiles keep every piece inside the regime that has training mass. Larger tiles push the decoder past what it saw; smaller ones multiply seams.
Turning tiling off is also wrong above ~768px β the patches stop agreeing and a 32px grid appears (block ratio 2.94 at 1024, 3.45 at 1536, against ~1.5 with tiling).
So: tiling on, tile_size 512.
Using it in ComfyUI
The nodes are part of ComfyUI-BFSNodes (v1.24.0+):
Option A β the author's original file, nothing converted (recommended if you want to stay on the upstream weights):
MiniMax-H3 Single-Frame VAE Loader
base_vaeβ the official MiniMax-H3 VAE (the complete one, with encoder)single_frame_decoderβminimax_h3_single_frame_decoder_500k.safetensors, straight from the original repositorytiling: True,tile_size: 512The node detects the diffusers naming, converts it in memory and grafts the missing encoder from
base_vae. Nothing is written to disk and the original file is untouched β its SHA-256 still matches, so the author'sload_decoder.pykeeps working with it.
Option B β a converted file from this repo (one input instead of two):
MiniMax-H3 VAE Loader / tile control
vae_nameβminimax_h3_single_frame_500k_comfy.safetensorstiling: True,tile_size: 512
Both paths produce bit-identical output (120 dB).
Then plain VAEEncode / VAEDecode in both cases.
fp16 and fp32 also decode bit-identically here β ComfyUI runs this VAE in fp16 either way, so the
fp16 file is the sensible one and the fp32 is provided only to remove the doubt.
Limitations (unchanged from the original)
Everything the original card says still applies β this is the same decoder. In particular it is
single-frame only: decoding a clip slice by slice, frame-to-frame flicker measured 2.6x the
original, and each latent slice decodes 4 frames of which only the last is valid (hence
decoded[:, :, -1] in the author's example). Fine texture is smoothed, and dense thin lines can moirΓ©.
Credit and license
All credit to iamkaikai. Same license as the original repository.