Fix GGUF KeyError: None when offloading rebuilds a parameter - #14698
Open
EigenAx2Pi wants to merge 1 commit into
Open
Fix GGUF KeyError: None when offloading rebuilds a parameter#14698EigenAx2Pi wants to merge 1 commit into
KeyError: None when offloading rebuilds a parameter#14698EigenAx2Pi wants to merge 1 commit into
Conversation
`GGUFParameter.__new__` defaults `quant_type` to `None` and then looks it up in `GGML_QUANT_SIZES` without a guard. Offloading rebuilds parameters as `param_cls(new_value, requires_grad=old_value.requires_grad)` (see `accelerate.utils.set_module_tensor_to_device`) without forwarding `quant_type`, so every such rebuild raised `KeyError: None`. The practical effect was that `enable_sequential_cpu_offload()` could not be used with any GGUF-quantised transformer, and since `enable_model_cpu_offload()` moves the whole transformer onto the accelerator at once, no GGUF checkpoint larger than available VRAM could be run at all - the case GGUF quantisation exists to serve. Inherit `quant_type` from the tensor being wrapped when it is not passed, and raise an explicit error where there is nothing to inherit. That path already raised (`GGML_QUANT_SIZES[None]`), so nothing that works today starts failing; it only stops the failure from surfacing as a bare `KeyError: None` deep inside accelerate, which reads like a corrupt quant file rather than a library bug. Leaf-level group offloading is unaffected: it moves tensors in place (`param.data = param.data.to(...)`) and never re-enters `__new__`. Fixes huggingface#14691 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Fixes #14691.
What was broken
GGUFParameter.__new__defaultsquant_typetoNoneand then looks it up inGGML_QUANT_SIZESwithout a guard. Offloading rebuilds parameters asparam_cls(new_value, requires_grad=old_value.requires_grad)—accelerate.utils.set_module_tensor_to_device— without forwardingquant_type, so every rebuild raisedKeyError: None.The effect:
enable_sequential_cpu_offload()was unusable with any GGUF-quantised transformer. Sinceenable_model_cpu_offload()moves the whole transformer onto the accelerator at once, no GGUF checkpoint larger than available VRAM could be run at all — precisely the case GGUF quantisation exists to serve. On an 8 GB card that ruled out FLUX.1-schnell Q8_0 (12.7 GB) and Chroma1-HD Q8_0 (9.7 GB).The error was also misleading:
KeyError: Nonesurfacing from inside accelerate reads as a corrupt or unsupported model file. I wrote off three different models as broken quants before running a known-good file under sequential offload and finding it failed identically.The fix
Inherit
quant_typefrom the tensor being wrapped when it isn't passed, and raise an explicit error where there is nothing to inherit. That second path already raised (GGML_QUANT_SIZES[None]), so nothing that works today starts failing — it only stops the failure from surfacing as a bareKeyError: None.The original suggestion in the issue was to leave
quant_shape = Noneinstead of raising. @TANGBUDU correctly pointed out that this is wrong:FluxLoraLoaderMixin._calculate_module_shape()does readweight.quant_shapefor aGGUFParameter, anddequantize_gguf_tensor()only checks thatquant_typeexists, not that it's valid — so aNone-typed parameter would have relocated the failure rather than removed it. Credit to them for the catch; this PR takes the inheritance-only route because of it.Tests
Two backend-level regression tests plus one pipeline-level test:
maintest_rewrap_without_quant_type_inherits_itKeyError: Nonetest_set_module_tensor_to_device_preserves_quant_typeKeyError: Nonetest_untyped_construction_raisesKeyError)Verified in both directions locally (
pytest tests/quantization/gguf/test_gguf.py -k Rewrap), reverting theutils.pychange to confirm each one actually fails without it.One trap worth flagging for review: a naive
set_module_tensor_to_devicetest passes onmain, because a same-device move with novalueshort-circuits before the rebuild branch. It only reproduces on an actual device change or an explicitvalue=.test_pipeline_inference_sequential_cpu_offloadcloses the coverage gap @TANGBUDU identified —test_sequential_cpu_offloadcurrently exists only onTestTorchAo, and all four GGUF pipeline tests useenable_model_cpu_offload(). It follows the TorchAO precedent of asserting only that inference runs. It is nightly + big-accelerator, so I could not run it on an 8 GB card — CI will be its first real execution.On group offloading
@DN6 asked whether the issue persists under leaf-level group offloading. It does not, and the reason is mechanical: group offloading moves tensors in place (
param.data = param.data.to(...)inhooks/group_offloading.py), soGGUFParameter.__new__is never re-entered. Only paths that reconstruct the parameter are affected.Measured on FLUX.1-schnell Q4_K_S, 512×512, 1 step, RTX 5070 Laptop 8 GB (text encoders nulled so only the transformer offload path is exercised):
enable_sequential_cpu_offload(),mainKeyError: Noneleaf_levelleaf_level,use_stream=Trueenable_sequential_cpu_offload(), this PRSo group offloading is a real workaround today.
enable_sequential_cpu_offload()is still the API the low-VRAM docs point at first, though, and it is broken for every GGUF checkpoint.Env
diffusers
main, torch 2.11.0+cu128, accelerate 1.14.0, gguf 0.19.0, Python 3.12, Linux (WSL2), RTX 5070 Laptop 8 GB (sm_120).cc @DN6 @TANGBUDU — review very welcome, this is my first contribution here.