MERNIK Gnom β learned damage ranker for quantization allocation
Gnom is the allocator's learned ranker from the MERNIK project: it predicts, per tensor group of a language model, how much damage (KL-divergence vs the BF16 reference) dropping that group by one quant tier will cause. The quantizer then spends its byte budget protecting the predicted damage leaders.
Earlier Gnom nets shipped as .npz blobs with the feature contract living in
code. These artifacts are self-describing .safetensors: weights + feature
contract + per-family normalization + measured scores live in one file.
Small by design: the deploy artifact is 75 MLPs Γ 2,209 parameters = 166,675 params total, 1.35 MB on disk.
Files
| file | role |
|---|---|
gnom_kfold_v5_2026-10-31.safetensors |
deploy β 5-fold within-family, 3 seeds Γ 5 folds per family |
gnom_ensemble_v4_2026-10-31.safetensors |
strict leave-one-family-out transfer exam (the conservative floor) |
gnom_multinet_v3_2026-10-30_lfm3ext_v2.safetensors |
single-seed per-family, clean pool (history) |
gnom_multinet_v3_2026-10-30_lfm3ext.safetensors |
first pass, unfiltered pool (history) |
gnom_v1_2026-10-30_lfm3ext.safetensors |
legacy single net, all rows (history) |
pool_fracture_lfm3_2026-10-30_v2.json |
the exact pool both current artifacts were trained on |
netpred/netpred_ling_v5_2026-10-31.json |
v5 per-group damage table for Ling-3.0-tiny (measured) |
netpred/netpred_ornith_v5_2026-10-31.json |
v5 table for Ornith-1.5-9B (generated; dry-run only) |
results/ling{4500,5100}wfgnomv5_result.json |
build + PPL/KLD provenance for the measured Ling arms |
load_gnom.py |
reference loader + self-test (both schemas) |
code/ |
trainer (train_gnom.py), featurizer (build_features.py), packer (pack_gnom.py), tinynet.py |
Usage
from load_gnom import load, predict
st, nets, meta = load("gnom_kfold_v5_2026-10-31.safetensors")
X = build_unit_rows(...) # 20-col contract, see load_gnom.py docstring
damage = predict(st, nets, "rl17", X) # higher = more damaging
Family selection: rl17 = Spark-X2.5-1.7B, rl4b = Spark-X2.5-4B,
lfm3 = LFM3-1.7B, rl9b = Ornith-1.0-9B, ling_moe = Ling-3.0-tiny.
Self-test (reproduces the stored exam numbers from the artifact + pool):
python load_gnom.py gnom_kfold_v5_2026-10-31.safetensors
The self-test reads the lab paths stored in the pool file, so it expects a
MERNIK checkout with gnom/features/ and the labels present (see Provenance).
The load/predict API itself has no path dependencies.
Two exams, two numbers
Clean 5-family Fracture pool, n=1165 units; quantizer pins (exact-zero damage: F32 1D/norms/ssm/rope tensors) filtered out β no signal there.
| family | 5-fold within-family (deploy) | LOMO floor (new family) |
|---|---|---|
| rl17 (1.7B) | +0.738 | +0.714 |
| rl4b (4B) | +0.615 | +0.708 |
| lfm3 (3rd arch) | +0.659 | +0.522 |
| rl9b (Ornith-9B) | +0.489 | β0.169 |
| ling_moe (MoE) | +0.134 | β0.228 |
| mean | +0.527 | +0.309 |
- Within-family (v5, deploy): 5-fold grouped folds over unit tags (all tiers of a unit share a fold β no same-unit leakage), 3 seeds per fold. Every unit is scored by nets that never saw it. This is the number to expect when quantizing one of these five families.
- LOMO (v4, floor): each family's net trained strictly without that family. Use as the conservative expectation for a genuinely new architecture.
The exams differ in folds and preprocessing, so the columns are two views, not a controlled delta (rl4b's LOMO is slightly above its in-family OOF for that reason).
v5 is not a marginal retrain β it is the first Gnom generation that beats the old production tables on a real model. Agreement with measured per-group damage, same groups and labels:
| model | previous production table | v5 ensemble | v5 honest OOF |
|---|---|---|---|
| Ornith-1.5-9B | +0.398 | +0.660 | +0.489 |
| Ling-3.0-tiny (MoE) | β0.022 | +0.419 | +0.134 |
The Ling row is the important one: the old table had no rank signal at all on the MoE (β0.022); v5 does. The ensemble column is optimistic (folds share neighbours through the fold-averaged table); the OOF column is what to expect on unseen units of the same family.
First measured deployment β Ling-3.0-tiny (2026-10-31)
The v5 deploy tables were measured on the weakest family on purpose: the MoE.
Per-group raw predictions (netpred/netpred_ling_v5_2026-10-31.json) were fed
to both allocators on the same BF16 base and imatrix; protocol unchanged
(ref_lingq8_wiki.dat, wiki 297Γ1024, -c 1024 -b 128 --seed 7).
Held-out agreement with measured group damage on Ling: +0.419 ensemble (honest OOF +0.134) vs β0.022 for the previous production table β the old table carried no Ling signal at all, so any non-zero ranking here is new.
| build | MiB | PPL | KLD mean | KLD median | KLD max |
|---|---|---|---|---|---|
| stock Q4_K_M (vendor, no imatrix) | 4600 | 13.327 | 0.114020 | 0.048512 | 14.354789 |
| NETPURE 4500 (old table, greedy) | 4507 | 13.503 | 0.095092 | 0.039573 | 8.716 |
| v5 water-fill 4500 | 4516 | 13.0555 | 0.070794 | 0.028326 | 9.388 |
| NETPURE 5100 (old table, greedy) | 5091 | 13.4271 | 0.089046 | 0.037072 | 9.071 |
| v5 greedy top-down 5100 | 5113 | 13.1311 | 0.062827 | 0.024551 | 10.695 |
| v5 water-fill 5100 | 5116 | 13.1289 | 0.050744 | 0.019695 | 9.858 |
At 4500 the v5 water-fill dominates the previous build on every column; at 5100 it buys the mean/median (β43% / β47% vs NETPURE) and pays the tail (max +8.7%). The greedy NETPURE builds still hold the lowest max KLD. Operator generation check was positive (informal, not a battery); HE was not run β the capability column sat on the floor at 4500 and the open question was fidelity per byte.
Against the vendor Q4_K_M, the heavier file loses. The 4500 arm is 84 MiB lighter than stock Q4_K_M (4516 vs 4600 MiB) and beats it on every column: PPL β2.0%, mean KLD β37.9%, median β41.6%, max KLD β34.6%, RMS Ξp 7.51% vs 9.41%, same-top-p 88.65% vs 85.80%. The 5100 arm costs +516 MiB but widens the gap: mean β55.5%, median β59.4%, max β31.3%, PPL β1.5%, same-top-p 90.43%. Stock Q4_K_M is the no-imatrix, no-allocator control β the win is fidelity per byte and per capability, not a bigger file.
Ornith table netpred/netpred_ornith_v5_2026-10-31.json is generated and
dry-run checked, not yet measured in a build.
Scope and known limits
- Dense decoder families (Spark 1.7B/4B, LFM3 hybrid): strong β 0.60β0.74.
- Ornith 9B: moderate in-family (0.45), negative strictly cross-family.
- MoE (Ling-3.0-tiny): weak even in-family (0.16). The 6-type one-hot covers dense tissue names only; expert routing/MLA structure is not represented in these features. Treat MoE support as open research, not a product claim.
- Single-seed numbers scatter (see metadata
per_seed_oof); always use the ensemble. Never quote a single seed alone.
Artifact schema (v5)
- tensors:
<family>/f<fold>/mu,<family>/f<fold>/sd(20,) β per-fold standardization;<family>/f<fold>/s<seed>/W1..b3β mish MLP (20β48β24β1) - v4 layout (kept for the transfer exam):
<family>/mu,<family>/sd,<family>/s<seed>/W1..b3 - metadata
nets: per-family dims, folds, seeds, fold_seed, oof_spearman, per_seed_oof, per_fold, n_units (group), n_rows - metadata
feature_order: the 20 input columns (18 raw + base_bpw + drop_bpw) - metadata
pool_sha: sha256[:16] of the training pool file - activation
mish, targetdamage_kld, epochs 1500, seeds {7,8,9}
Roundtrip is verified: reload β per-fold (xβmu)/sd β average fold Γ seed mish
forwards reproduces each stored score to 1e-9.
Provenance
- labels: wepiqx/MERNIK-Fracture
(KLD damage per tensor group; direct measurement, not PPL proxy; the
*_rankonly_*legs are what training consumes) - features:
features/*.npzin the same dataset (18 raw per-tensor columns + imatrix scalars; the 20-col contract addsbase_bpw/drop_bpw) - training code:
code/train_gnom.py+code/tinynet.py(this repo); featurizercode/build_features.pyneeds a MERNIK checkout (gguf/imatrix readers) - packer:
code/pack_gnom.py(kept in sync withscripts/pack_gnom.pyin wepiqx/MERNIK) - built: 2026-10-31, MERNIK lab