| |
| |
| """Register Glm5vForConditionalGeneration as a DSA-capable architecture in SGLang. |
| |
| WHY THIS PATCH IS NECESSARY (not an environment workaround): |
| SGLang gates the DeepSeek-Sparse-Attention path on a hardcoded architecture |
| tuple in ``is_deepseek_dsa`` (``srt/configs/model_config.py``). Upstream's own |
| DSA vision-language model (Mistral Large 3) is supported by adding its VLM |
| arch ``PixtralForConditionalGeneration`` to exactly this tuple in-tree — that |
| is the upstream mechanism; no env-var/out-of-tree hook exists for it (unlike |
| models/processors/mm-arch, which we register via the shipped |
| ``SGLANG_EXTERNAL_*`` hooks). Our GLM5V wrapper needs the same one-line entry |
| so the DSA attention backend (and the ``server_args`` DSA backend defaults) |
| recognize the model whose text backbone is GlmMoeDsa. |
| |
| Three insertions, each at a place where upstream registered its own DSA/MLA |
| VLM archs (KimiK25 / Pixtral precedents): |
| |
| 1. ``model_config.py``: the ``is_deepseek_dsa`` arch tuple — gates the DSA |
| attention backend; |
| 2. ``model_config.py``: the MLA attention-arch condition — without it the |
| KV pool is built as MHA and the DSA backend crashes on |
| ``dsa_kv_cache_store_fp8``; |
| 3. ``server_args.py``: the DSA model-adjustments gate list — sets the DSA |
| dense-attn kv-len threshold (= model index_topk) and the prefill/decode |
| DSA backend defaults (trtllm on Blackwell); without it decode dies with |
| ``Unsupported self.dsa_decode_impl = None``. |
| |
| A 4th insertion (added later) enables MTP/NEXTN speculative decoding: without |
| it the draft worker rebuilds the entire ~90GB multimodal model as the "draft" |
| and OOMs (our wrapper arch is in none of SGLang's draft-model rewrite lists). |
| See the ``DRAFT_ANCHOR`` block below. This is only needed when serving with |
| ``--speculative-algorithm NEXTN``; it is a no-op otherwise. (Measured: MTP gives |
| ~1.1-1.16x tok/s at low/moderate batch + ~2.0 accept-len, greedy outputs are |
| token-identical to non-spec, but it is graph-capped at batch 256 so plain |
| high-batch TP8 wins for throughput-bound RL rollout — MTP is a single-stream |
| LATENCY lever, not a throughput one.) |
| |
| Other in-tree arch lists mentioning GlmMoeDsa/KimiK25 are deliberately NOT |
| patched: they gate features we do not use (encoder disaggregation) or pure |
| performance defaults we set explicitly (flashinfer allreduce fusion, |
| attention-backend fallback). |
| |
| The same entries will be needed wherever slime launches SGLang rollout engines |
| for glm5v (Milestone 1) — candidate for an upstream sglang PR. |
| |
| Idempotent; run before ``sglang.launch_server`` (serve.sh does this). |
| """ |
|
|
| import pathlib |
| import subprocess |
| import sys |
|
|
| ARCH = "Glm5vForConditionalGeneration" |
| DSA_ANCHOR = '"GlmMoeDsaForCausalLM",' |
| DSA_INSERT = ( |
| DSA_ANCHOR |
| + f'\n "{ARCH}", # glm5v: GLM-5.2 (DSA) text + MoonViT vision (sglang_glm5v)' |
| ) |
| MLA_ANCHOR = 'or "KimiK25ForConditionalGeneration" in self.hf_config.architectures' |
| MLA_INSERT = ( |
| MLA_ANCHOR |
| + f'\n or "{ARCH}" in self.hf_config.architectures # glm5v (sglang_glm5v)' |
| ) |
|
|
|
|
| |
| |
| SA_ANCHOR = ( |
| '"KimiK25ForConditionalGeneration",\n' |
| ' "MistralLarge3ForCausalLM",\n' |
| ' "PixtralForConditionalGeneration",\n' |
| ' "GlmMoeDsaForCausalLM",' |
| ) |
| SA_INSERT = SA_ANCHOR + f'\n "{ARCH}", # glm5v (sglang_glm5v)' |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| DRAFT_ANCHOR = ( |
| " def _config_draft_model(self):\n" |
| " is_draft_model = self.is_draft_model\n" |
| ) |
| DRAFT_INSERT = ( |
| DRAFT_ANCHOR |
| + "\n" |
| + " # glm5v (sglang_glm5v): map the multimodal wrapper draft to its\n" |
| + " # text backbone so the GlmMoeDsa->NextN rewrite below applies.\n" |
| + f' if is_draft_model and self.hf_config.architectures[0] == "{ARCH}":\n' |
| + " self.hf_config = self.hf_text_config\n" |
| + ' self.hf_config.architectures = ["GlmMoeDsaForCausalLM"]\n' |
| ) |
|
|
|
|
| def find_module(mod: str) -> pathlib.Path: |
| out = subprocess.run( |
| [sys.executable, "-c", f"import {mod} as m; print(m.__file__)"], |
| capture_output=True, |
| text=True, |
| check=True, |
| ) |
| return pathlib.Path(out.stdout.strip()) |
|
|
|
|
| def apply(path: pathlib.Path, anchor: str, insert: str, marker: str, label: str) -> None: |
| src = path.read_text() |
| if marker in src: |
| print(f"{label}: already patched") |
| return |
| assert anchor in src, f"{label}: anchor not found in {path} (sglang version changed?)" |
| path.write_text(src.replace(anchor, insert, 1)) |
| print(f"{label}: patched ({path})") |
|
|
|
|
| def main() -> None: |
| mc = find_module("sglang.srt.configs.model_config") |
| apply(mc, DSA_ANCHOR, DSA_INSERT, f'"{ARCH}", # glm5v', "DSA tuple") |
| apply(mc, MLA_ANCHOR, MLA_INSERT, f'or "{ARCH}" in self.hf_config.architectures', "MLA condition") |
| apply( |
| mc, |
| DRAFT_ANCHOR, |
| DRAFT_INSERT, |
| f'if is_draft_model and self.hf_config.architectures[0] == "{ARCH}"', |
| "NEXTN draft-model map", |
| ) |
| sa = find_module("sglang.srt.server_args") |
| apply(sa, SA_ANCHOR, SA_INSERT, f'"{ARCH}", # glm5v', "server_args DSA gate") |
|
|
| |
| check = subprocess.run( |
| [ |
| sys.executable, |
| "-c", |
| ( |
| "from sglang.srt.configs.model_config import is_deepseek_dsa;" |
| f"print(is_deepseek_dsa({{'architectures': ['{ARCH}'], 'index_topk': 2048}}))" |
| ), |
| ], |
| capture_output=True, |
| text=True, |
| check=True, |
| ) |
| assert check.stdout.strip() == "True", check.stdout + check.stderr |
| print("verified: is_deepseek_dsa recognizes the arch") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|