Fix QK-norm .scale/.weight key mismatch on Flux-compat GGUF loads (silent NaN/black image) - #470
Open
ayanoby wants to merge 1 commit into
Open
Fix QK-norm .scale/.weight key mismatch on Flux-compat GGUF loads (silent NaN/black image)#470ayanoby wants to merge 1 commit into
ayanoby wants to merge 1 commit into
Conversation
Some third-party GGUF quantizations remap a non-Flux architecture with QK-normalization (e.g. LongCat-Image-Edit, converted to Flux-compatible 'bfl_format' naming for loader reuse) onto double_blocks/single_blocks naming, but store the norm weights under a .scale suffix instead of the .weight suffix that comfy's RMSNorm module (torch.nn.RMSNorm) actually registers its parameter as. Since load_diffusion_model_state_dict uses strict=False, this mismatch never raises: it's logged as 'unet missing'/'unet unexpected' warnings, and every affected RMSNorm ends up with an uninitialized weight tensor. The model still runs, but NaNs propagate through the rest of the forward pass and the final image comes out solid black. Architecture detection in comfy/model_detection.py already treats .weight and .scale as interchangeable for these keys via any_suffix_in(), so this makes the actual state dict loading consistent with that. Verified against stduhpf/LongCat-Image-Edit-gguf (Q4_K_M): before this patch, loading produces the missing/unexpected warnings above and a black image; after, no warnings and normal output, using the same GGUF file (no need to fall back to the full bf16 checkpoint). Full writeup: https://github.com/ayanoby/comfyui-gguf-qknorm-fix
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.
Problem
Some third-party GGUF quantizations that remap a non-Flux architecture with QK-normalization onto Flux's
double_blocks/single_blockstensor naming (for free compatibility with this loader's existing Flux-family support) store the QK-norm weights under a.scalesuffix instead of the.weightsuffix that comfy'sRMSNorm(comfy/ops.py, a thin wrapper aroundtorch.nn.RMSNorm) actually registers its parameter as.Because
comfy.sd.load_diffusion_model_state_dictloads withstrict=False, this mismatch never raises — it just logsunet missing: [...norm.query_norm.weight, ...]/unet unexpected: [...norm.query_norm.scale, ...]warnings for every affected layer and silently drops ~140 tensors. Every affectedRMSNorm.weightis then left uninitialized. The model still runs to completion, but NaNs propagate through the rest of the forward pass and the decoded image comes out solid black — with no hard error anywhere to point at the cause.Worth noting:
comfy/model_detection.py's architecture detection already treats.weightand.scaleas interchangeable for exactly these keys viaany_suffix_in(state_dict_keys, key_prefix, 'double_blocks.0.img_attn.norm.key_norm.', ["weight", "scale"])— so the model gets correctly identified as Flux-family, it just doesn't get correctly loaded.Root cause
Confirmed with
stduhpf/LongCat-Image-Edit-gguf(Q4_K_M), a GGUF quantization ofmeituan-longcat/LongCat-Image-Editrepacked into Flux-compatible ("bfl_format") naming. LongCat's DiT has an extra QK-normalization step that Flux.1 doesn't have; everything else in the architecture lined up 1:1 with Flux's naming under this repack, but this one component used thescalenaming convention (following llama.cpp/sd.cpp's own norm-tensor naming) instead ofweight.This is a different issue from #383 (1D tensors incorrectly GGUF-quantized instead of stored as raw fp32/bf16, fixed in a57094a) — that one is about quantization/dtype, this one is purely a key-naming mismatch, and reproduces on current
main.Fix
Rename
.query_norm.scale/.key_norm.scale→.weightat load time, right aftersd_keyis resolved ingguf_sd_loader(), before the tensor is added to the state dict. Minimal, only touches keys with these two exact suffixes, shouldn't affect any GGUF file that doesn't have this specific naming quirk.Verification
Before: loading
stduhpf/LongCat-Image-Edit-gguf(Q4_K_M) produces the missing/unexpected warnings above during model load, thenRuntimeWarning: invalid value encountered in castduring sampling/decode, and a fully black output image.After: no missing/unexpected warnings, no cast warnings, normal (non-black) output — using the same 3.7GB GGUF file, no need to fall back to the full ~12.5GB bf16 checkpoint.
(Ruled out other causes first: an isolated
VAEEncode→VAEDecoderound-trip on the same VAE reproduced the input correctly, and the workflow graph validated cleanly viagraphToPrompt()— so the VAE and graph shape weren't the problem before landing on the UNET weights themselves.)Full write-up with logs: https://github.com/ayanoby/comfyui-gguf-qknorm-fix
Test plan
unet missing/unet unexpectedwarnings for QK-norm tensors disappear after the patch