aquaman164's picture
Upload folder using huggingface_hub
a622e04 verified
Raw
History Blame Contribute Delete
7.38 kB
# SPDX-License-Identifier: Apache-2.0
"""AutoRound(auto_round:auto_gptq, mixed 2/3/4/8-bit, v1) -> vLLM quant config.
Reuses OneComp's MixedGPTQConfig + its numerically-validated v1 kernels UNCHANGED
(grouped_moe / fused_dq_gemm / mixed_moe). The ONLY adaptation is parsing: AutoRound
stores per-module bits in a flat `extra_config` keyed by full module path + a global
`bits`(=16 "unquantized unless overridden"), `group_size`, `sym`; we convert that to
the plugin's per-layer `quantization_bits` list-of-dicts {suffix: {bits, method}}.
Non-overridden (global-16) modules are omitted -> dispatch returns UnquantizedLinear
(BF16) for router gate / indexer / norms / embed / lm_head.
Verified (see VLLM_M3_PORT_SPEC.md): all non-expert quantized modules are 4/8-bit
(stock AutoGPTQ Marlin), only routed experts are mixed 2/3/4/8 (MixedGPTQMoEMethod).
v1 +1 dequant convention is identical between mixed_gptq and auto_round:auto_gptq, so
the kernels are reused with zero numeric change -> no quantization-side degradation.
"""
import os
import re
import sys
sys.path.insert(0, os.environ.get("ONECOMP_PATH", "OneCompression"))
from vllm.model_executor.layers.quantization import register_quantization_config
from vllm.model_executor.layers.linear import LinearBase, UnquantizedLinearMethod
from vllm_plugins.gptq.vllm_plugin import MixedGPTQConfig
# Our AutoRound checkpoint stores gate_up_proj ALREADY-FUSED (one quantized tensor),
# unlike mixed_gptq which had separate gate_proj/up_proj. Drop it from the plugin's
# fused-constituent table so the within-shard validator treats it as a single module
# (qkv stays fused: the checkpoint DOES store q/k/v separately). Plugin file unchanged.
from vllm_plugins.utils import module as _oc_module
_oc_module._FUSED_TO_CONSTITUENTS.pop("gate_up_proj", None)
_LAYER_RE = re.compile(r"\.layers\.(\d+)\.(.+)$")
def autoround_extra_config_to_quantization_bits(extra_config: dict) -> list:
"""Flat AutoRound extra_config -> per-layer list[ {suffix: {bits, method}} ]."""
num_layers = 0
parsed = []
for full_key, v in extra_config.items():
bits = v.get("bits") if isinstance(v, dict) else None
if bits is None or bits >= 16:
continue # global-16 / unquantized -> omit -> BF16
m = _LAYER_RE.search(full_key)
if not m:
continue
layer_idx, suffix = int(m.group(1)), m.group(2)
num_layers = max(num_layers, layer_idx + 1)
parsed.append((layer_idx, suffix, int(bits)))
qbits = [dict() for _ in range(num_layers)]
for layer_idx, suffix, bits in parsed:
cfg = {"bits": bits, "method": "gptq"}
qbits[layer_idx][suffix] = cfg
# The official vLLM M3 renames the shared expert mlp.shared_experts.* ->
# block_sparse_moe.shared_experts.*, and _lookup_module_config matches by
# `official_suffix.startswith(cfg_name)`. Without an alias the official
# prefix misses the ckpt-named bits -> the module falls to bf16
# (UnquantizedLinearMethod) -> wasted ~0.85 GiB/rank for down_proj (and
# it then fails to load our quantized weight). Alias keeps it quantized.
# (gate_up_proj is still forced bf16 by get_quant_method's override since
# our fused gate_up can't load as one GPTQ tensor; this only helps the
# single-tensor down_proj.)
if suffix.startswith("mlp.shared_experts."):
alias = "block_sparse_moe.shared_experts." + suffix[len("mlp.shared_experts."):]
qbits[layer_idx][alias] = cfg
return qbits
@register_quantization_config("autoround_mixed")
class AutoRoundMixedConfig(MixedGPTQConfig):
"""MixedGPTQConfig fed from an AutoRound quantization_config dict."""
@classmethod
def get_name(cls) -> str:
return "autoround_mixed"
# The official vLLM M3 model FUSES gate_up (MergedColumnParallelLinear) and
# q/k/v(+indexer) (QKVParallelLinear / MinimaxM3QKVParallelLinearWithIndexer).
# Our checkpoint stores gate_up pre-fused and the sparse-layer indexer q/k in
# bf16, so these two fused linears can't be loaded as a single GPTQ tensor.
# m3_official_loader de-quants them to bf16; mark them unquantized here so the
# model builds bf16 linears that accept those weights. Everything else
# (routed experts, o_proj, down_proj, dense attn) stays on the normal path.
#
# GATED behind M3_OFFICIAL_PORT=1 (set only by serve_m3_official.py): the M1
# production model (m3_api_server.py) ALSO uses autoround_mixed and KEEPS
# gate_up/qkv quantized, so this override must NOT apply there (else KeyError
# 'qweight' at forward — the linear has no qweight after being unquantized).
def get_quant_method(self, layer, prefix: str):
import os
# Only the sparse-attn fused qkv_proj MUST be bf16 (q/k/v are quantized
# but fused with the bf16 indexer q/k -> can't be one GPTQ tensor;
# m3_official_loader de-quants it). gate_up_proj USED to be forced bf16
# too, but it is now kept QUANTIZED: the loader splits the fused GPTQ
# gate_up into separate quantized gate_proj/up_proj shards (no de-quant)
# -> reclaims ~2 GiB/rank for KV. (bits found via the per-module config;
# the shared expert relies on the block_sparse_moe.* alias added in
# autoround_extra_config_to_quantization_bits.)
if (os.environ.get("M3_OFFICIAL_PORT") == "1"
and isinstance(layer, LinearBase)
and prefix.endswith(".self_attn.qkv_proj")):
return UnquantizedLinearMethod()
return super().get_quant_method(layer, prefix)
@classmethod
def from_config(cls, config: dict) -> "AutoRoundMixedConfig":
extra = config.get("extra_config", {}) or {}
qbits = autoround_extra_config_to_quantization_bits(extra)
return cls(
quantization_bits=qbits,
group_size=config.get("group_size", 128),
desc_act=False,
sym=config.get("sym", True),
lm_head_quantized=False,
checkpoint_format="gptq", # auto_round:auto_gptq == GPTQ v1
)
# ---------------------------------------------------------------------------
if __name__ == "__main__":
import json
cfgp = os.path.join(os.environ.get("M3_CKPT", os.path.dirname(os.path.abspath(__file__))), "quantization_config.json")
cfg = json.load(open(cfgp))
qb = autoround_extra_config_to_quantization_bits(cfg.get("extra_config", {}))
print(f"layers in quantization_bits: {len(qb)}")
# spot-check a dense, a moe attn, and an expert
def show(L, needle):
hit = {k: v for k, v in qb[L].items() if needle in k}
# compress: print count + a sample
sample = dict(list(hit.items())[:2])
print(f" L{L} [{needle}]: n={len(hit)} sample={sample}")
show(0, "self_attn.q_proj")
show(0, "mlp.gate_up_proj")
show(3, "self_attn.q_proj")
show(3, "self_attn.o_proj")
show(3, "mlp.experts.0.")
show(3, "mlp.shared_experts")
show(59, "self_attn.o_proj")
# sanity: total quantized modules
tot = sum(len(d) for d in qb)
from collections import Counter
bitc = Counter(v["bits"] for d in qb for v in d.values())
print(f"total quantized modules: {tot} | bits histogram: {dict(sorted(bitc.items()))}")
print("M3_QUANT_SELFTEST_OK")