fix(gguf): honor ComfyUI's comfy.gguf.orig_shape metadata - #9564
fix(gguf): honor ComfyUI's comfy.gguf.orig_shape metadata#9564Pfannkuchensack wants to merge 4 commits into
Conversation
ComfyUI's GGUF converter can only quantize 2-D tensors, so it reshapes any
tensor the quantizer rejects and records the native shape under a
`comfy.gguf.orig_shape.<tensor name>` KV entry. `gguf_sd_loader` ignored those
entries and used the stored shape, so such a checkpoint failed at load with a
size mismatch.
Concretely, Krea-2's `first.weight` is (6144, 64) but is stored as (1536, 256),
which produced:
size mismatch for img_in.weight: copying a param with shape
torch.Size([1536, 256]) from checkpoint, the shape in current model is
torch.Size([6144, 64])
The loader now reads the metadata and uses the declared native shape, rejecting
an entry whose element count doesn't match the stored tensor and warning on a
malformed one. This is architecture-agnostic, not a Krea-2 special case.
Verified end-to-end: the affected checkpoint from the issue installs, loads and
generates a coherent image.
Closes invoke-ai#9537
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JPPhoto
left a comment
There was a problem hiding this comment.
Other findings/issues (perhaps only the first is a real concern):
-
invokeai/backend/quantization/gguf/loaders.py:78-92andinvokeai/backend/quantization/gguf/ggml_tensor.py:186-188: unsupported fallback qtypes such asIQ4_NLreceive correctedtensor_shape, but NumPy dequantization keeps stored shape. Verified logical(8, 4)versus dequantized(1, 32). Effect: load may pass, then inference fails. Likelihood: plausible edge. Recovery: reconvert using supported qtype. Test: add fallback-qtype fixture asserting dequantized shape. -
invokeai/backend/quantization/gguf/loaders.py:55-63:int(v)truncates non-integral dimensions, whileOverflowErrorfrominfis uncaught. Malformed metadata is therefore silently misread or aborts import despite the documented warning-and-ignore behavior. Effect: wrong shape or failed model import. Likelihood: rare malformed GGUF. Recovery: repair or redownload. Test: parameterize[2.5, 8]and[inf].
Suggestions:
-
Consider validating finite integral dimensions and catching
OverflowError. -
Consider reshaping NumPy fallback output to
self.tensor_shape.
Follow-up to the comfy.gguf.orig_shape support, addressing review feedback. Qtypes without a torch dequantize kernel fall back to gguf's numpy implementation, which infers the output shape from the stored data. That matched the logical shape only as long as both were identical -- with a ComfyUI-reshaped tensor the fallback returned the stored shape, so loading succeeded and inference then failed. Reshape the fallback output to tensor_shape, as the torch path already does via oshape. Dimension values from comfy.gguf.orig_shape.* were passed straight to int(), which truncates non-integral values (int(2.5) == 2) and raises an uncaught OverflowError on inf. Both contradict the documented warn-and-ignore behaviour. Reject anything that is not a finite, integral, positive number, and verify that the metadata is an array at all. Tests cover the fallback qtype shape and the malformed-metadata cases.
JPPhoto
left a comment
There was a problem hiding this comment.
Approved!
This is something to consider in the future if it becomes an issue:
invokeai/backend/quantization/gguf/loaders.py:53-90:_coerce_dimaccepts arbitrarily large positive integers, thentorch.Size(dims)raisesRuntimeError: Overflow when unpacking longfor valid GGUF UINT64 metadata such as[2**63], bypassing warn-and-ignore handling. Effect: malformed checkpoints abort import. Likelihood: rare malformed or hostile GGUF. Recovery: repair or redownload. Test: feed_read_comfy_orig_shapesa field returning[2**63]and assert warning plus omission.
Suggestions:
- Consider rejecting dimensions above PyTorch's representable range before constructing
torch.Size.
Summary
Kind of change: fix (backend, GGUF model loading).
Why: Certain ComfyUI-produced GGUF checkpoints install fine but fail at generation with a weight shape mismatch, e.g. for a Krea-2 Q4_K_M checkpoint:
ComfyUI's GGUF converter can only quantize 2-D tensors, so it reshapes any tensor whose native shape the quantizer rejects and records the native shape in a
comfy.gguf.orig_shape.<tensor name>KV entry.gguf_sd_loaderignored those entries and used the stored shape instead, soload_state_dictsaw a wrongly-shaped tensor.The affected file's header shows exactly this — note that both shapes have the same element count, so it is purely a reshape:
This is not Krea-2 specific; other Krea-2 GGUFs load only because their conversion did not have to reshape this tensor.
How:
gguf_sd_loadernow reads thecomfy.gguf.orig_shape.*metadata and uses the declared native shape for theGGMLTensor. An entry whose element count does not match the stored tensor raises with an explicit message rather than producing a silently wrong view; a malformed entry is logged and ignored.Related Issues / Discussions
Closes #9537
QA Instructions
Unit tests:
They cover all three paths: metadata present (shape is applied), metadata absent (unchanged behaviour), metadata inconsistent (raises).
Manual, with the checkpoint from #9537 (Krea-2 SAT-IOR v2 Q4_K_M GGUF from CivitAI):
main / krea-2 / gguf_quantized.load_state_dictreports no missing or unexpected keys, and generation completes with a coherent image (verified at 512x512, 4 steps).Also worth a quick regression check that an unaffected GGUF (e.g. an existing FLUX or Krea-2 GGUF without
comfy.gguf.orig_shapekeys) still loads — those files take the unchanged code path.Merge Plan
Nothing special — self-contained backend change, no schema or frontend impact.
Checklist
What's Newcopy (if doing a release after this PR)