Download apply_patch.py from fishaudio/drama-3-int4: direct link, hf CLI and curl.
- Browser
- Download file 5.74 kB
-
https://huggingface.co/fishaudio/drama-3-int4/resolve/main/apply_patch.py
- Command line
-
hf download hf://fishaudio/drama-3-int4/apply_patch.py
-
curl -L -o apply_patch.py https://huggingface.co/fishaudio/drama-3-int4/resolve/main/apply_patch.py
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) | |