vllm_fp8_hybrid.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. """vllm_fp8_hybrid — int4(GPTQ-Marlin) + blockwise-fp8 hybrid dispatch.
  2. The checkpoint's routed experts + lm_head are GPTQ int4/int8 (served by
  3. AutoGPTQConfig), while dense side-layers (GDN in_proj/out_proj, QSA q/k/v/o,
  4. shared experts) are stored as blockwise FP8-e4m3 (`weight` fp8 +
  5. `weight_scale_inv`, block 128x128). Stock AutoGPTQConfig would init those as
  6. unquantized bf16 and then fail to load fp8 tensors.
  7. This patch (enabled by VLLM_FP8_HYBRID=1, no-op otherwise):
  8. * after ``maybe_update_config``, scans the checkpoint's safetensors metadata
  9. for F8_E4M3 weights that have a ``.weight_scale_inv`` sibling and records
  10. those layer names;
  11. * maps them through the model's hf->vllm mapper alongside
  12. ``modules_in_block_to_quantize``;
  13. * in ``get_quant_method``, dispatches matching LinearBase layers (including
  14. fused qkv_proj / gate_up_proj via ``packed_modules_mapping``) to a shared
  15. blockwise ``Fp8Config`` instead of the GPTQ/unquantized path.
  16. Port of Saren's spark-dflash-hybrid-fp8 (vLLM 0.23 INCConfig) onto the
  17. qwen38next build's AutoGPTQConfig.
  18. """
  19. import logging
  20. import os
  21. logger = logging.getLogger("vllm.fp8_hybrid")
  22. _SENTINEL = "_fp8_hybrid_patched"
  23. def _index_filtered_metadata(model_name, revision=None):
  24. """flashnext-int4-b12x change to Saren's module: read each tensor's dtype from the file the INDEX names for it.
  25. The served-model view links PLE-table files that also carry the original checkpoint's layer-1 tensors, including a bf16
  26. shared expert. get_safetensors_params_metadata merges every file in the folder, so whichever file came last in directory
  27. order decided that layer's dtype -- different on the two nodes (812 vs 809 fp8 layers, 2026-09-21): the node that saw bf16
  28. built layer 1's shared expert unquantized and died loading its fp8 weights. No index (e.g. a hub id) -> stock behaviour.
  29. """
  30. import json
  31. import os
  32. import struct
  33. index = os.path.join(str(model_name), "model.safetensors.index.json")
  34. if not os.path.isfile(index):
  35. from vllm.transformers_utils.config import get_safetensors_params_metadata
  36. return get_safetensors_params_metadata(model_name, revision=revision)
  37. by_file = {}
  38. for name, fname in json.load(open(index))["weight_map"].items():
  39. by_file.setdefault(fname, set()).add(name)
  40. md = {}
  41. for fname, names in by_file.items():
  42. with open(os.path.join(str(model_name), fname), "rb") as fh:
  43. n = struct.unpack("<Q", fh.read(8))[0]
  44. header = json.loads(fh.read(n))
  45. for name in names:
  46. if name in header:
  47. md[name] = header[name]
  48. return md
  49. def apply() -> None:
  50. if os.environ.get("VLLM_FP8_HYBRID", "0").lower() not in ("1", "true", "yes"):
  51. return
  52. from vllm.model_executor.layers.quantization import auto_gptq as m
  53. cfg_cls = m.AutoGPTQConfig
  54. if getattr(cfg_cls, _SENTINEL, False):
  55. return
  56. from vllm.model_executor.layers.linear import LinearBase
  57. from vllm.model_executor.layers.quantization.fp8 import Fp8Config
  58. from vllm.transformers_utils.config import get_safetensors_params_metadata
  59. orig_update = cfg_cls.maybe_update_config
  60. orig_mapper = cfg_cls.apply_vllm_mapper
  61. orig_gqm = cfg_cls.get_quant_method
  62. def maybe_update_config(self, model_name, hf_config=None, revision=None):
  63. orig_update(self, model_name, hf_config=hf_config, revision=revision)
  64. md = _index_filtered_metadata(model_name, revision)
  65. self.fp8_layers = {
  66. name[: -len(".weight")]
  67. for name, info in md.items()
  68. if name.endswith(".weight")
  69. and info.get("dtype") == "F8_E4M3"
  70. and name[: -len(".weight")] + ".weight_scale_inv" in md
  71. }
  72. if self.fp8_layers:
  73. logger.info(
  74. "fp8 hybrid: %d blockwise-fp8 layers detected", len(self.fp8_layers)
  75. )
  76. def apply_vllm_mapper(self, hf_to_vllm_mapper):
  77. orig_mapper(self, hf_to_vllm_mapper)
  78. if getattr(self, "fp8_layers", None):
  79. self.fp8_layers = set(hf_to_vllm_mapper.apply_list(list(self.fp8_layers)))
  80. def _is_fp8_layer(self, prefix: str) -> bool:
  81. fp8_layers = getattr(self, "fp8_layers", None)
  82. if not fp8_layers:
  83. return False
  84. head, _, proj = prefix.rpartition(".")
  85. fused = self.packed_modules_mapping.get(proj)
  86. names = [f"{head}.{p}" for p in fused] if fused and head else [prefix]
  87. return all(any(l in n for l in fp8_layers) for n in names)
  88. def get_quant_method(self, layer, prefix):
  89. if isinstance(layer, LinearBase) and self._is_fp8_layer(prefix):
  90. fp8_cfg = getattr(self, "_fp8_cfg", None)
  91. if fp8_cfg is None:
  92. fp8_cfg = Fp8Config(
  93. is_checkpoint_fp8_serialized=True,
  94. activation_scheme="dynamic",
  95. weight_block_size=[128, 128],
  96. )
  97. fp8_cfg.packed_modules_mapping = self.packed_modules_mapping
  98. self._fp8_cfg = fp8_cfg
  99. logger.debug("fp8 hybrid: %s -> Fp8LinearMethod", prefix)
  100. return fp8_cfg.get_quant_method(layer, prefix)
  101. return orig_gqm(self, layer, prefix)
  102. cfg_cls.maybe_update_config = maybe_update_config
  103. cfg_cls.apply_vllm_mapper = apply_vllm_mapper
  104. cfg_cls._is_fp8_layer = _is_fp8_layer
  105. cfg_cls.get_quant_method = get_quant_method
  106. setattr(cfg_cls, _SENTINEL, True)
  107. logger.info("fp8 hybrid patch applied to AutoGPTQConfig")