Quantization stores approximate weights in compact groups called blocks. Values within a block share encoding parameters, so changing one value can change how its neighbors are reconstructed.
secant quant patch edits Q8_0 tensors by decoding and re-encoding only affected blocks. It writes a new checkpoint by default; --in-place changes the input file. Unaffected blocks retain their stored bytes. This avoids converting the entire checkpoint back through a model framework.
This page separates two operations: reordering whole groups of columns and editing their values. “Commutation” means two operations give the same result in either order. Reordering complete Q8_0 blocks can preserve bytes exactly; changing weights introduces quantization error. Neither property guarantees model behavior.
Relation to prior work. In October 2026, Kiwimaker (apply_abliteration.py / apply_abliteration_v2.py) demonstrated in-place r+ np.memmap GGUF Q8_0 editing with SHA-256 verification of untouched byte ranges (while empirically skipping Q4_K and dequantizing/re-quantizing full 2-D Q8_0 matrices in float64 via gguf-py), and SC117 (October 2026) demonstrated byte-level in-place GGUF tensor transplantation and scale-preserving Q2_0 re-encoding. Existing .safetensors abliteration tools (jimsharded_ablate.py, and abliterix) re-serialize modified shards from scratch via save_file(). OPLoRA, Safe LoRA, and OGPSA concern constrained adaptation or alignment; their guarantees do not automatically extend through lossy storage. Secant’s specific contributions here are in-place zero-copy mmap byte-slice surgery inside .safetensors files, sub-tensor compressed-domain surgery directly on touched 34-byte block_q8_0 structs (crates/secant-core/src/quant.rs) without full-tensor float64 materialization, and the formal block-head alignment proof between 32-weight Q8_0 blocks () and 256-weight Q4_K superblocks under . See the paper for bibliographic details and limits.
Figure 1:How the stored blocks fit the 128-column ssm_out panels: four 32-element Q8_0 blocks divide each 128-column panel evenly (), whereas a single 256-element Q4_K superblock spans two neighboring heads with a shared FP16 scale d and dmin.
Figure 2:Where this page sits in the workflow: Stage 3. Each Q8_0 block holds 32 weights in 34 bytes (a 2-byte f16 scale d and 32 int8 codes), so a 128-column panel is exactly four whole blocks, and moving or editing a panel never touches a neighbor’s scale. A Q4_K superblock holds 256 weights in 144 bytes: f16 d and dmin, 12 bytes of packed sub-block scales, and 128 bytes of 4-bit codes. The boundary at column 128 therefore falls inside it, and carries half of its weights away from the scales they share. The arrows show on a toy grid (); Section 1 gives the Qwen3.5-4B case (, ).
1. How Column Groups Fit Into Stored Blocks¶
A panel is a group of 128 adjacent columns. Four Q8_0 blocks fit inside one panel. A Q4_K superblock spans two panels. That difference determines whether moving a panel also splits a set of weights that share encoding parameters.
When llama.cpp converts hybrid Qwen3.5 models with (such as Qwen3.5-4B, where ) to GGUF, it permutes the input columns of blk.N.ssm_out.weight in 128-column value-head panels (). (On Qwen3.5-0.8B, so and the deployed column order is the identity; we evaluate on blk.16.ssm_out.weight as a structural 128-tile permutation test alongside the real identity layout.)
Writing a HuggingFace 128-column panel index as (, ), its GGUF panel position is : the transpose of an grid into a grid. On Qwen3.5-4B this is a non-trivial bijection, not an involution: because , applying it twice does not return the identity (, ). The inverse is
Whether a quantized format commutes with depends on how its block size divides 128:
| Property | Q8_0 (GGML Type 8) | Q4_K (GGML Type 12) |
|---|---|---|
| Block size & byte footprint | 32 weights (34 bytes: 2B f16 scale + 32B int8 codes) | 256 weights (144 bytes: shared f16 d, dmin + 8 sub-blocks) |
| Alignment with 128-col ssm_out panels | Exact (128 = 4 × 32): 0 straddling blocks | Fails (256 = 2 × 128): straddles 2 value heads (8 per 2048-col row, 16 per 4096-col row) |
| Reported weight-component change (B2) | 98.5% removed (0.5172 → 0.0079) | 67% of the component retained (0.5172 → 0.346) |
| Secant default behavior | Supported in-place (secant quant patch) | Refused (block-aligned surgery needs Q8_0) |
In
Q8_0(32elements per block): Every 128-column value-head panel is an exact union of 4 completeQ8_0blocks. Editing or permuting a panel never touches a neighboring panel’s scale factor.In
Q4_K(256elements per superblock): Every aligned superblock spans two adjacent 128-column panels and shares top-levelFP16scaledand minimumdmin. Editing one panel can alter the reconstructed neighbor. A panel permutation that moves whole superblocks without changing their internal layout, including the identity, can still commute. The reported67%retained component is a particular weight-space measurement, not a guaranteed consequence of straddling.
The permutation argument assumes a deterministic, position-independent encoder, aligned rows and complete blocks. Percentages here describe projections of weights onto a supplied direction, not the percentage of prompts refused. The standalone Rust secant quant patch refuses Q4_K; experimental Q4_K re-encoding results below are not receipts from a successful invocation of that command.
2. Compare Editing Before and After Compression¶
Starting with already-quantized weights introduces an initial approximation error. Editing and encoding those weights adds another error. The equations below keep track of those errors separately so the two paths can be compared without assuming that rounding cancels out.
Let denote the block-quantize-then-dequantize operator for block size . We assume only that is deterministic and block-local: the dequantized values of block depend only on the input values of block . Suppose we apply a column-sparse behavioral edit that touches a subset of blocks . Define the three quantization errors
The decomposition fixes the same additive in both paths. It is exact under that assumption, but contains three quantization errors, not one. A projector-subtraction edit recomputes its delta from the current weights, so applying it after dequantization generally uses a different delta; the displayed identity alone does not describe that operator’s two paths. A uniform “half-step” bound is not valid for an arbitrary codec, so each term needs a codec-specific argument.
2.1 A Per-Weight Error Bound for Q8_0¶
There are two sources of rounding here: the integer weight code and the stored scale. The bound adds them, under the stated scale-range assumptions. It describes an idealized calculation; the final paragraph explains what the actual encoder adds.
The Q8_0 encoder (crates/secant-core/src/quant.rs, quantize_row_q8_0) computes, for each 32-element block , the scale , the codes , and stores . Dequantization returns . Assume lies in the FP16 normal range (), so that . Then each element obeys
up to float32 rounding in the encoder arithmetic. (For a subnormal scale, add per element.) Write and on a touched block . Because , the post-edit scale drifts by at most
before FP16 rounding. Applying the per-element bound to each term, with :
so every element of a touched Q8_0 block satisfies
This is an ideal-arithmetic bound with FP16 scale rounding included. It requires the scale-range assumption for each of , and , not only the original block. It does not include the Rust encoder’s float32 reciprocal, multiplication and rounding errors and is not a certified bound for all machine inputs. The reported max_abs_diff is an empirical measurement, separate from that bound.
2.2 What the Q4_K Experiment Measured¶
Sharing scale parameters across panels can make an edit affect more weights than intended. The measurements below describe one experiment; they are not a general error bound or a supported Q4_K patch workflow.
Q4_K is not a single uniform scalar quantizer: each 256-element superblock has an FP16 super-scale d and super-minimum dmin, plus eight 6-bit sub-block scales and minimums. On Qwen3.5 ssm_out, each superblock straddles two 128-column value heads, so an edit to one head can change the shared fit of the neighboring head. We do not claim a proved per-element bound for Q4_K. Its behavior is characterized empirically: observed Path 1 vs. Path 2 max_abs_diff (about the Q8_0 value), 8 straddling superblocks per 2048-column row (16 per 4096-column row), and about 67% of the original weight-direction component retained in experimental Q4_K re-encoding (0.5172 0.346, Section 1). The implemented policy is that secant quant patch accepts only Q8_0 tensors and refuses Q4_K (and every other dtype) with tensor <name> is <dtype>, block-aligned surgery needs Q8_0.
3. Compare the Retained Error Measurements¶
All rows below come from one identified experiment. Reconstruction error measures the distance from the intended float result; the directional residual measures the component left along the supplied direction. Those are different quantities.
The banked exp2_receipt.json (secant.phase3.exp2.v1, SHA-256 1c437338d697ccfb6b24286ac60c84fa0c0a73f3a0120e3aefd920c5d608b0c7) records a single Qwen3.5-0.8B ssm_out experiment with a projector edit of Frobenius norm 0.5172394742. Under commutation.identity_real, both codecs commute with the identity permutation. Their reconstruction errors still differ:
| Metric under identity ordering | Q8_0 | Q4_K |
|---|---|---|
| Path 1 relative Frobenius reconstruction error | 0.5836% | 7.5863% |
| Path 2 relative Frobenius reconstruction error | 0.7992% | 8.2210% |
| Path 2 residual along the supplied direction, L2 | 0.0078591 | 0.3457191 |
| Path 1 versus Path 2 maximum absolute difference | 0.0021572 | 0.0238073 |
These are retained experimental measurements, not fresh model runs or outputs of the current Rust CLI. The separate whole-model Q8_0 byte-preservation experiment remains in Section 4.
4. Check Which Blocks and Tensors Changed¶
A block can be touched by the logical edit yet keep the same encoded bytes after rounding. The table distinguishes touched blocks, rewritten blocks, and blocks outside the edit. Whole-file verification separately checks the tensors that were not selected.
Comparing Path 1 (schematically, secant patch on BF16 followed by external Q8_0 quantization) against Path 2 (secant quant patch directly on Qwen3.5-0.8B.Q8_0.gguf) shows that direct 34-byte block surgery leaves all untouched blocks 100% bit-identical and matches Path 1 within 0.179% to 0.923% per-tensor relative Frobenius error (note that blocks where the float delta rounds to the same int8 code remain byte-identical even when inside the touched column support):
Path 1: edit, then quantize; Path 2: quantize, then edit affected blocks.
Unchanged touched blocks were processed but kept identical bytes. Untouched blocks lie outside the edit support.
Q8_0 block coverage
| Tensor / direction | Total blocks | Modified (Path 2) | Touched, unchanged |
|---|---|---|---|
| blk.12.ffn_down.weight (B2) | 114,688 | 108,949 | 5,739 |
| blk.13.ffn_down.weight (B3) | 114,688 | 48,703 | 449 |
| blk.15.ffn_down.weight (B1a) | 114,688 | 31,104 | 640 |
| blk.19.ffn_down.weight (B1b) | 114,688 | 21,842 | 686 |
| All other 331 tensors in file | n/a | 0 | 0 |
Untouched support and numerical difference
| Tensor / direction | Untouched, byte-identical | Relative Frobenius difference |
|---|---|---|
| blk.12.ffn_down.weight (B2) | 0 (dense row projector) | 0.923% |
| blk.13.ffn_down.weight (B3) | 65,536 (100% identical) | 0.251% |
| blk.15.ffn_down.weight (B1a) | 82,944 (100% identical) | 0.225% |
| blk.19.ffn_down.weight (B1b) | 92,160 (100% identical) | 0.179% |
| All other 331 tensors in file | 100% (0 bytes differing) | 0.000% |