#!/usr/bin/env python3 """Splice the 15 BF16 mtp.* tensors into the modelopt NVFP4 quant output → the servable seat. Runs AFTER quant_modelopt.py. Copies heretic2-modelopt-nvfp4 → heretic2-modelopt-nvfp4-mtp, splices the grafted BF16 mtp head into the single shard (transformers never builds an mtp module at load, so mtp is always post-quant-spliced — same as AEON/pantheon), and adds the mtp module names to config.json exclude_modules for tidiness. NOTE: the actual thing that keeps the mtp head BF16 at serve time is the sitecustomize MTP workaround (see runbook landmine #4); the config exclude here is belt-and-suspenders and does NOT by itself prevent the drafter-quant crash. Run in a vLLM container (root; /tank/aimodels files are root-owned): docker run --rm -v /tank/aimodels:/tank/aimodels -v /home/lkraven:/lk \ --entrypoint python3 vllm/vllm-openai:v0.24.0 /lk/finalize_modelopt_mtp.py """ import json import os import shutil from safetensors import safe_open from safetensors.torch import save_file WORK = "/tank/aimodels/heretic2-nvfp4-work" SRC = f"{WORK}/heretic2-modelopt-nvfp4" DST = f"{WORK}/heretic2-modelopt-nvfp4-mtp" GRAFT = f"{WORK}/heretic2-mtp-bf16" if os.path.exists(DST): shutil.rmtree(DST) print(f"copying {SRC} -> {DST}", flush=True) shutil.copytree(SRC, DST) out_st = f"{DST}/model.safetensors" # single shard (quant_modelopt.py forces max_shard_size huge) mtp_st = f"{GRAFT}/model-mtp.safetensors" tensors = {} with safe_open(out_st, framework="pt") as f: for k in f.keys(): tensors[k] = f.get_tensor(k) n_main = len(tensors) with safe_open(mtp_st, framework="pt") as f: mtp_keys = list(f.keys()) for k in mtp_keys: tensors[k] = f.get_tensor(k) assert not any("mtp" in k.lower() for k in list(tensors)[:n_main]), "output already had mtp?" save_file(tensors, out_st, metadata={"format": "pt"}) print(f"spliced {len(mtp_keys)} bf16 mtp tensors -> {len(tensors)} total", flush=True) cfgp = f"{DST}/config.json" cfg = json.load(open(cfgp)) qc = cfg.setdefault("quantization_config", {}) exc = qc.setdefault("exclude_modules", []) mtp_mods = sorted({k.rsplit(".", 1)[0] for k in mtp_keys}) exc.extend(m for m in mtp_mods if m not in exc) json.dump(cfg, open(cfgp, "w"), indent=2) print(f"config exclude_modules += {len(mtp_mods)} mtp modules; total {len(exc)}", flush=True) print(f"DONE: {DST}", flush=True)