drama-3-int4 / apply_patch.py
zhaochenyang20's picture akhaliq's picture
akhaliq HF Staff
Duplicate from akhaliq/drama-3-int4
525f312
Raw History Blame Contribute Delete
5.74 kB
"""Teach SGLang-Omni's Drama 3 loader the full int4 checkpoint (quant_method "drama3-int4").
Routed FusedMoE experts run SGLang's moe_wna16 kernel (W4A16, GPTQ packing, symmetric, group 128);
the dense target linears (full-attention q/k/v/o, gated-deltanet in_proj_qkv/in_proj_z/out_proj,
shared-expert gate/up/down) run GPTQ-Marlin (standard GPTQ sym checkpoint fields, repacked by
GPTQMarlinLinearMethod.process_weights_after_loading); every other layer stays unquantized bf16.
Idempotent. Usage: python apply_patch.py /path/to/sglang-omni
"""
import sys
from pathlib import Path
root = Path(sys.argv[1]) / "sglang_omni/models/fishaudio_drama3"
q = root / "quantization.py"
s = q.read_text()
ADD = '''
# --- added by drama-3-int4 (int4 experts AND dense linears) ----------------------------------
import re as _re # noqa: E402
from sglang.srt.layers.quantization.gptq.gptq import ( # noqa: E402
GPTQMarlinConfig,
GPTQMarlinLinearMethod,
)
from sglang.srt.layers.quantization.moe_wna16 import MoeWNA16Config, MoeWNA16Method # noqa: E402
DRAMA3_INT4_QUANTIZATION_METHOD = "drama3-int4"
# Dense checkpoint-side prefixes that hold int4 GPTQ fields. Matches by checkpoint name, which
# is what sglang's weight_loader prefix strings carry (model.language_model.layers.N....).
# The checkpoint's quantization_config.dense.targets says exactly which dense linears were
# quantized offline; the default below matches the full drama3-int4 release. Never matches
# mlp.gate (router), mlp.shared_expert_gate, linear_attn.in_proj_a/b, conv1d, norms, embeddings.
_ALL_DENSE_TARGETS = [
"self_attn.q_proj", "self_attn.k_proj", "self_attn.v_proj", "self_attn.o_proj",
"linear_attn.in_proj_qkv", "linear_attn.in_proj_z", "linear_attn.out_proj",
"mlp.shared_expert.gate_proj", "mlp.shared_expert.up_proj", "mlp.shared_expert.down_proj",
]
class Drama3Int4Config(QuantizationConfig):
"""int4 W4A16 everywhere it pays: moe_wna16 for FusedMoE, GPTQ-Marlin for dense targets."""
# SGLang fuses modules at load time; the loader prefix is the FUSED name while the
# checkpoint (and our targets list) uses per-projection names. Map back before matching.
MERGED = {"qkv_proj": ("q_proj", "k_proj", "v_proj"),
"gate_up_proj": ("gate_proj", "up_proj"),
"in_proj_qkvz": ("in_proj_qkv", "in_proj_z")}
def _checkpoint_names(self, prefix: str) -> list:
p = prefix.replace("model.layers.", "model.language_model.layers.", 1)
head, _, last = p.rpartition(".")
return [f"{head}.{n}" for n in self.MERGED.get(last, (last,))]
def __init__(self, group_size: int = 128, targets: list | None = None) -> None:
super().__init__()
full_moe = {"quant_method": "gptq", "bits": 4, "group_size": group_size,
"sym": True, "desc_act": False}
self.wna16 = MoeWNA16Config("gptq", 4, group_size, False, False, None, full_moe)
full_dense = {"quant_method": "gptq_marlin", "bits": 4, "group_size": group_size,
"sym": True, "desc_act": False}
self.marlin = GPTQMarlinConfig(4, group_size, False, True, False, {}, full_dense)
targets = _ALL_DENSE_TARGETS if targets is None else targets
self.dense_re = (_re.compile("|".join(_re.escape(t) for t in targets))
if targets else None)
def get_name(self) -> str:
return DRAMA3_INT4_QUANTIZATION_METHOD
def get_supported_act_dtypes(self) -> list[torch.dtype]:
return [torch.bfloat16, torch.half]
@classmethod
def get_min_capability(cls) -> int:
return 80
@staticmethod
def get_config_filenames() -> list[str]:
return []
@classmethod
def from_config(cls, config: Mapping[str, object]) -> "Drama3Int4Config":
if (config.get("quant_method") != DRAMA3_INT4_QUANTIZATION_METHOD
or config.get("bits") != 4 or not config.get("sym")):
raise ValueError(
f"expected quant_method={DRAMA3_INT4_QUANTIZATION_METHOD!r}, bits=4, sym=True; "
f"got {dict(config)}"
)
gs = int(config.get("group_size", 128))
if gs <= 0 or gs % 32:
raise ValueError(f"group_size must be a positive multiple of 32; got {gs}")
dense_cfg = config.get("dense") or {}
targets = dense_cfg.get("targets") if isinstance(dense_cfg, Mapping) else None
return cls(gs, targets)
def get_quant_method(self, layer: torch.nn.Module, prefix: str) -> QuantizeMethodBase | None:
if isinstance(layer, FusedMoE):
return MoeWNA16Method(self.wna16)
elif isinstance(layer, LinearBase):
if (prefix and self.dense_re
and any(self.dense_re.search(n) for n in self._checkpoint_names(prefix))):
return GPTQMarlinLinearMethod(self.marlin)
return UnquantizedLinearMethod()
else:
return None
def get_scaled_act_names(self) -> list[str]:
return []
'''
if "Drama3Int4Config" not in s:
q.write_text(s + ADD)
e = root / "engine_builder.py"
s = e.read_text()
if "Drama3Int4Config" not in s:
s = s.replace(" ExpertFp8Config,\n", " ExpertFp8Config,\n DRAMA3_INT4_QUANTIZATION_METHOD,\n Drama3Int4Config,\n", 1)
s = s.replace(" QUANTIZATION_METHODS[QUANTIZATION_METHOD] = ExpertFp8Config\n",
" QUANTIZATION_METHODS[QUANTIZATION_METHOD] = ExpertFp8Config\n"
" QUANTIZATION_METHODS[DRAMA3_INT4_QUANTIZATION_METHOD] = Drama3Int4Config\n", 1)
assert "QUANTIZATION_METHODS[DRAMA3_INT4_QUANTIZATION_METHOD]" in s, "engine_builder layout changed"
e.write_text(s)
print("patched", q, e)