Spare 1-D norm weights from a blanket --type - #2
Merged
danielhanchen merged 1 commit intoAug 8, 2026
Conversation
danielhanchen
force-pushed
the
fix/spare-1d-norm-weights-from-blanket-quant
branch
from
August 7, 2026 15:06
2f5aaf0 to
a32e7a1
Compare
tensor_should_be_converted has no rule for 1-D weights, so a per-channel norm
scale survives a blanket --type only by accident: when its length does not
divide the quant block size (FLUX q_norm/k_norm are [128] and 128 % 256 != 0),
or when one of the FLUX-era name rules happens to match it.
MiniMax-H3's per-block norms are [5376], and 5376 % 256 == 0, so a blanket
--type q4_K quantizes 105 of them. The result loads and renders a plausible
video, so a "does it run" check passes it, but scored against a bf16 render of
the same prompt and seed it is destroyed.
A 1-D weight is a per-channel gain, never a matmul weight. Every channel of a
block would share one scale and one min, and a gain vector has no reason to be
locally smooth, so quantizing it buys almost nothing and costs a lot: on this
model holding all 211 1-D tensors adds 0.77 MiB to a 10.60 GiB file, 0.007%.
Measured on minimax_h3_fl2va_pruned, converted from the same bf16 checkpoint
with a blanket --type q4_K and no --tensor-type-rules, rendered at 640x384,
25 frames, 4 steps, cfg 1.0, seed 1234, --rng cpu, and scored against a bf16
render of that same prompt and seed:
1-D tensor types PSNR SSIM LPIPS
before F32 5, F16 51, BF16 106, Q4_K 105 9.87 0.074 0.981
after F32 5, F16 51, BF16 211 22.22 0.841 0.292
SSIM 0.074 means the output is essentially uncorrelated with the reference.
The 1-D rule alone is sufficient. A build made with
--tensor-type-rules 'norm[0-9]*\.weight$=bf16,condition_proj\.weight$=bf16'
produces the same 1-D layout and scores 21.52 / 0.838 / 0.299, so nothing here
depends on also sparing condition_proj.weight, which is 2-D and a separate
quality choice. Peak VRAM is unchanged at 16.83 GiB.
danielhanchen
force-pushed
the
fix/spare-1d-norm-weights-from-blanket-quant
branch
from
August 7, 2026 15:07
a32e7a1 to
9f6c4fd
Compare
This was referenced Aug 8, 2026
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.
tensor_should_be_convertedhas no rule for 1-D weights. A per-channel norm scale survives a blanket--typeonly by accident: when its length does not divide the quant block size, or when one of the FLUX-era name rules happens to match it.FLUX gets away with it because
q_norm/k_normare[128]and128 % 256 != 0, so the existing block-size check spares them. MiniMax-H3's per-block norms are[5376], and5376 % 256 == 0, so a blanket--type q4_Kquantizes 105 of them.Why this is worth catching
The result loads and renders a plausible video, so a "does it run" check passes it. Scored against a bf16 render of the same prompt and seed (640x384, 25 frames, 4 steps, cfg 1.0, seed 1234,
--rng cpu, text encoder on CPU):q4_Kblanket, beforeq4_Kblanket, afterSSIM 0.074 means the output is essentially uncorrelated with the reference. Peak VRAM is unchanged at 16.83 GiB, and the file grows 0.77 MiB on 10.60 GiB (0.007%).
The change
One branch, next to the existing block-size check: if the target type is quantized and the tensor is 1-D, leave it alone. A 1-D weight is a per-channel gain, never a matmul weight. Every channel of a block would share one scale and one min, and a gain vector has no reason to be locally smooth, so quantizing it buys almost nothing.
The 1-D rule alone is sufficient
The workaround for this today is
--tensor-type-rules 'norm[0-9]*\.weight$=bf16,condition_proj\.weight$=bf16'. That build produces the same 1-D layout and scores 21.52 / 0.838 / 0.299, so nothing here depends on also sparingcondition_proj.weight, which is 2-D and a separate quality choice rather than something this rule should touch.The two builds land within a small margin of each other in opposite directions. On a 4-step schedule a small weight perturbation moves the sampling trajectory a long way, so that gap should be read as trajectory noise, not as evidence that either treatment of
condition_projis better.Verification
Converted the same 40 GB bf16 checkpoint with a blanket
--type q4_Kand no--tensor-type-rules, then read the tensor types back out of the GGUF and rendered it:Q4_KCross-checked two ways. The same one-line rule applied to upstream
masterand to this branch produce byte-identical 1-D layouts and identical scores (22.22 / 0.8407 / 0.2921), so the result does not depend on anything else in this fork.Builds clean on Linux with
-DSD_CUDA=ONand on CPU (sd-cliandsd-serverboth link).Scope
Deliberately narrow. This is the general rule that should have been there from the start, and it only affects blanket
--typeconversions of models whose 1-D tensors happen to divide the block size, so no existing FLUX, SD or SDXL quant changes at all.--tensor-type-rulesstill overrides it for anyone who wants the old behaviour on a specific pattern.