| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980 |
- #!/usr/bin/env python3
- """Build GLM-5.3's MTP layer unquantised when the checkpoint stores it that way.
- nvidia/GLM-5.3-Flash-NVFP4 keeps its MTP layer (layers.45) entirely in BF16 --
- 889 tensors, no weight_scale anywhere -- but its config.json does not say so:
- quant_algo is NVFP4 for every Linear, and `ignore` lists 132 modules without
- mentioning layer 45. vLLM follows config.json (modelopt_fp4), builds the MTP
- experts as packed FP4, and the load dies in RoutedExperts._load_w2 with
- The size of tensor a (256) must match the size of tensor b (512)
- -- 512 is a BF16 w2 shard at TP=4, 256 the packed FP4 one. hf_quant_config.json
- (MIXED_PRECISION, whose quantized_layers omit layer 45) is right, but vLLM
- does not read it for this checkpoint.
- The fix follows the WEIGHTS rather than hard-coding a layer: if the checkpoint
- index holds no *weight_scale for this MTP layer, its prefix is appended to the
- quant config's exclude_modules as a ModelOpt wildcard before the layer is built.
- Every sublayer then takes the ordinary unquantised path -- ModelOpt returns
- UnquantizedLinearMethod for excluded Linears and no method for excluded
- RoutedExperts. A checkpoint that does quantise its MTP layer is left alone.
- One anchor, exactly once, or the build fails.
- """
- from pathlib import Path
- PATH = Path("/usr/local/lib/python3.12/dist-packages/vllm/models/glm5next/common/mtp.py")
- # The layer passes vllm_config to its decoder layer, which reads the shared
- # quant config itself, so the exclusion goes onto that object before it is built.
- OLD = """ config = vllm_config.speculative_config.draft_model_config.hf_config
- self.config = config
- """
- NEW = """ config = vllm_config.speculative_config.draft_model_config.hf_config
- self.config = config
- quant_config = vllm_config.quant_config
- # GLM53-MTP-BF16: see image/patches/glm53_mtp_bf16.py in spark-glm53.
- if quant_config is not None and _glm53_mtp_layer_unquantized(
- vllm_config.speculative_config.draft_model_config.model, prefix
- ):
- _excl = getattr(quant_config, "exclude_modules", None)
- if isinstance(_excl, list) and f"{prefix}.*" not in _excl:
- _excl.append(f"{prefix}.*")
- import logging
- logging.getLogger(__name__).info(
- "GLM53-MTP-BF16: %s has no quantisation scales in the "
- "checkpoint; building it unquantised", prefix)
- """
- HELPER_ANCHOR = "class Glm5NextMultiTokenPredictorLayer(nn.Module):\n"
- HELPER = '''def _glm53_mtp_layer_unquantized(model_dir, prefix):
- """True when the checkpoint carries no quantisation scales for this MTP layer.
- GLM53-MTP-BF16. Reads model.safetensors.index.json; any failure to read it
- means "do not second-guess the config", so the answer is False.
- """
- import json
- import os
- try:
- idx = int(prefix.rsplit(".", 1)[-1])
- with open(os.path.join(model_dir, "model.safetensors.index.json")) as f:
- keys = json.load(f)["weight_map"]
- except Exception:
- return False
- tag = f".layers.{idx}."
- layer_keys = [k for k in keys if tag in k]
- return bool(layer_keys) and not any(k.endswith("weight_scale") for k in layer_keys)
- '''
- text = PATH.read_text()
- if "GLM53-MTP-BF16" in text:
- print("[glm53-mtp-bf16] already applied")
- else:
- assert text.count(OLD) == 1, f"[glm53-mtp-bf16] constructor anchor matched {text.count(OLD)} times"
- assert text.count(HELPER_ANCHOR) == 1, "[glm53-mtp-bf16] class anchor not unique"
- text = text.replace(OLD, NEW).replace(HELPER_ANCHOR, HELPER + HELPER_ANCHOR)
- PATH.write_text(text)
- print("[glm53-mtp-bf16] MTP layer excluded from quantisation when the checkpoint has no scales for it")
|