Land the MeroMero v2-31B NVFP4A16 quant and record the pinned-transformers trap

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.
This commit is contained in:
vh
2026-09-10 10:56:16 -07:00
parent b8dbe71a1c
commit 1a5bc2ddf1
19 changed files with 1393 additions and 17 deletions
+46
View File
@@ -471,6 +471,47 @@ Reference: `services/erp-seat-quant/quant_nvfp4a16_gemma4_moe.py` (linearize_moe
11,520 expert Linears + post-steps; the published `prithivMLmods/gemma-4-26B-A4B-it-NVFP4A16`
recipe replicated, 222→252 ignore entries with audio/norm/router regexes added).
**So for any `*A16` scheme, do not pass a dataset at all** — not a shorter one, none. It removes
the §3.14 tokenizer bake-in *and* llm-compressor's "initialize model processor ... required when a
dataset is provided" demand, which is fatal on any upload that ships no `processor_config.json`.
Both of those cost an attempt on MeroMero v2 (2026-09-10); dropping the corpus costs nothing,
because a `DataFreePipeline` was never going to read it. Driver:
`services/meromero-quant/quant_a16_datafree.py`. **Confirmed twice more the same day**: the A4B
heretic quant, run *with* the corpus, shipped `max_length: 8192` in its `tokenizer.json`; the v2
dense, run without it, came out `truncation: null`.
### 3.17 ⭐⭐ The transformers you measured is not the transformers that ran — llm-compressor pins it
**Measured 2026-09-10, MeroMero v2-31B, and it cost a full misdiagnosis.** The quant died in
`AutoTokenizer.from_pretrained` with
`AmbiguousGlobalPerLayerAttributeError: 'head_dim' is a per-layer attribute`. The obvious reading
was that the source config carried a `per_layer_config` key the canonical one lacked, so that key
was the defect. It was not.
`pip install llmcompressor==0.13.0` **downgrades transformers underneath you** — 5.16.1 → 5.14.1 in
the `vllm/vllm-openai` image. The config had been 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, so it refuses the global read. Under the image's own 5.16.1 the very
same config loads fine, which is exactly what makes this class expensive: **the version you print
at the top of the script is not the version the quant runs on.**
- **Print the version AFTER the install**, in the same container, and put it in the log.
`python3 -c 'import transformers; print(transformers.__version__)'` as a pipeline step, not a
thing you check by hand once.
- **Pin the image by digest for the length of a campaign.** `:latest` was re-pulled between
attempts 3 and 4 of this run and moved the toolchain mid-diagnosis, so the same command produced
a different error for reasons that had nothing to do with the change under test.
- **Reproduce the actual failing call, not a paraphrase.** A bare `AutoConfig.from_pretrained` did
not reproduce this at all — the trigger was reached through `AutoTokenizer`. Testing the config
in isolation would have "cleared" it.
- **Keep a known-good tree as the positive control.** zerofata's canonical v2, quantized cleanly
three weeks earlier, is what separated "this config is malformed" from "this toolchain moved".
Without it, four green variants and one red one are just noise.
Related but distinct from §3.4, which is about version deadlocks you can *see*. This one is a
silent downgrade inside a line you already trusted. Instruments: `services/meromero-quant/`
(`tok_repro.py`, `perlayer_test.py`).
### 3.14 ⭐⭐ Calibration BAKES a truncation cap into the shipped tokenizer
**Symptom (on a newer transformers, at startup, on a vision model):**
@@ -579,6 +620,10 @@ Never optional, always in this order, and the last one **verifies rather than as
Reference implementation: `services/gen-seat-mixed-quant/post_quant.py`.
On Gemma-4 steps 1 and 3 are N/A — the family ships no MTP head at all — which leaves 2 and 4, and
4 is the one that fires. `services/meromero-quant/post_quant_gemma4.py` runs them idempotently with
a `--check` mode; point it at a tree you already trust before you trust its verdict on a new one.
### 4.4 Test on a temp port, never on the live seat
Serve the candidate on an alt port with the live seat's **exact** flags, run the gate (§5), and
@@ -698,6 +743,7 @@ above, and where the two disagree, **this file wins**.
|---|---|
| `services/gen-seat-mixed-quant/` | **current reference.** Mixed NVFP4+FP8 on Qwen3.8-27B-Uncensored: scripts, acceptance harness, raw measurements. |
| `stacks/gen-seat/README.md` | the live `gen` seat (7 LiteLLM aliases) |
| `services/meromero-quant/` | NVFP4A16 on Gemma-4 (MeroMero A4B + v2-31B ablits): the five-attempt failure chain, the pinned-transformers trap (§3.17), and the GPU-free verification instruments. |
| `stacks/meromero-charrp/README.md` | Gemma-4 seat — the **tool-call/reasoning-parser** trap (a parser default that returns null `content` for all prose) |
| `services/heretic2-nvfp4-quant/` | modelopt-format MTP seat — historical; see §7 before following it |
| `tools/mistral-small4-nvfp4/` | MoE + native-convert path; source of §3.6 |