The v2 dense quant had failed four times. Attempt 5 lands it at 19 G. The blocker was not what it looked like. `AmbiguousGlobalPerLayerAttributeError` on `head_dim` read as a malformed upload -- DogOnKeyboard's config carries a `per_layer_config` key zerofata's canonical one lacks -- and the standing fix was to force `allow_global_per_layer_attribute_access=True`. Both halves were wrong. `pip install llmcompressor==0.13.0` downgrades transformers 5.16.1 -> 5.14.1. The config was serialized by 5.16.1, which materializes `per_layer_config` from `global_head_dim` + `layer_types`; 5.14.1 carries the heterogeneity guard but not the gemma4 resolver. Under the image's own transformers the same config loads fine. `:latest` was also re-pulled during attempt 4 and no earlier run, so the toolchain moved mid-diagnosis. Two things separated "malformed upload" from "moved toolchain": reproducing the real failing call (a bare AutoConfig load does not reproduce it; the trigger is reached through AutoTokenizer) and keeping zerofata's canonical tree, quantized cleanly on 2026-08-21, as a positive control. The fix drops `per_layer_config` rather than forcing global access. It is exactly redundant -- keys are precisely the ten full_attention layer indices, sole value (512, 4), verbatim the global fields -- and forcing instead would make `config.head_dim` answer 256 to the callers building the 512-wide layers. patch_perlayer.py re-proves that redundancy at apply time and refuses if it ever stops holding. Verified on the tensor table rather than the exit code: the output is identical family-for-family and count-for-count to the August canonical quant, with 356 BF16 vision-tower tensors preserved and input_activations=None. A GPU-free load leaves 0 tensors on meta and generates coherent prose. The section 4.4 serve test has NOT run -- GPU1 has 19.9 GB free against 19.5 GB of weights, so it needs a live seat displaced. Also fixes the A4B output, which had a truncation cap baked into its tokenizer (max_length 8192) from being quantized with the calibration corpus. Playbook gains section 3.17 for the pinned-transformers class and sharpens 3.16 to say drop the dataset outright for any A16 scheme.
7.7 KiB
[2026-09-10] MeroMero — both quants landed; the v2 dense took five attempts
Operator wanted a MeroMero seat. Getting there cost five quant attempts and corrected three wrong hypotheses (two of the previous session's, one of mine), so the failure chain is the durable part. Both outputs now exist and are verified against a known-good tree. Neither is serving yet.
The family, because I got it wrong first
I called the unsuffixed 31B "v1" because v2 is v2-31B. The operator caught it. Creation dates from
the upstream author settle it:
2026-04-15 zerofata/G4-MeroMero-26B-A4B 30 layers, kv 8 MoE 128 experts ← the ORIGINAL
2026-04-30 zerofata/G4-MeroMero-31B 60 layers, kv 16 DENSE ← a separate line
2026-07-31 zerofata/G4-MeroMero-v2-31B 60 layers, kv 16 DENSE
Not a v1→v2 sequence at one size: an A4B MoE first, then a dense 31B line that later got a v2.
⚠ The -v1- repo 401s, and I read that as "v1 must be the unsuffixed one" instead of checking dates.
The geometry is why it matters. The A4B is 30 layers / kv 8 — identical to Pfish-6 (a gemma4 26B-A4B) — so it drops into the existing KV budget at 262k. The dense 31B is 60 layers / kv 16, ~4x the KV per token, and did NOT fit 262k on GPU1 alongside the other seats.
Published quants: none of them are our scheme
pekkAi/G4-MeroMero-31B-NVFP4 true v1 dense, W4A4
llmfan46/G4-MeroMero-31B-uncensored-heretic-NVFP4 abliterated dense, W4A16_NVFP4 (pulled, 19 GB)
pekkAi/G4-MeroMero-26B-A4B-it-...-heretic-NVFP4 abliterated A4B, W4A4 + FP8 KV baked in
NVFP4 of v2-31B DOES NOT EXIST, from anyone (0 of 27 v2 repos)
W4A4 carries our own measured long-context collapse (Granite, 30k ctx), which is why every seat we run is A16. So the operator ruled: pull both ablits bf16 and quantize in-house at W4A16.
MTP — checked on the SOURCES, not inferred
Gemma-4 ships no MTP head at all: 0 mtp tensors and no mtp config keys in the A4B bf16, the v2 bf16, Pfish-6, or the published quants. The "mtp if applicable" clause is a no-op here; the MTP work in our history is Qwen-family.
A4B — SUCCEEDED
G4-MeroMero-26B-A4B-it-uncensored-heretic-NVFP4A16, 16 G, 7 files, 2m08s. That time confirms
playbook §3.16: weight-only A16 runs a DataFreePipeline and never touches the dataset.
⚠ Output is missing preprocessor_config.json — §4.3 post-step 2 still owed before serving.
v2 dense — FOUR failures, each a different layer
num_key_value_headsis None atGemma4TextAttention.__init__. I hypothesised a transformers version deadlock (config authored by 5.16.1, container on 5.12.1) — wrong: all three configs construct fine on 5.12.1. Then I hypothesised the staletorch_dtypekwarg reshaping the config — also wrong: every kwarg combination returns 16. ⚠ A 2-layer truncation PASSED and hid it. The failing local is chosen per layer type; onlyfull_attentionlayers take the branch. Testing each layer type individually found it in seconds. Real cause: DogOnKeyboard's config setsattention_k_eq_v: truebut omitsnum_global_key_value_headsandglobal_head_dim— a malformed upload, not a toolchain issue. Patched from zerofata's canonical values (4 / 512) after shape-verifying the checkpoint: full-attn k_proj[2048,5376]= 4×512, sliding[4096,5376]= 16×256, identical to canonical. Original atconfig.json.bak-pre-kvfields-20260910.initialize model processor ... required when a dataset is provided— DogOnKeyboard ships noprocessor_config.json(the A4B does; that is why it succeeded). Copied from canonical, AND dropped the dataset entirely, since §3.16 says A16 ignores it and §3.14 says building it bakes a truncation cap into the shipped tokenizer. Removing it kills both for zero loss.- My own bug: the reference module runs argparse with
required=Trueat IMPORT, so blankingsys.argvstill exited 2. Placeholder args, real argv restored after. AmbiguousGlobalPerLayerAttributeError: 'head_dim' is a per-layer attribute— RESOLVED, and not what it looked like. See "The attempt-4 trap" below.- Clean.
rc=0, 19 G, 3m07s.
⚠ My wrapper reported rc=0 on a failed run because it read $? after an echo. A wrapper that
reports success on failure is the false-reassurance class; fixed to capture $? immediately.
The attempt-4 trap — the toolchain moved, the config was fine
The standing hypothesis was that DogOnKeyboard's per_layer_config key was the defect and
allow_global_per_layer_attribute_access=True was the fix. Both halves were wrong, and the second
half would have shipped a risk for no reason.
pip install llmcompressor==0.13.0 downgrades transformers 5.16.1 → 5.14.1. The config was
serialized by 5.16.1, which materializes per_layer_config out of global_head_dim +
layer_types; 5.14.1 has the heterogeneity guard but not the gemma4 resolver, so it refuses the
global read. Under the image's own 5.16.1 the identical config loads fine. On top of that,
vllm/vllm-openai:latest was re-pulled during attempt 4 and in no earlier run — the pull line is
in that block alone — so the error changed for reasons unrelated to anything under test.
Two things made this findable, and neither was inspection:
- Reproducing the real call. A bare
AutoConfig.from_pretraineddoes not reproduce it; the trigger is reached throughAutoTokenizer. Testing the config alone would have cleared it. - A known-good positive control. zerofata's canonical v2, quantized cleanly on 2026-08-21, is what separated "this upload is malformed" from "this toolchain moved". Four green variants and one red one are noise without it.
Fix: drop per_layer_config, don't force global access. It is exactly redundant — keys are
precisely the ten full_attention layer indices, sole value (512, 4), verbatim the global fields.
Forcing instead leaves the config heterogeneous and makes config.head_dim answer 256 to every
caller including the ones building the 512-wide layers; geometry survived it in a meta-device check,
but llmcompressor's onloading is an unaudited caller and that is what the warning is about. The
patch re-proves the redundancy at apply time and refuses if it ever stops holding.
What landed, and what is verified
G4-MeroMero-v2-31B-heretic-NVFP4A16— 19 G, and its tensor table is identical family for family and count for count to the 2026-08-21 canonical quant: 410 U8 packed + 410 F8_E4M3 + 410 F32 scales on the LM Linears, 356 BF16 vision-tower tensors preserved,input_activations=None(genuinely A16). Shard sizes match that tree byte for byte.- CPU load-and-generate: 0 tensors left on meta, decompresses, emits coherent prose. n=1, greedy, 24 tokens — an "is it wired up" check and nothing more.
- ⚠ The A4B output had the §3.14 truncation cap baked in (
max_length: 8192), because it was quantized with the corpus. Caught and fixed; backup attokenizer.json.bak-pre-truncfix. The v2, run data-free, came outtruncation: null.
Still owed
The §4.4 serve test has NOT run. GPU1 has 19.9 GB free against 19.5 GB of weights, so it cannot happen without displacing a live seat — operator's call. Until it does, "vllm servable" is unverified for this tree, and the dense 31B's 60-layer / kv-16 geometry still does not fit 262k on GPU1 beside the current tenants regardless.
Instruments and the full write-up: services/meromero-quant/. General lessons:
docs/pfi/model-quantization-playbook.md §3.16, §3.17 (new), §4.3.
Related: 2026-09-10-r49-babybronte-d1-d3-and-the-1-epoch-pilot