Skip to content

Commit c4ebc52

Browse files
kmbandyclaude
andcommitted
docs(MAD-223): ML8_README.md for the weight calibration pipeline
End-to-end docs covering the 6 scripts shipped tonight: - centroid_quantizer, calibrate_ml8, diagnose_calibration, ml8_io, reconstruct_model, calibration_report With quick-start commands, default rationale, metric explanation (W_SNR vs Y_SNR), interim file format, and MAD-223 acceptance gate (Δ_PPL < 0.08 vs f16). Separate from the existing README.md (which documents the older MAD-214 KV-cache calibration tooling) — these are distinct workflows with different fit_loss defaults. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 46e2b15 commit c4ebc52

1 file changed

Lines changed: 102 additions & 0 deletions

File tree

scripts/calibration/ML8_README.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# ml8-4 Weight Calibration
2+
3+
MAD-223 Phase B. End-to-end tooling to quantize a HuggingFace model's MLP weights
4+
to **ml8-4** format: 16 signed centroids per group, ~4.125 bpv.
5+
6+
For KV-cache calibration (MAD-214), see the existing `README.md` in this directory.
7+
8+
## Pipeline overview
9+
10+
```
11+
HF model (fp16) ──┐
12+
│ calibrate_ml8.py
13+
wikitext-2 ───────┤ ────────────────────► per-layer .pt blobs + manifest.json
14+
│ (Lloyd-Max + GPTQ) │
15+
│ │
16+
│ ▼ reconstruct_model.py
17+
│ ml8-overlaid HF model + PPL re-eval
18+
19+
└─► diagnose_calibration.py (compare quant variants)
20+
```
21+
22+
## Scripts
23+
24+
| Script | Purpose |
25+
|---|---|
26+
| `centroid_quantizer.py` | `CentroidQuantizer` — drop-in for auto_gptq.Quantizer (signed-16 LUT, MSE Lloyd-Max, optional mag_weighted / Hessian-aware) |
27+
| `calibrate_ml8.py` | Main driver: HF model + wikitext-2 → per-layer GPTQ-quantized .pt blobs + manifest |
28+
| `diagnose_calibration.py` | Runs 5 quantization variants on the same matrix (uniform INT4, naive ± loss, GPTQ ± loss) for debugging |
29+
| `ml8_io.py` | `load_ml8_layer`, `reconstruct_weight`, `bits_per_value` — disk format helpers |
30+
| `reconstruct_model.py` | Load HF model + overlay all saved .pt blobs → re-eval PPL on rehydrated model |
31+
| `calibration_report.py` | Pretty-print `manifest.json` — Y_SNR distribution, per-kind, size, PPL, worst layers |
32+
33+
## Quick start
34+
35+
```bash
36+
# 1. Calibrate (≈45 min for Qwen3.5-4B on R9700)
37+
python3 calibrate_ml8.py \
38+
--model Qwen/Qwen3.5-4B \
39+
--output-dir /tmp/ml8-qwen3-4b \
40+
--n-samples 32 --seq-len 1024 --group-size 128 \
41+
--eval-ppl --ppl-max-tokens 100000
42+
43+
# 2. Summary
44+
python3 calibration_report.py /tmp/ml8-qwen3-4b
45+
46+
# 3. Re-eval PPL from saved artifacts (verifies disk format)
47+
python3 reconstruct_model.py \
48+
--model Qwen/Qwen3.5-4B \
49+
--calibration-dir /tmp/ml8-qwen3-4b \
50+
--eval-ppl --also-eval-baseline --ppl-max-tokens 100000
51+
```
52+
53+
## Defaults (and why)
54+
55+
| Knob | Default | Rationale |
56+
|---|---|---|
57+
| `--fit-loss` | `mse` | Lloyd-Max with uniform sample weighting. **Do NOT use `mag_weighted p=5`** — it was the MAD-214 KV winner because activations have heavy outliers; weights are Gaussian-near-zero and mag_p5 starves the dense center (measured: 10 dB regression on Qwen3.5-4B). |
58+
| `--n-centroids` | 16 | 4-bit indices (ml8-**4**). |
59+
| `--group-size` | 128 | Matches auto-gptq convention. Smaller = more LUT overhead but better fit; 128 is the sweet spot. |
60+
| `--percdamp` | 0.01 | Hessian diagonal damping for Cholesky stability. |
61+
| `--n-samples` | 64 | Calibration corpus size in wikitext rows. 32 also works; 128 is overkill. |
62+
| `--seq-len` | 2048 | Per-row tokenization length. Combined with `--n-samples`, target ≈30-130K total calibration tokens. |
63+
64+
## Metrics
65+
66+
Two SNR metrics are reported per linear:
67+
68+
- **W_SNR** — element-wise weight reconstruction (what naive snap optimizes)
69+
- **Y_SNR** — output-space reconstruction weighted by H (what GPTQ optimizes) ← **the meaningful one**
70+
71+
GPTQ deliberately makes individual weight elements DIFFER MORE from the original to make the OUTPUT activations MATCH MORE closely. Expect GPTQ to LOSE on W_SNR and WIN big on Y_SNR. Typical Qwen-class MLP linears: Y_SNR 26–30 dB with MSE+GPTQ.
72+
73+
## File format (interim, before MAD-223 Phase C)
74+
75+
Each layer's `.pt` blob:
76+
77+
```python
78+
{
79+
"name": str, # e.g. "model.layers.0.mlp.up_proj"
80+
"shape": [rows, in_features],
81+
"group_size": int, # 128
82+
"n_centroids": int, # 16
83+
"indices": int8 [rows, in_features], # values 0..15
84+
"centroids_per_group": fp32 [n_groups, 16],
85+
"scale_per_group": fp32 [rows, n_groups],
86+
"mse": float, "w_snr_db": float, "y_snr_db": float, "rel_err": float,
87+
}
88+
```
89+
90+
Reconstruction: `W[r, c] = centroids_per_group[c // group_size][indices[r, c]] * scale_per_group[r, c // group_size]`
91+
92+
Phase C will define the native `.ml8` binary format + GGUF-wrapped variant (per the
93+
decision to ship both formats — see saved KG decision 2026-05-22).
94+
95+
## Acceptance gate (MAD-223)
96+
97+
`Δ_PPL = quantized − baseline` measured on wikitext-2 test split.
98+
99+
- **`Δ_PPL < 0.08`** → ship as-is (Phase B.5 AWQ scaling NOT needed)
100+
- **`Δ_PPL ≥ 0.08`** → enable Phase B.5 AWQ activation-scaling preprocess
101+
102+
`calibrate_ml8.py --eval-ppl` and `calibration_report.py` both check this automatically.

0 commit comments

Comments
 (0)