"""Post-step on GLM-5.3-Flash-NVFP4-FP8ATTN: quantize lm_head to block-FP8. Composable AFTER convert_fp8attn.py (r2 content). Targets the fp8attn-r4 image, whose modelopt.py dispatches ParallelLMHead + FP8_BLOCK128 to _Fp8BlockLMHeadMethod (vocab-block-sharded scale loader). lm_head.weight BF16 [154880, 4096] -> F8_E4M3 + weight_scale_inv F32 [1210, 32], block [128,128], dequant multiplier amax/448 — identical scheme/derivation to convert_fp8attn.py's quant_block. Geometry: 154880 % 128 == 0 and the TP2 vocab shard 77440 % 128 == 0, so weight and scale shard exactly on block boundaries. config.json: remove "lm_head" from quantization_config.ignore (its substring match would keep the head excluded) and add quantized_layers["lm_head"] = {"quant_algo": "FP8_BLOCK128"}. Reversible: the touched shard, config.json and the index are snapshotted as *.pre-lmhead first (shard via hardlink — zero cost). `--revert` puts all three back and removes the snapshots. embed_tokens stays BF16 (gather semantics: VocabParallelEmbedding never receives a quant method). Source NVFP4 dir untouched (all writes are new-file + os.replace). """ import json import os import struct import sys import time import ml_dtypes import numpy as np DST = None # set from --dir in __main__ TENSOR = "lm_head.weight" BLOCK = 128 SUFFIX = ".pre-lmhead" def read_header(path): with open(path, "rb") as f: n = struct.unpack("