#!/usr/bin/env python3 """Mandatory post-steps after quantizing Qwen3.8-27B via the wrapper class. The wrapper-class save drops the MTP head and the vision preprocessor configs. All three of these have bitten previous rounds: 1. graft `model-mtp.safetensors` verbatim from the bf16 source and register its tensors in the output index (else no speculative decoding at all); 2. restore preprocessor_config.json / processor_config.json / video_preprocessor_config.json (else the vision tower can't preprocess); 3. VERIFY `re:^mtp.*` is in quantization_config.ignore -- if it is missing, vLLM loads the grafted bf16 MTP head as though it were quantized and it comes up uninitialised, giving 0% acceptance. This is THE bug that cost two prior rounds; it is verified here rather than assumed. """ import json, os, shutil, sys def main(): src, out = sys.argv[1], sys.argv[2] fail = [] # --- 1. MTP graft --------------------------------------------------------- # Two source layouts exist in the wild and both must work: # (a) a standalone `model-mtp.safetensors` -- how JonathanColetti ships its # grafted head, so a plain file copy suffices; # (b) mtp.* living inside a NUMBERED shard -- how MuXodious/absolute-heresy # ships (model-00012-of-00012.safetensors), because it is an unmodified # full checkpoint rather than a graft. # Handling only (a) leaves the output index pointing at a `model-mtp.safetensors` # that was never created: the checkpoint looks fine to a tensor count but every # mtp tensor is unresolvable at load. Extract instead of copy for (b). mtp_src = os.path.join(src, "model-mtp.safetensors") mtp_dst = os.path.join(out, "model-mtp.safetensors") if os.path.exists(mtp_dst): print("MTP shard already present in output") elif os.path.exists(mtp_src): print(f"copying MTP shard ({os.path.getsize(mtp_src)/1e9:.2f} GB) ...", flush=True) shutil.copy2(mtp_src, mtp_dst) else: # layout (b): materialise the standalone shard the index will reference idx_path = os.path.join(src, "model.safetensors.index.json") wm = json.load(open(idx_path))["weight_map"] shards = sorted({wm[k] for k in wm if k.startswith("mtp")}) if not shards: fail.append(f"no mtp.* in {src} (neither model-mtp.safetensors nor any shard)") else: from safetensors import safe_open from safetensors.torch import save_file print(f"extracting mtp.* from {shards} -> model-mtp.safetensors ...", flush=True) tensors = {} for shard in shards: with safe_open(os.path.join(src, shard), framework="pt") as f: for k in f.keys(): if k.startswith("mtp"): tensors[k] = f.get_tensor(k) save_file(tensors, mtp_dst, metadata={"format": "pt"}) print(f" wrote {len(tensors)} tensors, " f"{os.path.getsize(mtp_dst)/1e6:.1f} MB") src_idx = json.load(open(os.path.join(src, "model.safetensors.index.json"))) mtp_keys = [k for k in src_idx["weight_map"] if k.startswith("mtp")] out_idx_p = os.path.join(out, "model.safetensors.index.json") # A quant that lands under ~23 GB fits in ONE shard, and llm-compressor then # writes a bare `model.safetensors` with NO index at all. Every step below # needs one, so build it here rather than failing. # # Read the safetensors HEADER directly -- the first 8 bytes are a # little-endian u64 header length, followed by that many bytes of JSON # keyed by tensor name. Do NOT use safe_open() for this: it mmaps the whole # shard and ENOMEMs on ZFS against a 22 GB file. # # This has now bitten THREE separate rounds (2026-08-15, -08-20, -08-21), # each time fixed by hand and never in the script. Fixed in the script. if not os.path.exists(out_idx_p): import struct weight_map, total = {}, 0 for fn in sorted(f for f in os.listdir(out) if f.endswith(".safetensors")): path = os.path.join(out, fn) total += os.path.getsize(path) with open(path, "rb") as fh: n = struct.unpack("