glm53_mtp_bf16.py 3.7 KB

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