1a5bc2ddf1
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.
101 lines
3.9 KiB
Python
101 lines
3.9 KiB
Python
"""Attempt-4 blocker: is dropping `per_layer_config` the correct fix, or must we
|
|
force `allow_global_per_layer_attribute_access`?
|
|
|
|
The two candidates are NOT equivalent:
|
|
* DROP -> config becomes homogeneous in transformers' eyes and the global
|
|
`global_head_dim` / `num_global_key_value_heads` fields describe the
|
|
full-attention layers, exactly as zerofata's canonical config does.
|
|
* FORCE -> config stays heterogeneous; `config.head_dim` starts answering 256 to
|
|
every caller, including the ones building the 512-wide full-attention
|
|
layers. That is the hazard transformers' own warning names.
|
|
|
|
So this is not a "did the traceback go away" test. It builds the model on the meta
|
|
device from each candidate and reads the ACTUAL k_proj widths back, against the
|
|
checkpoint's measured 2048 (full) / 4096 (sliding). A candidate that constructs but
|
|
mis-shapes a layer is a worse outcome than the crash, because it would ship.
|
|
|
|
Positive control: zerofata's canonical config, which we already quantized
|
|
successfully on 2026-08-21, MUST pass every check here. If it doesn't, the
|
|
instrument is broken and none of the negatives mean anything.
|
|
"""
|
|
import json, shutil, tempfile, traceback
|
|
from pathlib import Path
|
|
|
|
import torch
|
|
import transformers
|
|
from transformers import AutoConfig
|
|
|
|
print(f"transformers {transformers.__version__} torch {torch.__version__}", flush=True)
|
|
|
|
HERETIC = Path("/tank/aimodels/G4-MeroMero-v2-31B-heretic-bf16")
|
|
CANON = Path("/tank/aimodels/meromero-v2-nvfp4-work/src")
|
|
|
|
# Measured off the checkpoints by shape_verify.py; both trees agree.
|
|
EXPECT = {"full_attention": 2048, "sliding_attention": 4096}
|
|
|
|
|
|
def variant(name, cfg_dict):
|
|
d = Path(tempfile.mkdtemp(prefix=f"cfg-{name}-"))
|
|
(d / "config.json").write_text(json.dumps(cfg_dict))
|
|
return name, d
|
|
|
|
|
|
heretic = json.loads((HERETIC / "config.json").read_text())
|
|
canon = json.loads((CANON / "config.json").read_text())
|
|
|
|
dropped = json.loads(json.dumps(heretic))
|
|
dropped["text_config"].pop("per_layer_config")
|
|
|
|
forced = json.loads(json.dumps(heretic))
|
|
forced["text_config"]["allow_global_per_layer_attribute_access"] = True
|
|
|
|
variants = [
|
|
variant("A-canonical-POSITIVE-CONTROL", canon),
|
|
variant("B-heretic-asis", heretic),
|
|
variant("C-heretic-drop-per_layer_config", dropped),
|
|
variant("D-heretic-force-global-access", forced),
|
|
]
|
|
|
|
for name, d in variants:
|
|
print(f"\n=== {name} ===", flush=True)
|
|
try:
|
|
cfg = AutoConfig.from_pretrained(d)
|
|
except Exception as e:
|
|
print(f" CONFIG FAILED: {type(e).__name__}: {str(e)[:160]}")
|
|
continue
|
|
t = cfg.text_config
|
|
fields = {}
|
|
for k in ("head_dim", "num_key_value_heads", "global_head_dim",
|
|
"num_global_key_value_heads"):
|
|
try:
|
|
fields[k] = getattr(t, k, "<absent>")
|
|
except Exception as e:
|
|
fields[k] = f"<{type(e).__name__}>"
|
|
print(f" config OK: {fields}")
|
|
|
|
try:
|
|
from transformers import Gemma4ForConditionalGeneration as M
|
|
with torch.device("meta"):
|
|
model = M(cfg)
|
|
except Exception:
|
|
print(" MODEL BUILD FAILED:")
|
|
print(" " + traceback.format_exc().strip().replace("\n", "\n ")[-1200:])
|
|
continue
|
|
|
|
layer_types = t.layer_types
|
|
probes = [next(i for i, x in enumerate(layer_types) if x == "full_attention"),
|
|
next(i for i, x in enumerate(layer_types) if x == "sliding_attention")]
|
|
layers = model.model.language_model.layers
|
|
verdict = []
|
|
for li in probes:
|
|
got = tuple(layers[li].self_attn.k_proj.weight.shape)
|
|
want = EXPECT[layer_types[li]]
|
|
ok = got[0] == want
|
|
verdict.append(ok)
|
|
print(f" L{li:>2} {layer_types[li]:<18} k_proj {got} "
|
|
f"want out={want} {'OK' if ok else '*** MISMATCH ***'}")
|
|
print(f" => {'GEOMETRY MATCHES CHECKPOINT' if all(verdict) else 'GEOMETRY WRONG'}")
|
|
|
|
for _, d in variants:
|
|
shutil.rmtree(d, ignore_errors=True)
|