feat: load ConvRot int4 and per-channel int8 safetensors - #1829
Open
mosaic-devel wants to merge 1 commit into
Open
feat: load ConvRot int4 and per-channel int8 safetensors#1829mosaic-devel wants to merge 1 commit into
mosaic-devel wants to merge 1 commit into
Conversation
Some checkpoints ship weights already quantized by an external toolchain, storing the payload as raw bytes plus sidecar tensors that describe how to dequantize it. These currently fail to load at all: I8 is rejected as an unsupported dtype and U8 tensors are silently dropped. Add a load-time repack into native ggml types, so inference uses the ordinary kernels and no backend-specific support is needed: ConvRot convrot_w4a4 (int4) -> Q4_0 int8_tensorwise / int8 -> Q8_0 bitsandbytes NF4 -> F32 Both integer paths are exact: the int4 values occupy [-7,7] so the +8 bias lands inside Q4_0's nibble range, and the per-output-channel scale becomes the block scale. The only loss is F32->F16 on that scale (~5e-4 relative, against ~15% int4 quantization error). ConvRot additionally stores W*H rather than W, where H is a block-diagonal regular-Hadamard rotation over convrot_groupsize input channels, so the activation must be rotated to match: y = x*W^T = x*(W'*H)^T = (x*H)*W'^T H is the radix-4 butterfly core Kronecker-composed log4(groupsize) times and scaled by 1/sqrt(groupsize), which makes it symmetric as well as orthogonal, so no transpose bookkeeping is needed. It is applied as one matmul against a shared constant, costing groupsize/out_features extra FLOPs - under 5% at 6144 wide. Activations stay F32, so this is W4A16: slower than a dedicated 4-bit activation kernel but more accurate. LoRA is skipped on rotated layers with a warning, since adapter deltas are trained against the unrotated weight. Conversion refuses rotated weights outright, because no output container carries the rotation marker and the result would load cleanly and generate noise.
Owner
|
Thanks for the PR. But I don't think converting ConvRot/int4 weights into existing ggml quant types in the safetensors loader is the right abstraction. These weights are not semantically Q4_0/Q8_0; they require additional runtime behavior (activation rotation and special dequantization). Repacking them into existing ggml types hides the actual tensor representation and prevents backend-specific optimized kernels in the future. IMO this should be implemented as a native ggml tensor type/operator instead. The loader should preserve the original quantization format and let ggml handle execution. |
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
Checkpoints quantized by an external toolchain store the payload as raw bytes plus sidecar tensors describing how to dequantize it. Today they cannot be loaded at all:
I8is rejected as an unsupported dtype, andU8tensors are silently dropped before they reachtensor_storages.That includes the ConvRot int4 exports now appearing for Krea 2 (~4.5 bits/weight, roughly a third the size of Q8_0), which matters most on small cards — on an 8 GB RX 6600 XT the int4 model runs where the larger GGUF quants did not.
Solution
Repack once, at load time, into native ggml types, so inference uses the ordinary kernels and no backend-specific support is needed:
convrot_w4a4(int4)Q4_0int8_tensorwise/ ConvRotlinear_dtype: int8Q8_0F32Both integer paths are exact: int4 values occupy
[-7,7], so the+8bias lands insideQ4_0's nibble range, and the per-output-channel scale becomes the block scale. NoteQ4_0interleaves elementjwithj+16while the source packs adjacent pairs, so the repack is a permutation rather than a copy.The rotation
ConvRot stores
W·Hrather thanW, whereHis a block-diagonal regular-Hadamard rotation overconvrot_groupsizeinput channels. So the activation has to be rotated to match:Ignoring this does not degrade gracefully — it produces noise.
His the radix-4 butterfly core Kronecker-composedlog4(groupsize)times and scaled by1/sqrt(groupsize), which makes it symmetric as well as orthogonal, so there is no transpose bookkeeping. It is applied as one matmul against a shared constant, costinggroupsize/out_featuresextra FLOPs — under 5% at 6144 wide.Activations stay F32, so this is W4A16: slower than a dedicated 4-bit-activation kernel, but more accurate than the paper's W4A4.
Because the hook lives in
Linear, every model in the repo gets this automatically — no per-architecture changes.Two deliberate refusals
convertrefuses rotated weights. No output container carries the rotation marker, so an export would load cleanly and generate noise.Verification
Format was reverse-engineered and checked numerically against a reference GGUF of the same base model (correlation of dequantized weights vs. reference):
HThe repack itself is bit-exact against a direct reference dequantization (
dequantize_row_q4_0/_q8_0), comparing against the F16-rounded scale:End-to-end on Vulkan (RX 6600 XT, RADV), Krea 2 Turbo ConvRot int4 + Wan2.1 VAE:
generates a correct image with legible rendered sign text — which is a fairly sensitive check, since a subtly wrong rotation, nibble order or scale garbles text first. Happy to attach samples.
Caveats
Note
This was written with AI assistance. Errors are mine; happy to rework or drop any part of it.