Add Nunchaku Lite single-file quantization#14100
Conversation
sayakpaul
left a comment
There was a problem hiding this comment.
Thanks for getting started! Just did a first pass and left high-level reviews.
| @@ -0,0 +1,161 @@ | |||
| import json | |||
There was a problem hiding this comment.
For tests, WDYT of adding a mixin to https://github.com/huggingface/diffusers/blob/main/tests/models/testing_utils/quantization.py and then extending a popular model like Flux to use that mixin?
There was a problem hiding this comment.
Yes, let's do it that way
|
I have just implemented the native loading feature, which now can load by
import torch
from diffusers import ErnieImagePipeline
pipe = ErnieImagePipeline.from_pretrained(
"rootonchair/ERNIE-Image-Turbo-nunchaku-lite-int4",
torch_dtype=torch.bfloat16,
).to("cuda")
image = pipe(
prompt="A modern red armchair in a quiet studio, soft window light, realistic product photography",
height=1024,
width=1024,
num_inference_steps=8,
guidance_scale=1.0,
use_pe=False,
).images[0]
image.save("ernie-image-turbo-nunchaku-lite-int4.png")Quantization config now change to: If we agree to use this schema, I will remove the old metadata/from_single_file approach |
sayakpaul
left a comment
There was a problem hiding this comment.
Looking good. I think we can remove all metadata related code?
sayakpaul
left a comment
There was a problem hiding this comment.
No rush but let us know once you would like another round of review.
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
|
@sayakpaul I think this is ready for the next review |
There was a problem hiding this comment.
🤗 Serge says:
This adds a new nunchaku_lite quantization backend (config, quantizer, runtime linear layers backed by the HF kernels package, docs, and tests). The overall structure follows existing quantizer integrations, but there are a few blocking issues.
Security
_get_ops()insrc/diffusers/quantizers/nunchaku/utils.pycallsget_kernel(..., trust_remote_code=True)against a personal user repo (rootonchair/nunchaku-lite-kernels). This silently enables remote code execution for anyone who loads a Nunchaku Lite checkpoint, with no user opt-in. No other kernel usage in the repo does this (ggufandattention_dispatchcallget_kernelwithouttrust_remote_code). This needs to be removed or made an explicit user decision, and the kernel repo should ideally live under an org namespace with pinned revisions.
Description vs. diff mismatch
- The PR description claims "The loader reads safetensors metadata during
from_single_fileso Nunchaku Lite checkpoints can use their embedded runtime manifest" — but there are no changes tosingle_file_model.pyor any loader in this diff. Correspondingly, themetadataparameter ofNunchakuLiteQuantizer._process_model_before_weight_loadingis never populated by any call site and is dead code.
Correctness
NunchakuLiteTesterMixin._test_quantized_layersis tautological: it setsexpected_quantized_layers = num_quantized_layersand then asserts they're equal, so the count check can never fail. It should compare against the number of targets in the quantization config (like the base mixin compares against linear-layer count).- The docs and the
NunchakuLiteQuantizationConfigdocstring repeatedly referencemodel.json, but Diffusers model configs (and the test in this PR) useconfig.json. As written, users following the doc will put the quantization config in a file Diffusers never reads.
Style
nunchaku_quantizer.pyhas trailing whitespace (line 64) —make stylewas apparently not run.- New source files are missing the Apache license header used across
src/diffusers. NunchakuLiteQuantizationConfigis appended to_import_structurevia a standalone statement instead of being listed in the dict literal like every other unconditional export.import itertoolsis buried insidecheck_strict_state_dict_match; move it to module top level.SVDQW4A4Linearre-validatesprecision/group_sizethatNunchakuLiteQuantizationConfig.post_initalready enforces — per repo guidelines, drop the duplicated defensive checks (the forward path hard-codes group sizes 16/64 anyway, so thegroup_sizeargument is effectively unused at runtime).
serge v0.1.0 · model: claude-fable-5 · 10 LLM turns · 22 tool calls · 390.5s · 437374 in / 30106 out tokens
| if _ops is None: | ||
| from kernels import get_kernel | ||
|
|
||
| _ops = get_kernel(_HF_KERNEL_REPO, version=_HF_KERNEL_VERSION, trust_remote_code=True).ops |
There was a problem hiding this comment.
Security: trust_remote_code=True silently enables execution of arbitrary remote code from a personal user repo whenever a Nunchaku Lite checkpoint is loaded — the user never opts in. Neither of the existing get_kernel call sites in this repo (quantizers/gguf/utils.py, models/attention_dispatch.py) passes trust_remote_code. Please drop it (publish the kernel as a standard prebuilt kernels repo that doesn't require remote code), or at minimum surface this as an explicit user-facing opt-in. Hosting under a personal namespace (rootonchair/...) rather than an org also makes this a supply-chain risk for everyone using the backend.
| self, | ||
| model: "ModelMixin", | ||
| state_dict: dict[str, Any] | None = None, | ||
| metadata: dict[str, str] | None = None, |
There was a problem hiding this comment.
metadata is never passed by any caller — neither modeling_utils.py nor single_file_model.py forwards safetensors metadata to preprocess_model. The PR description claims the loader "reads safetensors metadata during from_single_file", but no loader changes exist in this diff. Per the repo guidelines (no unused parameters "for API consistency"), remove this parameter.
| self._verify_if_layer_quantized(name, module, config_kwargs) | ||
| num_quantized_layers += 1 | ||
|
|
||
| expected_quantized_layers = num_quantized_layers |
There was a problem hiding this comment.
This assertion is tautological: expected_quantized_layers is set to num_quantized_layers, so the num_quantized_layers == expected_quantized_layers check below can never fail, and num_fp32_modules is always 0. The only effective check left is num_quantized_layers > 0. Compute the expected count from the config instead, e.g. the total number of entries in svdq_w4a4["targets"] + awq_w4a16["targets"], so the test actually verifies all targets were replaced.
| The exported state dict must match the target Diffusers model architecture exactly. Checkpoints quantized with | ||
| fused QKV projections won't load into a model config that expects separate Q, K, and V projection modules. | ||
|
|
||
| Example compact `model.json` config: |
There was a problem hiding this comment.
Same as the doc page: Diffusers reads the model config from config.json, not model.json. Please fix the filename here and in the "quantization_config stored in model.json" sentence above so users don't package their checkpoints incorrectly.
| ], | ||
| } | ||
|
|
||
| _import_structure["quantizers.quantization_config"].append("NunchakuLiteQuantizationConfig") |
There was a problem hiding this comment.
Since this export is unconditional, add "NunchakuLiteQuantizationConfig" directly to the "quantizers.quantization_config" list in the _import_structure dict literal above instead of appending via a standalone statement — that matches how every other unconditional export is declared. Note also that NunchakuLiteQuantizationConfig.__init__ references torch unconditionally, while all other quantization configs exported here are gated behind is_torch_available(); consider whether this one needs the same guard.
| if device is None: | ||
| device = torch.device("cpu") | ||
|
|
||
| if precision not in {"int4", "nvfp4"}: |
There was a problem hiding this comment.
These precision/group_size checks duplicate validation that NunchakuLiteQuantizationConfig.post_init already enforces (and post_init is stricter: it pins group_size to 16 for fp4 / 64 for int4, while this accepts any positive value that forward then ignores — the activation-scale layout hard-codes 16/64). Per the repo's no-defensive-code guideline, drop the re-validation here and rely on the config.
Co-authored-by: sergereview[bot] <283583894+sergereview[bot]@users.noreply.github.com>
Co-authored-by: sergereview[bot] <283583894+sergereview[bot]@users.noreply.github.com>
sayakpaul
left a comment
There was a problem hiding this comment.
Nice, thanks a lot for the further updates. I will post about the benchmarking I conducted through HF Jobs and Claude Code below.
| Compile the quantized transformer after loading the pipeline for faster inference. | ||
|
|
||
| ```python | ||
| pipe.transformer = torch.compile(pipe.transformer, mode="default", fullgraph=False) |
There was a problem hiding this comment.
Why can't we do it fullgraph=True? Can you open an issue on the nunchaku-lite repo with the trace?
There was a problem hiding this comment.
with the second kernel version, this has been resolved
| use_pe=False, | ||
| ).images[0] | ||
| image.save("ernie-image-turbo-nunchaku-lite.png") | ||
| ``` |
There was a problem hiding this comment.
Is it compatible with offloading, too?
There was a problem hiding this comment.
yes it is compatible with enable_sequential_cpu_offload and enable_model_cpu_offload
| | `svdq_w4a4` | `fp4` | 16 | Uses NVFP4 runtime kernels with SVDQ low-rank correction. | | ||
| | `svdq_w4a4` | `int4` | 64 | Uses INT4 W4A4 kernels with SVDQ low-rank correction. | | ||
| | `awq_w4a16` | `int4` | 64 | Uses INT4 weight-only AWQ-style kernels. | | ||
|
|
There was a problem hiding this comment.
I think it was not addressed? I think should mention a matrix of the quant schemes and their supported hardware / CUDA capabilities. Also, the note on hopper since we cannot support it.
|
|
||
| self.post_init() | ||
|
|
||
| def post_init(self): |
| @@ -0,0 +1,161 @@ | |||
| import json | |||
|
Even though the below is generated with Claude Code (I have manually verified them). Nunchaku Lite (PR #14100) — testing notesI ran the snippet from Reproduced the doc benchmark ✅Ran the exact doc snippet (
These match the doc's reported 2.271 s → 1.675 s almost exactly, so the NVFP4 + Rough edge 1: capability check is more permissive than the shipped kernels
Consequence: on an H100 (sm_90) the INT4 path passes which is a confusing failure mode (looks like a runtime bug, not an unsupported-hardware message). So Hopper is effectively unsupported for INT4 today even though the check green-lights it. Suggestions (pick whatever fits):
Rough edge 2: kernels only ship for torch 2.11 / 2.12
For Blackwell specifically, the working combo is torch 2.11 + cu128 ( Suggestion: document the supported torch versions in Environment
|
|
Now running the tests. |
|
Testing report (again manually verified). Nunchaku Lite (PR #14100) — test run on HF JobsRan the PR's test suite Setup
Result9 passed in 4.66s ✅ (pytest exit code 0), including One caveat (not a PR bug)The 3 tests that download the kernel initially failed due to an external dependency bug:
Suggestion: report to |
Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
03399a0 to
5522954
Compare
5522954 to
86cd7c8
Compare
…chair/diffusers into feature/nunchaku-lite-single-file
|
@bot /style |
|
Style bot fixed some files and pushed the changes. |
| Initialize [`~quantizers.PipelineQuantizationConfig`] with the following parameters. | ||
|
|
||
| - `quant_backend` specifies which quantization backend to use. Currently supported backends include: `bitsandbytes_4bit`, `bitsandbytes_8bit`, `gguf`, `quanto`, and `torchao`. | ||
| - `quant_backend` specifies which quantization backend to use. Currently supported backends include: `bitsandbytes_4bit`, `bitsandbytes_8bit`, `gguf`, `nunchaku_lite`, `quanto`, and `torchao`. |
There was a problem hiding this comment.
seems like nunchaku_lite is more of a prequantized loader versus a backend that quantizes pretrained/non-quantized checkpoints on the fly which is what this section mainly demonstrates. it would be better to add a new "Loading prequantized checkpoints" section with this there
| ```bash | ||
| pip install -U kernels | ||
| ``` |
There was a problem hiding this comment.
move the installation before the first code snippet which needs it
| pip install -U kernels | ||
| ``` | ||
|
|
||
| > [!NOTE] |
There was a problem hiding this comment.
would move this to the loading section where architecture matching is more relevant versus under CUDA kernels
|
@rootonchair there are some CI failures. Would it be possible for you to fix them? The PR is basically ready to be merged. |
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com> Co-authored-by: Vinh H. Pham <phamvinh257@gmail.com>
|
@sayakpaul @stevhliu I just updated the docs and the unit test, could you check again |


What does this PR do?
Adds Nunchaku Lite single-file checkpoint loading for Diffusers models.
This introduces
NunchakuLiteQuantizationConfigand a new Nunchaku Lite quantizer that can patch supported nn.Linear modules into runtime SVDQ/AWQ linear layers before strict checkpoint loading. The loader reads safetensors metadata duringfrom_single_fileso Nunchaku Lite checkpoints can use their embedded runtime manifest to decide which modules to replace.Deprecated API
New API for
from_single_fileuseFixes # (issue)
Before submitting
.ai/review-rules.md?documentation guidelines, and
here are tips on formatting docstrings.
Who can review?
Anyone in the community is free to review the PR once the tests have passed. Feel free to tag
members/contributors who may be interested in your PR.