Download smooth_quant.py from wepiqx/MERNIK: direct link, hf CLI and curl.
- Browser
- Download file 12.1 kB
-
https://huggingface.co/wepiqx/MERNIK/resolve/main/smooth_quant.py
- Command line
-
hf download hf://wepiqx/MERNIK/smooth_quant.py
-
curl -L -o smooth_quant.py https://huggingface.co/wepiqx/MERNIK/resolve/main/smooth_quant.py
12.1 kB
| #!/usr/bin/env python3 | |
| """smooth_quant: SmoothQuant-style preprocessing from in_sum2 (no extra calibration). | |
| Per-input-channel scales s = rms_act^a / rms_w^(1-a) (geometric-mean | |
| normalized), migrated offline: | |
| - linear input side: W' = W * s (exact) | |
| - compensation: preceding RMSNorm g' = g / s, or paired-linear | |
| output rows /s through SiLU (standard approx). | |
| FFN-down input (post-SiLU, no norm) folds into up/gate output rows. | |
| Attention Q/K outputs untouched (RoPE/softmax don't commute). | |
| v1 scope: ffn_gate/up/down, attn_output, attn_qkv (input side only). | |
| Verify numerically (block-output drift), then real quant duel. | |
| Usage: | |
| python smooth_quant.py --model M-F16.gguf --imatrix M.imatrix.gguf \\ | |
| --out M-SM.gguf --alpha 1.0 [--dry] | |
| """ | |
| import argparse | |
| import os | |
| import struct | |
| import sys | |
| import numpy as np | |
| import gguf | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), | |
| "scripts")) | |
| from graft_mtp3 import pack_str, pack_field | |
| FFN_TRIO = ("ffn_gate", "ffn_up", "ffn_down") | |
| def layer_of(name): | |
| p = name.split(".") | |
| if len(p) >= 2 and p[0] in ("blk", "BLK") and p[1].isdigit(): | |
| return int(p[1]) | |
| return None | |
| def base_of(name): | |
| """strip blk.N. prefix -> tensor kind, e.g. blk.3.ffn_gate.weight""" | |
| p = name.split(".") | |
| if len(p) >= 3 and p[0] in ("blk", "BLK") and p[1].isdigit(): | |
| return ".".join(p[2:]) | |
| return name | |
| def preceding_norm(name, available): | |
| """norm tensor feeding this linear (arch-aware by availability).""" | |
| lyr = layer_of(name) | |
| if lyr is None: | |
| return None | |
| pre = f"blk.{lyr}." | |
| cands = [] | |
| if "ffn_" in name: | |
| cands = [pre + "ffn_norm.weight", pre + "post_attention_norm.weight"] | |
| elif "attn_" in name or "qkv" in name: | |
| cands = [pre + "attn_norm.weight"] | |
| for c in cands: | |
| if c in available: | |
| return c | |
| return None | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--model", required=True) | |
| ap.add_argument("--imatrix", required=True) | |
| ap.add_argument("--out", required=True) | |
| ap.add_argument("--alpha", type=float, default=1.0) | |
| ap.add_argument("--dry", action="store_true") | |
| ap.add_argument("--skip-down", action="store_true", | |
| help="forensics: skip ffn_down smoothing (isolate the " | |
| "SiLU paired-fold path)") | |
| args = ap.parse_args() | |
| sys.path.insert(0, ".") | |
| from imatrix_reader import read_imatrix | |
| im = read_imatrix(args.imatrix) | |
| im_raw = {n: np.asarray(d["in_sum2_raw"], dtype=np.float64) | |
| for n, d in im["tensors"].items() | |
| if d.get("in_sum2_raw") is not None} | |
| r = gguf.GGUFReader(args.model) | |
| T = {t.name: t for t in r.tensors} | |
| from gguf.constants import GGMLQuantizationType as GQ | |
| assert all(int(t.tensor_type) in (int(GQ.F32), int(GQ.F16), int(GQ.BF16)) | |
| for t in r.tensors if "norm" not in t.name), \ | |
| "v1 needs F32/F16/BF16 source" | |
| import ml_dtypes | |
| def get(name): | |
| t = T[name] | |
| d = np.asarray(t.data) | |
| if d.dtype == np.uint8: # BF16 raw | |
| return np.frombuffer(d.tobytes(), | |
| dtype=ml_dtypes.bfloat16).astype(np.float32) | |
| return np.asarray(d, dtype=np.float32) | |
| # plan: tensor -> (scales or None) | |
| # SHARED-INPUT RULE (scar 2026-09-19): gate+up read ONE norm output — | |
| # they MUST share a single joint s. Folding two different s into one | |
| # norm (v1 bug) breaks math in block 0 and compounds to PPL 523897. | |
| plan, folds = {}, {} | |
| joint_done = set() | |
| for name in T: | |
| b = base_of(name) | |
| if not any(k in b for k in ("ffn_gate", "ffn_up", "ffn_down", | |
| "attn_qkv")): | |
| continue | |
| if args.skip_down and "ffn_down" in b: | |
| continue | |
| # NOTE: attn_output excluded — softmax/V inside, no valid fold point | |
| if ".weight" not in name or name not in im_raw: | |
| continue | |
| W = get(name) | |
| e = im_raw[name] | |
| if e.shape[0] < W.shape[-1]: | |
| continue | |
| in_dim = W.shape[-1] | |
| rms_a = np.sqrt(np.maximum(e[:in_dim], 1e-12)) | |
| rms_w = np.sqrt(np.maximum((W.astype(np.float64) ** 2).mean(axis=0), | |
| 1e-24)) | |
| s = (rms_a ** args.alpha) / (rms_w ** (1.0 - args.alpha) + 1e-24) | |
| s = s / np.exp(np.log(np.maximum(s, 1e-24)).mean()) | |
| plan[name] = s | |
| # compensation target | |
| if b.startswith("ffn_down"): | |
| folds[name] = [n for n in | |
| (f"blk.{layer_of(name)}.ffn_gate.weight", | |
| f"blk.{layer_of(name)}.ffn_up.weight") | |
| if n in T] | |
| else: | |
| nn = preceding_norm(name, T) | |
| folds[name] = [nn] if nn else [] | |
| # joint resolution: gate+up share ONE input -> ONE s (geometric mean), | |
| # folded ONCE into their shared norm. v1 folded both -> PPL 523897. | |
| for name in list(plan): | |
| if ".ffn_gate.weight" not in name: | |
| continue | |
| up = name.replace("ffn_gate", "ffn_up") | |
| if up not in plan: | |
| continue | |
| sj = np.sqrt(np.maximum(plan[name], 1e-24) * | |
| np.maximum(plan[up], 1e-24)) | |
| plan[name] = sj | |
| plan[up] = sj | |
| # single fold target (shared norm); drop stale per-src entries | |
| nn = preceding_norm(name, T) | |
| folds[name] = [nn] if nn else [] | |
| folds[up] = [] | |
| joint_done.add(layer_of(name)) | |
| print(f"smoothing plan: {len(plan)} tensors, alpha={args.alpha}, " | |
| f"joint gate+up blocks: {sorted(joint_done)}") | |
| if args.dry: | |
| for n in sorted(plan)[:8]: | |
| print(" smooth", n, "->", folds[n]) | |
| return | |
| skipping = {n for n in plan if "attn_output" in base_of(n)} | |
| print(f"smoothing plan: {len(plan) - len(skipping)} tensors " | |
| f"(+folds), skipped: {len(skipping)} (attn_output)") | |
| # (drift check lives after transformed() def, before write) | |
| # STREAMING write (scar 2026-09-18: accumulating all arrays in a dict | |
| # ate 8 GB and OOM-killed the HE server mid-battery — one tensor in RAM | |
| # at a time from here on; plan/folds dicts hold only small scale vectors) | |
| def transformed(name): | |
| """Smoothed/folded array or None (= stream raw).""" | |
| t = T[name] | |
| d = np.asarray(t.data) | |
| if d.dtype == np.uint8: | |
| base = np.frombuffer(d.tobytes(), | |
| dtype=ml_dtypes.bfloat16).astype(np.float32) | |
| else: | |
| base = np.asarray(d, dtype=np.float32) | |
| b = base_of(name) | |
| out = None | |
| if name in plan and name not in skipping: | |
| W = base.astype(np.float64) | |
| s = plan[name] | |
| if W.shape[-1] == s.shape[0]: | |
| out = (W * s).astype(np.float32) | |
| for src, tgts in folds.items(): | |
| if src in skipping or name not in tgts: | |
| continue | |
| s = plan[src].astype(np.float32) | |
| cur = base.astype(np.float32).copy() if out is None else out | |
| if "norm" in name: | |
| if cur.shape[0] == s.shape[0]: | |
| cur = cur / s | |
| elif cur.shape[0] == s.shape[0]: | |
| cur = cur / s[:, None] if cur.ndim > 1 else cur / s | |
| out = cur | |
| return out | |
| # write copy with replaced tensors (F32 for norms stays F32; BF16 kept) | |
| parts = [] | |
| for k, f in r.fields.items(): | |
| if k.startswith("GGUF."): | |
| continue | |
| b = pack_field(k, f) | |
| if b is not None: | |
| parts.append(b) | |
| kv_raw = b"".join(parts) | |
| ti_raw, off, order = b"", 0, [] | |
| for t in r.tensors: | |
| name = t.name | |
| dims = [int(d) for d in t.shape] | |
| ti_raw += pack_str(name) | |
| ti_raw += struct.pack("<I", len(dims)) | |
| for d in dims: | |
| ti_raw += struct.pack("<Q", d) | |
| ti_raw += struct.pack("<I", int(t.tensor_type)) | |
| ti_raw += struct.pack("<Q", off) | |
| ne = 1 | |
| for d in dims: | |
| ne *= d | |
| bpe = 4 if int(t.tensor_type) == int(GQ.F32) else 2 | |
| off += ne * bpe | |
| off += (32 - off % 32) % 32 | |
| order.append(name) | |
| hdr = b"GGUF" + struct.pack("<I", 3) + struct.pack("<Q", len(order)) | |
| hdr += struct.pack("<Q", len(parts)) + kv_raw + ti_raw | |
| hdr += b"\x00" * ((32 - len(hdr) % 32) % 32) | |
| # numeric drift check: full FFN block incl. norm, original vs | |
| # smoothed. Catches fold bugs BEFORE burning a quant (scar: PPL 523897 | |
| # from double-folding one norm). Aborts the run if drift > 1e-2. | |
| def silu(x): | |
| return x / (1.0 + np.exp(-np.clip(x, -30, 30))) | |
| def rmsnorm(x, g): | |
| return x / max(np.sqrt((x ** 2).mean()), 1e-12) * g | |
| drifts = [] | |
| for L in sorted({layer_of(n) for n in plan | |
| if layer_of(n) is not None})[:3]: | |
| names = {base_of(n): n for n in T if layer_of(n) == L} | |
| need = ["ffn_gate.weight", "ffn_up.weight"] + ( | |
| [] if args.skip_down else ["ffn_down.weight"]) | |
| nn = None | |
| for cand in (f"blk.{L}.ffn_norm.weight", | |
| f"blk.{L}.post_attention_norm.weight"): | |
| if cand in T: | |
| nn = cand | |
| break | |
| if not all(k in names for k in need) or nn is None: | |
| continue | |
| go, uo = (get(names[k]).astype(np.float64) for k in need[:2]) | |
| do = get(names[need[2]]).astype(np.float64) if len(need) > 2 else None | |
| gnorm = get(nn).astype(np.float64) | |
| tr = [transformed(names[k]) for k in need] + [transformed(nn)] | |
| if any(v is None for v in tr): | |
| print(f" blk.{L}: transform missing — skip drift") | |
| continue | |
| if len(need) > 2: | |
| gs, us, ds, gn = (np.asarray(v, dtype=np.float64) for v in tr) | |
| else: | |
| gs, us, gn = (np.asarray(v, dtype=np.float64) for v in tr) | |
| rng = np.random.default_rng(7) | |
| x = rng.standard_normal(go.shape[-1]).astype(np.float64) | |
| # layout: numpy (out,in) — verify by construction below | |
| if not (go.shape[-1] == x.shape[0]): | |
| print(f" blk.{L}: layout mismatch {go.shape} vs x — abort math") | |
| continue | |
| x0 = rmsnorm(x, gnorm) | |
| h0 = silu(go @ x0) * (uo @ x0) | |
| x1 = rmsnorm(x, gn) | |
| h1 = silu(gs @ x1) * (us @ x1) | |
| if do is not None: | |
| ds = np.asarray(transformed(names[need[2]]), dtype=np.float64) | |
| y0, y1 = do @ h0, ds @ h1 | |
| else: | |
| y0, y1 = h0, h1 # skip-down: compare pre-down activations | |
| drifts.append(float(np.abs(y1 - y0).max() / | |
| max(np.abs(y0).max(), 1e-12))) | |
| if drifts: | |
| print(f" block drift max-rel-err: {max(drifts):.2e} " | |
| f"(mean {sum(drifts) / len(drifts):.2e})") | |
| if max(drifts) > 1e-2: | |
| print(" DRIFT TOO BIG — aborting, fix folds first") | |
| sys.exit(2) | |
| with open(args.out, "wb") as fout: | |
| fout.write(hdr) | |
| for name in order: | |
| t = T[name] | |
| arr = transformed(name) | |
| if arr is not None: | |
| tt = int(t.tensor_type) | |
| if tt == int(GQ.BF16): | |
| raw = np.ascontiguousarray( | |
| arr).astype(ml_dtypes.bfloat16).tobytes() | |
| elif tt == int(GQ.F16): | |
| raw = np.ascontiguousarray( | |
| arr).astype(np.float16).tobytes() | |
| else: | |
| raw = np.ascontiguousarray( | |
| arr).astype(np.float32).tobytes() | |
| else: | |
| raw = np.ascontiguousarray(t.data).tobytes() | |
| fout.write(raw) | |
| fout.write(b"\x00" * ((32 - len(raw) % 32) % 32)) | |
| print("wrote", args.out, os.path.getsize(args.out)) | |
| if __name__ == "__main__": | |
| main() | |