harrypart commited on
Commit
697ef33
·
verified ·
1 Parent(s): 72a1733

engine plugin (vllm + sglang)

Browse files
plugins/._pyproject.toml ADDED
Binary file (163 Bytes). View file
 
plugins/._sglang_glm5v ADDED
Binary file (163 Bytes). View file
 
plugins/._vllm_glm5v ADDED
Binary file (163 Bytes). View file
 
plugins/pyproject.toml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "glm5v-serve"
7
+ version = "0.1.0"
8
+ description = "Serve GLM-5.2-Vision (GLM-5.2 text + MoonViT vision + PatchMerger projector) on vLLM and SGLang"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "Apache-2.0" }
12
+
13
+ # vLLM discovers plugins through this entry-point group and calls them during
14
+ # engine init — so `pip install glm5v-serve` is all that stands between a stock
15
+ # `vllm serve baseten/GLM-5.2-Vision-FP8` and a working VLM. No flags, no patches.
16
+ [project.entry-points."vllm.general_plugins"]
17
+ glm5v = "vllm_glm5v.register:register"
18
+
19
+ [tool.setuptools]
20
+ packages = ["vllm_glm5v", "sglang_glm5v"]
plugins/sglang_glm5v/.___init__.py ADDED
Binary file (163 Bytes). View file
 
plugins/sglang_glm5v/._glm5v.py ADDED
Binary file (163 Bytes). View file
 
plugins/sglang_glm5v/._patch.py ADDED
Binary file (163 Bytes). View file
 
plugins/sglang_glm5v/._patch_sglang_dsa_arch.py ADDED
Binary file (163 Bytes). View file
 
plugins/sglang_glm5v/._processor.py ADDED
Binary file (163 Bytes). View file
 
plugins/sglang_glm5v/__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """sglang_glm5v: out-of-tree SGLang support for GLM-5.2-Vision.
3
+
4
+ MoonViT vision tower + trained PatchMerger projector + GLM-5.2 text backbone.
5
+
6
+ Registered through SGLang's shipped external-package hooks — no in-tree edits:
7
+
8
+ export SGLANG_EXTERNAL_MODEL_PACKAGE=sglang_glm5v # models (EntryClass)
9
+ export SGLANG_EXTERNAL_MM_PROCESSOR_PACKAGE=sglang_glm5v # image processor
10
+ export SGLANG_EXTERNAL_MM_MODEL_ARCH=Glm5vForConditionalGeneration
11
+
12
+ Those hooks do not cover one thing: SGLang gates its DeepSeek-sparse-attention
13
+ path on a hardcoded architecture list. Register the arch there with
14
+
15
+ python -m sglang_glm5v.patch
16
+
17
+ See patch.py for why that is a genuine gap rather than a workaround.
18
+ """
plugins/sglang_glm5v/glm5v.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """glm5v on SGLang: MoonViT (Kimi-K2.5) vision tower + trained PatchMerger
3
+ projector + GLM-5.2 (GlmMoeDsa) text decoder.
4
+
5
+ Mirror of ``vllm_glm5v/glm5v.py`` for SGLang. It is SGLang's in-tree
6
+ ``KimiK25ForConditionalGeneration`` with the same three swaps we made on vLLM:
7
+
8
+ 1. text backbone ``DeepseekV3ForCausalLM`` -> ``GlmMoeDsaForCausalLM``
9
+ (GLM-5.2; SGLang ships it natively in ``glm4_moe.py``, DSA path included).
10
+ 2. projector ``linear_2`` output dim -> GLM hidden 6144, via
11
+ ``vision_config.text_hidden_size`` (pure config, no code).
12
+ 3. image placeholder -> GLM's ``<|image|>`` (154854), via
13
+ ``config.media_placeholder_token_id`` + the Glm5v processor (see
14
+ ``processor.py``; the model itself is placeholder-agnostic).
15
+
16
+ Only swap (1) needs code, so ``__init__`` is a copy of the parent's with the
17
+ language-model class replaced. Everything else (MoonViT tower, projector,
18
+ ``get_image_feature``, ``forward``, ``load_weights`` incl. the
19
+ ``mm_projector.proj.0 -> linear_1`` key remap) is inherited verbatim.
20
+
21
+ Vision tower + projector stay bf16: the parent passes ``quant_config=None`` to
22
+ the tower (unless ModelSlim) and builds the projector unquantized; only the GLM
23
+ text backbone sees the FP8 quant config.
24
+ """
25
+
26
+ import logging
27
+
28
+ from torch import nn
29
+
30
+ from sglang.srt.layers.quantization.modelslim.modelslim import ModelSlimConfig
31
+ from sglang.srt.layers.quantization.quark.quark import QuarkConfig
32
+ from sglang.srt.models.glm4_moe import GlmMoeDsaForCausalLM
33
+ from sglang.srt.models.kimi_k25 import (
34
+ K2VLMultiModalProjector,
35
+ KimiK25ForConditionalGeneration,
36
+ MoonViT3dPretrainedModel,
37
+ )
38
+ from sglang.srt.server_args import get_global_server_args
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ class Glm5vForConditionalGeneration(KimiK25ForConditionalGeneration):
44
+ """glm5v: MoonViT vision + trained projector + GLM-5.2 text."""
45
+
46
+ def __init__(
47
+ self,
48
+ config,
49
+ quant_config=None,
50
+ prefix: str = "",
51
+ **kwargs, # match parent (absorbs init_tts etc.)
52
+ ) -> None:
53
+ # Skip KimiK25ForConditionalGeneration.__init__ (it hardcodes the
54
+ # DeepseekV3 text class); replicate its body with GlmMoeDsa instead.
55
+ nn.Module.__init__(self)
56
+ self.config = config
57
+ self.quant_config = quant_config
58
+ self.use_data_parallel = get_global_server_args().mm_enable_dp_encoder
59
+
60
+ # Vision tower (bf16; quant only under ModelSlim, same as parent).
61
+ self.vision_tower = MoonViT3dPretrainedModel(
62
+ config.vision_config,
63
+ use_data_parallel=self.use_data_parallel,
64
+ quant_config=(
65
+ quant_config if isinstance(quant_config, ModelSlimConfig) else None
66
+ ),
67
+ prefix="vision_tower",
68
+ )
69
+ # MM projector (unquantized; linear_2 out = vision_config.text_hidden_size).
70
+ self.mm_projector = K2VLMultiModalProjector(config.vision_config)
71
+
72
+ self.language_model = None
73
+ if not config.encoder_only:
74
+ # THE swap: GLM-5.2 (MLA + MoE + DSA sparse attention) text backbone.
75
+ self.language_model = GlmMoeDsaForCausalLM(
76
+ config.text_config,
77
+ quant_config,
78
+ prefix=(
79
+ "language_model"
80
+ if isinstance(quant_config, (ModelSlimConfig, QuarkConfig))
81
+ else ""
82
+ ),
83
+ )
84
+
85
+ # Match vision/projector dtype to the language model (parent behavior).
86
+ if self.language_model is not None and hasattr(self.language_model, "dtype"):
87
+ target_dtype = self.language_model.dtype
88
+ self.vision_tower = self.vision_tower.to(dtype=target_dtype)
89
+ self.mm_projector = self.mm_projector.to(dtype=target_dtype)
90
+
91
+
92
+ EntryClass = [Glm5vForConditionalGeneration]
plugins/sglang_glm5v/patch.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Register Glm5vForConditionalGeneration as a DSA-capable architecture in SGLang.
4
+
5
+ WHY THIS PATCH IS NECESSARY (not an environment workaround):
6
+ SGLang gates the DeepSeek-Sparse-Attention path on a hardcoded architecture
7
+ tuple in ``is_deepseek_dsa`` (``srt/configs/model_config.py``). Upstream's own
8
+ DSA vision-language model (Mistral Large 3) is supported by adding its VLM
9
+ arch ``PixtralForConditionalGeneration`` to exactly this tuple in-tree — that
10
+ is the upstream mechanism; no env-var/out-of-tree hook exists for it (unlike
11
+ models/processors/mm-arch, which we register via the shipped
12
+ ``SGLANG_EXTERNAL_*`` hooks). Our GLM5V wrapper needs the same one-line entry
13
+ so the DSA attention backend (and the ``server_args`` DSA backend defaults)
14
+ recognize the model whose text backbone is GlmMoeDsa.
15
+
16
+ Three insertions, each at a place where upstream registered its own DSA/MLA
17
+ VLM archs (KimiK25 / Pixtral precedents):
18
+
19
+ 1. ``model_config.py``: the ``is_deepseek_dsa`` arch tuple — gates the DSA
20
+ attention backend;
21
+ 2. ``model_config.py``: the MLA attention-arch condition — without it the
22
+ KV pool is built as MHA and the DSA backend crashes on
23
+ ``dsa_kv_cache_store_fp8``;
24
+ 3. ``server_args.py``: the DSA model-adjustments gate list — sets the DSA
25
+ dense-attn kv-len threshold (= model index_topk) and the prefill/decode
26
+ DSA backend defaults (trtllm on Blackwell); without it decode dies with
27
+ ``Unsupported self.dsa_decode_impl = None``.
28
+
29
+ A 4th insertion (added later) enables MTP/NEXTN speculative decoding: without
30
+ it the draft worker rebuilds the entire ~90GB multimodal model as the "draft"
31
+ and OOMs (our wrapper arch is in none of SGLang's draft-model rewrite lists).
32
+ See the ``DRAFT_ANCHOR`` block below. This is only needed when serving with
33
+ ``--speculative-algorithm NEXTN``; it is a no-op otherwise. (Measured: MTP gives
34
+ ~1.1-1.16x tok/s at low/moderate batch + ~2.0 accept-len, greedy outputs are
35
+ token-identical to non-spec, but it is graph-capped at batch 256 so plain
36
+ high-batch TP8 wins for throughput-bound RL rollout — MTP is a single-stream
37
+ LATENCY lever, not a throughput one.)
38
+
39
+ Other in-tree arch lists mentioning GlmMoeDsa/KimiK25 are deliberately NOT
40
+ patched: they gate features we do not use (encoder disaggregation) or pure
41
+ performance defaults we set explicitly (flashinfer allreduce fusion,
42
+ attention-backend fallback).
43
+
44
+ The same entries will be needed wherever slime launches SGLang rollout engines
45
+ for glm5v (Milestone 1) — candidate for an upstream sglang PR.
46
+
47
+ Idempotent; run before ``sglang.launch_server`` (serve.sh does this).
48
+ """
49
+
50
+ import pathlib
51
+ import subprocess
52
+ import sys
53
+
54
+ ARCH = "Glm5vForConditionalGeneration"
55
+ DSA_ANCHOR = '"GlmMoeDsaForCausalLM",'
56
+ DSA_INSERT = (
57
+ DSA_ANCHOR
58
+ + f'\n "{ARCH}", # glm5v: GLM-5.2 (DSA) text + MoonViT vision (sglang_glm5v)'
59
+ )
60
+ MLA_ANCHOR = 'or "KimiK25ForConditionalGeneration" in self.hf_config.architectures'
61
+ MLA_INSERT = (
62
+ MLA_ANCHOR
63
+ + f'\n or "{ARCH}" in self.hf_config.architectures # glm5v (sglang_glm5v)'
64
+ )
65
+
66
+
67
+ # server_args.py DSA adjustments gate: the list containing KimiK25 is unique
68
+ # to that block (other GlmMoeDsa lists in the file lack it).
69
+ SA_ANCHOR = (
70
+ '"KimiK25ForConditionalGeneration",\n'
71
+ ' "MistralLarge3ForCausalLM",\n'
72
+ ' "PixtralForConditionalGeneration",\n'
73
+ ' "GlmMoeDsaForCausalLM",'
74
+ )
75
+ SA_INSERT = SA_ANCHOR + f'\n "{ARCH}", # glm5v (sglang_glm5v)'
76
+
77
+
78
+ # --- MTP / speculative NEXTN draft-model support (4th insertion) ---
79
+ # WHY: with --speculative-algorithm NEXTN, SGLang builds a SEPARATE draft model
80
+ # (is_draft_model=True) whose arch it rewrites to a single-layer NextN module in
81
+ # ``_config_draft_model``. That method maps the *text* backbone
82
+ # ``GlmMoeDsaForCausalLM`` -> ``DeepseekV3ForCausalLMNextN`` (correct: our MTP
83
+ # weights live at text layer 78: eh_proj/enorm/hnorm + one block). But our
84
+ # TOP-LEVEL arch is the multimodal wrapper ``Glm5vForConditionalGeneration``,
85
+ # which is in NONE of the rewrite lists -> the draft worker instead rebuilds the
86
+ # ENTIRE multimodal model (MoonViT + full 78-layer 90GB text model) as the
87
+ # "draft" and OOMs (a second ~90GB copy on top of base weights + KV + graphs).
88
+ #
89
+ # FIX: mirror the shipped ``Step3p7ForConditionalGeneration`` precedent (a VLM
90
+ # wrapper whose draft is its text MTP): when is_draft_model and arch is our
91
+ # wrapper, swap hf_config -> hf_text_config and set arch to GlmMoeDsaForCausalLM.
92
+ # Inserted at the TOP of _config_draft_model so the existing
93
+ # GlmMoeDsaForCausalLM -> DeepseekV3ForCausalLMNextN rule then fires on it.
94
+ DRAFT_ANCHOR = (
95
+ " def _config_draft_model(self):\n"
96
+ " is_draft_model = self.is_draft_model\n"
97
+ )
98
+ DRAFT_INSERT = (
99
+ DRAFT_ANCHOR
100
+ + "\n"
101
+ + " # glm5v (sglang_glm5v): map the multimodal wrapper draft to its\n"
102
+ + " # text backbone so the GlmMoeDsa->NextN rewrite below applies.\n"
103
+ + f' if is_draft_model and self.hf_config.architectures[0] == "{ARCH}":\n'
104
+ + " self.hf_config = self.hf_text_config\n"
105
+ + ' self.hf_config.architectures = ["GlmMoeDsaForCausalLM"]\n'
106
+ )
107
+
108
+
109
+ def find_module(mod: str) -> pathlib.Path:
110
+ out = subprocess.run(
111
+ [sys.executable, "-c", f"import {mod} as m; print(m.__file__)"],
112
+ capture_output=True,
113
+ text=True,
114
+ check=True,
115
+ )
116
+ return pathlib.Path(out.stdout.strip())
117
+
118
+
119
+ def apply(path: pathlib.Path, anchor: str, insert: str, marker: str, label: str) -> None:
120
+ src = path.read_text()
121
+ if marker in src:
122
+ print(f"{label}: already patched")
123
+ return
124
+ assert anchor in src, f"{label}: anchor not found in {path} (sglang version changed?)"
125
+ path.write_text(src.replace(anchor, insert, 1))
126
+ print(f"{label}: patched ({path})")
127
+
128
+
129
+ def main() -> None:
130
+ mc = find_module("sglang.srt.configs.model_config")
131
+ apply(mc, DSA_ANCHOR, DSA_INSERT, f'"{ARCH}", # glm5v', "DSA tuple")
132
+ apply(mc, MLA_ANCHOR, MLA_INSERT, f'or "{ARCH}" in self.hf_config.architectures', "MLA condition")
133
+ apply(
134
+ mc,
135
+ DRAFT_ANCHOR,
136
+ DRAFT_INSERT,
137
+ f'if is_draft_model and self.hf_config.architectures[0] == "{ARCH}"',
138
+ "NEXTN draft-model map",
139
+ )
140
+ sa = find_module("sglang.srt.server_args")
141
+ apply(sa, SA_ANCHOR, SA_INSERT, f'"{ARCH}", # glm5v', "server_args DSA gate")
142
+
143
+ # verify the DSA gate end-to-end
144
+ check = subprocess.run(
145
+ [
146
+ sys.executable,
147
+ "-c",
148
+ (
149
+ "from sglang.srt.configs.model_config import is_deepseek_dsa;"
150
+ f"print(is_deepseek_dsa({{'architectures': ['{ARCH}'], 'index_topk': 2048}}))"
151
+ ),
152
+ ],
153
+ capture_output=True,
154
+ text=True,
155
+ check=True,
156
+ )
157
+ assert check.stdout.strip() == "True", check.stdout + check.stderr
158
+ print("verified: is_deepseek_dsa recognizes the arch")
159
+
160
+
161
+ if __name__ == "__main__":
162
+ main()
plugins/sglang_glm5v/patch_sglang_dsa_arch.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Register Glm5vForConditionalGeneration as a DSA-capable architecture in SGLang.
4
+
5
+ WHY THIS PATCH IS NECESSARY (not an environment workaround):
6
+ SGLang gates the DeepSeek-Sparse-Attention path on a hardcoded architecture
7
+ tuple in ``is_deepseek_dsa`` (``srt/configs/model_config.py``). Upstream's own
8
+ DSA vision-language model (Mistral Large 3) is supported by adding its VLM
9
+ arch ``PixtralForConditionalGeneration`` to exactly this tuple in-tree — that
10
+ is the upstream mechanism; no env-var/out-of-tree hook exists for it (unlike
11
+ models/processors/mm-arch, which we register via the shipped
12
+ ``SGLANG_EXTERNAL_*`` hooks). Our GLM5V wrapper needs the same one-line entry
13
+ so the DSA attention backend (and the ``server_args`` DSA backend defaults)
14
+ recognize the model whose text backbone is GlmMoeDsa.
15
+
16
+ Three insertions, each at a place where upstream registered its own DSA/MLA
17
+ VLM archs (KimiK25 / Pixtral precedents):
18
+
19
+ 1. ``model_config.py``: the ``is_deepseek_dsa`` arch tuple — gates the DSA
20
+ attention backend;
21
+ 2. ``model_config.py``: the MLA attention-arch condition — without it the
22
+ KV pool is built as MHA and the DSA backend crashes on
23
+ ``dsa_kv_cache_store_fp8``;
24
+ 3. ``server_args.py``: the DSA model-adjustments gate list — sets the DSA
25
+ dense-attn kv-len threshold (= model index_topk) and the prefill/decode
26
+ DSA backend defaults (trtllm on Blackwell); without it decode dies with
27
+ ``Unsupported self.dsa_decode_impl = None``.
28
+
29
+ A 4th insertion (added later) enables MTP/NEXTN speculative decoding: without
30
+ it the draft worker rebuilds the entire ~90GB multimodal model as the "draft"
31
+ and OOMs (our wrapper arch is in none of SGLang's draft-model rewrite lists).
32
+ See the ``DRAFT_ANCHOR`` block below. This is only needed when serving with
33
+ ``--speculative-algorithm NEXTN``; it is a no-op otherwise. (Measured: MTP gives
34
+ ~1.1-1.16x tok/s at low/moderate batch + ~2.0 accept-len, greedy outputs are
35
+ token-identical to non-spec, but it is graph-capped at batch 256 so plain
36
+ high-batch TP8 wins for throughput-bound RL rollout — MTP is a single-stream
37
+ LATENCY lever, not a throughput one.)
38
+
39
+ Other in-tree arch lists mentioning GlmMoeDsa/KimiK25 are deliberately NOT
40
+ patched: they gate features we do not use (encoder disaggregation) or pure
41
+ performance defaults we set explicitly (flashinfer allreduce fusion,
42
+ attention-backend fallback).
43
+
44
+ The same entries will be needed wherever slime launches SGLang rollout engines
45
+ for glm5v (Milestone 1) — candidate for an upstream sglang PR.
46
+
47
+ Idempotent; run before ``sglang.launch_server`` (serve.sh does this).
48
+ """
49
+
50
+ import pathlib
51
+ import subprocess
52
+ import sys
53
+
54
+ ARCH = "Glm5vForConditionalGeneration"
55
+ DSA_ANCHOR = '"GlmMoeDsaForCausalLM",'
56
+ DSA_INSERT = (
57
+ DSA_ANCHOR
58
+ + f'\n "{ARCH}", # glm5v: GLM-5.2 (DSA) text + MoonViT vision (sglang_glm5v)'
59
+ )
60
+ MLA_ANCHOR = 'or "KimiK25ForConditionalGeneration" in self.hf_config.architectures'
61
+ MLA_INSERT = (
62
+ MLA_ANCHOR
63
+ + f'\n or "{ARCH}" in self.hf_config.architectures # glm5v (sglang_glm5v)'
64
+ )
65
+
66
+
67
+ # server_args.py DSA adjustments gate: the list containing KimiK25 is unique
68
+ # to that block (other GlmMoeDsa lists in the file lack it).
69
+ SA_ANCHOR = (
70
+ '"KimiK25ForConditionalGeneration",\n'
71
+ ' "MistralLarge3ForCausalLM",\n'
72
+ ' "PixtralForConditionalGeneration",\n'
73
+ ' "GlmMoeDsaForCausalLM",'
74
+ )
75
+ SA_INSERT = SA_ANCHOR + f'\n "{ARCH}", # glm5v (sglang_glm5v)'
76
+
77
+
78
+ # --- MTP / speculative NEXTN draft-model support (4th insertion) ---
79
+ # WHY: with --speculative-algorithm NEXTN, SGLang builds a SEPARATE draft model
80
+ # (is_draft_model=True) whose arch it rewrites to a single-layer NextN module in
81
+ # ``_config_draft_model``. That method maps the *text* backbone
82
+ # ``GlmMoeDsaForCausalLM`` -> ``DeepseekV3ForCausalLMNextN`` (correct: our MTP
83
+ # weights live at text layer 78: eh_proj/enorm/hnorm + one block). But our
84
+ # TOP-LEVEL arch is the multimodal wrapper ``Glm5vForConditionalGeneration``,
85
+ # which is in NONE of the rewrite lists -> the draft worker instead rebuilds the
86
+ # ENTIRE multimodal model (MoonViT + full 78-layer 90GB text model) as the
87
+ # "draft" and OOMs (a second ~90GB copy on top of base weights + KV + graphs).
88
+ #
89
+ # FIX: mirror the shipped ``Step3p7ForConditionalGeneration`` precedent (a VLM
90
+ # wrapper whose draft is its text MTP): when is_draft_model and arch is our
91
+ # wrapper, swap hf_config -> hf_text_config and set arch to GlmMoeDsaForCausalLM.
92
+ # Inserted at the TOP of _config_draft_model so the existing
93
+ # GlmMoeDsaForCausalLM -> DeepseekV3ForCausalLMNextN rule then fires on it.
94
+ DRAFT_ANCHOR = (
95
+ " def _config_draft_model(self):\n"
96
+ " is_draft_model = self.is_draft_model\n"
97
+ )
98
+ DRAFT_INSERT = (
99
+ DRAFT_ANCHOR
100
+ + "\n"
101
+ + " # glm5v (sglang_glm5v): map the multimodal wrapper draft to its\n"
102
+ + " # text backbone so the GlmMoeDsa->NextN rewrite below applies.\n"
103
+ + f' if is_draft_model and self.hf_config.architectures[0] == "{ARCH}":\n'
104
+ + " self.hf_config = self.hf_text_config\n"
105
+ + ' self.hf_config.architectures = ["GlmMoeDsaForCausalLM"]\n'
106
+ )
107
+
108
+
109
+ def find_module(mod: str) -> pathlib.Path:
110
+ out = subprocess.run(
111
+ [sys.executable, "-c", f"import {mod} as m; print(m.__file__)"],
112
+ capture_output=True,
113
+ text=True,
114
+ check=True,
115
+ )
116
+ return pathlib.Path(out.stdout.strip())
117
+
118
+
119
+ def apply(path: pathlib.Path, anchor: str, insert: str, marker: str, label: str) -> None:
120
+ src = path.read_text()
121
+ if marker in src:
122
+ print(f"{label}: already patched")
123
+ return
124
+ assert anchor in src, f"{label}: anchor not found in {path} (sglang version changed?)"
125
+ path.write_text(src.replace(anchor, insert, 1))
126
+ print(f"{label}: patched ({path})")
127
+
128
+
129
+ def main() -> None:
130
+ mc = find_module("sglang.srt.configs.model_config")
131
+ apply(mc, DSA_ANCHOR, DSA_INSERT, f'"{ARCH}", # glm5v', "DSA tuple")
132
+ apply(mc, MLA_ANCHOR, MLA_INSERT, f'or "{ARCH}" in self.hf_config.architectures', "MLA condition")
133
+ apply(
134
+ mc,
135
+ DRAFT_ANCHOR,
136
+ DRAFT_INSERT,
137
+ f'if is_draft_model and self.hf_config.architectures[0] == "{ARCH}"',
138
+ "NEXTN draft-model map",
139
+ )
140
+ sa = find_module("sglang.srt.server_args")
141
+ apply(sa, SA_ANCHOR, SA_INSERT, f'"{ARCH}", # glm5v', "server_args DSA gate")
142
+
143
+ # verify the DSA gate end-to-end
144
+ check = subprocess.run(
145
+ [
146
+ sys.executable,
147
+ "-c",
148
+ (
149
+ "from sglang.srt.configs.model_config import is_deepseek_dsa;"
150
+ f"print(is_deepseek_dsa({{'architectures': ['{ARCH}'], 'index_topk': 2048}}))"
151
+ ),
152
+ ],
153
+ capture_output=True,
154
+ text=True,
155
+ check=True,
156
+ )
157
+ assert check.stdout.strip() == "True", check.stdout + check.stderr
158
+ print("verified: is_deepseek_dsa recognizes the arch")
159
+
160
+
161
+ if __name__ == "__main__":
162
+ main()
plugins/sglang_glm5v/processor.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """SGLang multimodal processor for glm5v.
3
+
4
+ Subclass of the in-tree Kimi-K2.5 GPU image processor with GLM's image
5
+ placeholder: ``<|image|>`` (id from ``config.media_placeholder_token_id``,
6
+ 154854) instead of Kimi's ``<|media_pad|>``. The GLM chat template
7
+ (``chat_template.jinja`` in the assembled checkpoint) wraps each image as
8
+ ``<|begin_of_image|><|image|><|end_of_image|>``; only the inner ``<|image|>``
9
+ run is expanded to per-patch tokens, exactly as in the vLLM deployment.
10
+
11
+ All preprocessing (NaViT resize math, GPU patchify, grid_thw handling) is
12
+ inherited unchanged — it is the same MoonViT pipeline the projector was
13
+ trained against.
14
+ """
15
+
16
+ import re
17
+
18
+ from sglang.srt.multimodal.processors.base_processor import MultimodalSpecialTokens
19
+ from sglang.srt.multimodal.processors.kimi_k25 import (
20
+ KimiGPUProcessorWrapper,
21
+ KimiK2_5VLImageProcessor,
22
+ )
23
+
24
+ from sglang_glm5v.glm5v import Glm5vForConditionalGeneration
25
+
26
+
27
+ class Glm5vImageProcessor(KimiK2_5VLImageProcessor):
28
+ models = [Glm5vForConditionalGeneration]
29
+
30
+ def __init__(self, hf_config, server_args, _processor, *args, **kwargs):
31
+ # Call the grandparent chain directly: the parent __init__ hardcodes
32
+ # Kimi's <|media_pad|> token; we rebuild mm_tokens + the GPU wrapper
33
+ # with GLM's <|image|> and keep everything else identical.
34
+ super(KimiK2_5VLImageProcessor, self).__init__(
35
+ hf_config, server_args, _processor, *args, **kwargs
36
+ )
37
+ self.mm_tokens = MultimodalSpecialTokens(
38
+ image_token="<|image|>",
39
+ image_token_id=hf_config.media_placeholder_token_id,
40
+ image_token_regex=re.compile(r"(?:<\|image\|>)+"),
41
+ ).build(_processor)
42
+
43
+ media_proc_cfg = _processor.media_processor.media_proc_cfg
44
+ self._processor = KimiGPUProcessorWrapper(
45
+ _processor,
46
+ image_token=self.mm_tokens.image_token,
47
+ patch_size=media_proc_cfg["patch_size"],
48
+ merge_kernel_size=media_proc_cfg["merge_kernel_size"],
49
+ in_patch_limit=media_proc_cfg["in_patch_limit"],
50
+ patch_limit_on_one_side=media_proc_cfg["patch_limit_on_one_side"],
51
+ fixed_output_tokens=media_proc_cfg.get("fixed_output_tokens"),
52
+ image_mean=media_proc_cfg["image_mean"],
53
+ image_std=media_proc_cfg["image_std"],
54
+ )
plugins/vllm_glm5v/.___init__.py ADDED
Binary file (163 Bytes). View file
 
plugins/vllm_glm5v/._glm5v.py ADDED
Binary file (163 Bytes). View file
 
plugins/vllm_glm5v/._glm5v_config.py ADDED
Binary file (163 Bytes). View file
 
plugins/vllm_glm5v/._register.py ADDED
Binary file (163 Bytes). View file
 
plugins/vllm_glm5v/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """glm5v: out-of-tree vLLM VLM (MoonViT + projector + GLM-5.2)."""
plugins/vllm_glm5v/glm5v.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """
3
+ glm5v: out-of-tree vLLM model = MoonViT (Kimi-K2.6) vision tower + trained MLP
4
+ projector + GLM-5.2 (GlmMoeDsa) text decoder.
5
+
6
+ This is a thin frankenstein over vLLM's in-tree `kimi_k25` VLM:
7
+ * vision tower + projector classes are imported VERBATIM from kimi_k25_vit
8
+ (the MoonViT3d encoder + KimiK25MultiModalProjector are bit-identical to the
9
+ encoder/projector we trained against).
10
+ * the text backbone is swapped from DeepseekV2ForCausalLM -> GlmMoeDsaForCausalLM
11
+ (GLM-5.2; same `deepseek_v2.py` MLA+MoE+DSA stack in vLLM).
12
+ * the image placeholder is GLM's `<|image|>` (154854), span
13
+ `<|begin_of_image|><|image|><|end_of_image|>`, instead of Kimi's `<|media_pad|>`.
14
+ * projector `linear_2` outputs GLM hidden (6144) via vision_config.mm_hidden_size.
15
+
16
+ Register out-of-tree via `ModelRegistry.register_model` (see register.py) with the
17
+ config.json `architectures = ["Glm5vForConditionalGeneration"]`.
18
+ """
19
+
20
+ from collections.abc import Iterable, Mapping
21
+ from typing import Any
22
+
23
+ import torch
24
+ from torch import nn
25
+
26
+ from vllm.config import VllmConfig
27
+ from vllm.logger import init_logger
28
+ from vllm.model_executor.layers.quantization import QuantizationConfig
29
+ from vllm.model_executor.layers.quantization.compressed_tensors import (
30
+ compressed_tensors,
31
+ )
32
+ from vllm.model_executor.models.interfaces import (
33
+ SupportsMultiModal,
34
+ SupportsPP,
35
+ SupportsQuant,
36
+ )
37
+ from vllm.model_executor.models.kimi_k25 import (
38
+ KimiK25DummyInputsBuilder,
39
+ KimiK25MultiModalProcessor,
40
+ KimiK25ProcessingInfo,
41
+ )
42
+ from vllm.model_executor.models.kimi_k25_vit import (
43
+ KimiK25MultiModalProjector,
44
+ MoonViT3dPretrainedModel,
45
+ vision_tower_forward,
46
+ )
47
+ from vllm.multimodal import MULTIMODAL_REGISTRY
48
+ from vllm.multimodal.inputs import NestedTensors
49
+ from vllm.platforms import current_platform
50
+ from vllm.sequence import IntermediateTensors
51
+
52
+ from .glm5v_config import Glm5vConfig
53
+ from vllm.model_executor.models.utils import (
54
+ AutoWeightsLoader,
55
+ WeightsMapper,
56
+ init_vllm_registered_model,
57
+ maybe_prefix,
58
+ )
59
+
60
+ logger = init_logger(__name__)
61
+
62
+
63
+ class Glm5vProcessingInfo(KimiK25ProcessingInfo):
64
+ """Processing info: GLM tokenizer + `<|image|>` placeholder + MoonViT image proc.
65
+
66
+ Reuses Kimi-K2.6's image processor (identical MoonViT preprocessing) and its
67
+ `KimiK25Processor` wiring; only the config type and placeholder token differ.
68
+ """
69
+
70
+ def get_hf_config(self):
71
+ return self.ctx.get_hf_config(Glm5vConfig)
72
+
73
+
74
+ class Glm5vDummyInputsBuilder(KimiK25DummyInputsBuilder):
75
+ pass
76
+
77
+
78
+ class Glm5vMultiModalProcessor(KimiK25MultiModalProcessor):
79
+ pass
80
+
81
+
82
+ @MULTIMODAL_REGISTRY.register_processor(
83
+ Glm5vMultiModalProcessor,
84
+ info=Glm5vProcessingInfo,
85
+ dummy_inputs=Glm5vDummyInputsBuilder,
86
+ )
87
+ class Glm5vForConditionalGeneration(
88
+ nn.Module,
89
+ SupportsMultiModal,
90
+ SupportsPP,
91
+ SupportsQuant,
92
+ ):
93
+ """glm5v: MoonViT vision + trained projector + GLM-5.2 text."""
94
+
95
+ supports_encoder_tp_data = True
96
+
97
+ hf_to_vllm_mapper = WeightsMapper(
98
+ orig_to_new_prefix={
99
+ # GLM-5.2 text shards are stored with raw `model.*` / `lm_head.*` keys;
100
+ # nest them under the `language_model` submodule. (vision_tower.* and
101
+ # mm_projector.* keys are already correctly namespaced in our assembly.)
102
+ "lm_head.": "language_model.lm_head.",
103
+ "model.": "language_model.model.",
104
+ }
105
+ )
106
+
107
+ @classmethod
108
+ def get_placeholder_str(cls, modality: str, i: int) -> str | None:
109
+ if modality == "image":
110
+ # GLM image span; the inner <|image|> token is the one expanded.
111
+ return "<|begin_of_image|><|image|><|end_of_image|>"
112
+ raise ValueError(f"Unsupported modality: {modality}")
113
+
114
+ def __init__(self, vllm_config: VllmConfig, prefix: str = "") -> None:
115
+ super().__init__()
116
+ model_config = vllm_config.model_config
117
+ config: Glm5vConfig = model_config.hf_config
118
+ self.config = config
119
+ quant_config = vllm_config.quant_config
120
+
121
+ self.use_data_parallel = (
122
+ model_config.multimodal_config.mm_encoder_tp_mode == "data"
123
+ )
124
+ self.hidden_size = config.text_config.hidden_size
125
+ self.device = current_platform.current_device()
126
+
127
+ # Vision tower + projector (MoonViT, kept out of compressed-tensors quant).
128
+ with self._mark_tower_model(vllm_config, "vision_chunk"):
129
+ self.vision_tower = MoonViT3dPretrainedModel(
130
+ config.vision_config,
131
+ quant_config=self._maybe_ignore_quant_config(quant_config),
132
+ prefix=maybe_prefix(prefix, "vision_tower"),
133
+ )
134
+ self.vision_tower = self.vision_tower.to(
135
+ device=self.device, dtype=model_config.dtype
136
+ )
137
+ self.mm_projector = KimiK25MultiModalProjector(
138
+ config=config.vision_config,
139
+ use_data_parallel=self.use_data_parallel,
140
+ quant_config=self._maybe_ignore_quant_config(quant_config),
141
+ prefix=maybe_prefix(prefix, "mm_projector"),
142
+ )
143
+ self.mm_projector = self.mm_projector.to(
144
+ device=self.device, dtype=model_config.dtype
145
+ )
146
+
147
+ self.quant_config = quant_config
148
+ with self._mark_language_model(vllm_config):
149
+ self.language_model = init_vllm_registered_model(
150
+ vllm_config=vllm_config,
151
+ hf_config=config.text_config,
152
+ prefix=maybe_prefix(prefix, "language_model"),
153
+ architectures=["GlmMoeDsaForCausalLM"],
154
+ )
155
+ self.make_empty_intermediate_tensors = (
156
+ self.language_model.make_empty_intermediate_tensors
157
+ )
158
+ self.media_placeholder: int = self.config.media_placeholder_token_id
159
+
160
+ def _maybe_ignore_quant_config(self, quant_config: QuantizationConfig):
161
+ # Keep the MoonViT vision tower + trained projector in bf16; only the GLM
162
+ # text backbone is quantized. The GLM-5.2-NVFP4 checkpoint uses ModelOpt
163
+ # NVFP4 (modelopt_fp4) for the text Linears, but our vision/projector
164
+ # weights are bf16 -- so skip quant for them regardless of method.
165
+ from vllm.model_executor.layers.quantization.modelopt import (
166
+ ModelOptNvFp4Config,
167
+ ModelOptFp8Config,
168
+ )
169
+
170
+ if isinstance(
171
+ quant_config,
172
+ (
173
+ compressed_tensors.CompressedTensorsConfig,
174
+ ModelOptNvFp4Config,
175
+ ModelOptFp8Config,
176
+ ),
177
+ ):
178
+ return None
179
+ return quant_config
180
+
181
+ def _parse_and_validate_media_input(self, **kwargs: object):
182
+ pixel_values = kwargs.pop("pixel_values", None)
183
+ grid_thws = kwargs.pop("grid_thws", None)
184
+ if pixel_values is None:
185
+ return None
186
+ if isinstance(pixel_values, list):
187
+ pixel_values = torch.cat(pixel_values, dim=0)
188
+ if len(pixel_values.shape) == 5 or len(pixel_values.shape) == 3:
189
+ pixel_values = pixel_values.reshape(
190
+ pixel_values.shape[0] * pixel_values.shape[1], *pixel_values.shape[2:]
191
+ )
192
+ target_dtype = next(self.vision_tower.parameters()).dtype
193
+ pixel_values = pixel_values.to(target_dtype)
194
+ assert isinstance(grid_thws, torch.Tensor)
195
+ grid_thws = grid_thws.reshape(-1, grid_thws.shape[-1])
196
+ assert grid_thws.ndim == 2 and grid_thws.size(1) == 3
197
+ return {"pixel_values": pixel_values, "grid_thws": grid_thws}
198
+
199
+ def _process_media_input(self, media_input) -> list[torch.Tensor]:
200
+ return vision_tower_forward(
201
+ self.vision_tower,
202
+ media_input["pixel_values"],
203
+ media_input["grid_thws"],
204
+ mm_projector=self.mm_projector,
205
+ use_data_parallel=self.use_data_parallel,
206
+ )
207
+
208
+ def embed_multimodal(self, **kwargs: object) -> NestedTensors | None:
209
+ media_input = self._parse_and_validate_media_input(**kwargs)
210
+ if media_input is None:
211
+ return None
212
+ return self._process_media_input(media_input)
213
+
214
+ def forward(
215
+ self,
216
+ input_ids: torch.Tensor,
217
+ positions: torch.Tensor,
218
+ intermediate_tensors: IntermediateTensors | None = None,
219
+ inputs_embeds: torch.Tensor | None = None,
220
+ **kwargs: object,
221
+ ) -> IntermediateTensors:
222
+ if intermediate_tensors is not None:
223
+ inputs_embeds = None
224
+ return self.language_model(
225
+ input_ids=input_ids,
226
+ positions=positions,
227
+ intermediate_tensors=intermediate_tensors,
228
+ inputs_embeds=inputs_embeds,
229
+ )
230
+
231
+ def compute_logits(self, hidden_states: torch.Tensor, **kwargs) -> torch.Tensor:
232
+ return self.language_model.compute_logits(hidden_states)
233
+
234
+ def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
235
+ loader = AutoWeightsLoader(self)
236
+ loaded = loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
237
+ # Sanity log: confirm the trained projector tensors were actually loaded
238
+ # (not left at init). Helps catch key-name mismatches.
239
+ proj = sorted(k for k in loaded if "mm_projector" in k)
240
+ logger.info("[glm5v] mm_projector keys loaded (%d): %s", len(proj), proj)
241
+ return loaded
plugins/vllm_glm5v/glm5v_config.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Config for glm5v = MoonViT vision + GLM-5.2 (glm_moe_dsa) text."""
3
+
4
+ from transformers import AutoConfig
5
+ from transformers.configuration_utils import PretrainedConfig
6
+
7
+ from vllm.transformers_utils.configs.kimi_k25 import KimiK25VisionConfig
8
+
9
+
10
+ class Glm5vConfig(PretrainedConfig):
11
+ """glm5v top-level config.
12
+
13
+ Holds a MoonViT `vision_config` (KimiK25VisionConfig) and a GLM-5.2
14
+ `text_config` (model_type `glm_moe_dsa`). `media_placeholder_token_id` is GLM's
15
+ `<|image|>` id (154854). `vision_config.mm_hidden_size` is the projector's output
16
+ dim and MUST equal the GLM text hidden size (6144).
17
+ """
18
+
19
+ model_type = "glm5v"
20
+
21
+ def __init__(
22
+ self,
23
+ vision_config: dict | KimiK25VisionConfig | None = None,
24
+ text_config: dict | PretrainedConfig | None = None,
25
+ ignore_index: int = -100,
26
+ media_placeholder_token_id: int = 154854,
27
+ pad_token_id: int = 154820,
28
+ # True => vLLM's chat layer maps OpenAI `image`/`video` content to the
29
+ # model's `vision_chunk` modality (the Kimi MoonViT path expects this).
30
+ use_unified_vision_chunk: bool = True,
31
+ video_placeholder: str = "<|glm5v_video_placeholder|>",
32
+ **kwargs,
33
+ ):
34
+ # Vision config (MoonViT).
35
+ if vision_config is None:
36
+ self.vision_config = KimiK25VisionConfig()
37
+ elif isinstance(vision_config, dict):
38
+ self.vision_config = KimiK25VisionConfig(**vision_config)
39
+ else:
40
+ self.vision_config = vision_config
41
+
42
+ # Text config (GLM-5.2 / glm_moe_dsa). Build via AutoConfig so the
43
+ # GlmMoeDsa config class (and all its MLA/DSA/MoE fields) is used.
44
+ # NOTE: transformers calls Glm5vConfig() with no args (to_diff_dict),
45
+ # so text_config=None must default to a bare GlmMoeDsaConfig.
46
+ if text_config is None:
47
+ self.text_config = AutoConfig.for_model("glm_moe_dsa")
48
+ elif isinstance(text_config, dict):
49
+ tc = dict(text_config)
50
+ tc.setdefault("model_type", "glm_moe_dsa")
51
+ self.text_config = AutoConfig.for_model(**tc)
52
+ else:
53
+ self.text_config = text_config
54
+
55
+ # Projector output dim must match GLM hidden.
56
+ self.vision_config.mm_hidden_size = self.text_config.hidden_size
57
+
58
+ self.ignore_index = ignore_index
59
+ self.media_placeholder_token_id = media_placeholder_token_id
60
+ self.use_unified_vision_chunk = use_unified_vision_chunk
61
+ self.video_placeholder = video_placeholder
62
+
63
+ if getattr(self.text_config, "quantization_config", None) is not None:
64
+ self.quantization_config = self.text_config.quantization_config
65
+
66
+ super().__init__(pad_token_id=pad_token_id, **kwargs)
67
+
68
+ @property
69
+ def hidden_size(self) -> int:
70
+ return self.text_config.hidden_size
71
+
72
+ @property
73
+ def vocab_size(self) -> int:
74
+ return self.text_config.vocab_size
75
+
76
+ def __getattr__(self, name: str):
77
+ """Delegate unknown attributes to the GLM text_config.
78
+
79
+ vLLM's MLA / DSA-indexer machinery sometimes reads text-model fields
80
+ (e.g. `index_topk`, `index_n_heads`, `kv_lora_rank`) off the top-level
81
+ model config rather than off `text_config`. Forwarding here lets the GLM
82
+ sparse-attention path find them without copying every field up.
83
+ NOTE: only called when normal attribute lookup fails, so it won't shadow
84
+ real attributes; guard against recursion on `text_config` itself.
85
+ """
86
+ if name == "text_config":
87
+ raise AttributeError(name)
88
+ try:
89
+ text_config = self.__dict__["text_config"]
90
+ except KeyError as exc: # during unpickling / early init
91
+ raise AttributeError(name) from exc
92
+ return getattr(text_config, name)
plugins/vllm_glm5v/register.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Register glm5v config + model with HF AutoConfig and vLLM's ModelRegistry.
3
+
4
+ Import this module (or call `register()`) BEFORE constructing `LLM(...)` /
5
+ `vllm serve`. For `vllm serve`, point `VLLM_PLUGINS`/entrypoint at it, or simply
6
+ `import vllm_glm5v.register` from a sitecustomize / -X importtime hook. The
7
+ simplest path used here: copy these files next to vLLM's models and import.
8
+ """
9
+
10
+
11
+ def register() -> None:
12
+ from transformers import AutoConfig
13
+
14
+ from .glm5v_config import Glm5vConfig
15
+
16
+ try:
17
+ AutoConfig.register("glm5v", Glm5vConfig)
18
+ except ValueError:
19
+ pass # already registered
20
+
21
+ from vllm import ModelRegistry
22
+
23
+ ModelRegistry.register_model(
24
+ "Glm5vForConditionalGeneration",
25
+ "vllm_glm5v.glm5v:Glm5vForConditionalGeneration",
26
+ )
27
+
28
+
29
+ register()