Skip to content

Commit f11a0f4

Browse files
authored
[feat]Add GLM-5-FP8 support (#61)
### PR Category Core ### PR Types New Model Support ### PR Description Support GLM-5-FP8
1 parent 2e543db commit f11a0f4

4 files changed

Lines changed: 185 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright (c) 2025 BAAI. All rights reserved.
2+
# Adapted from https://github.com/vllm-project/vllm/blob/v0.11.0/examples/offline_inference/basic/basic.py
3+
# Below is the original copyright:
4+
# SPDX-License-Identifier: Apache-2.0
5+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
6+
7+
import os
8+
os.environ["VLLM_ALLOW_LONG_MAX_MODEL_LEN"] = "1"
9+
from vllm import LLM, SamplingParams
10+
import torch
11+
from vllm.config.compilation import CompilationConfig
12+
13+
14+
if __name__ == '__main__':
15+
prompts = [
16+
"Hello, my name is",
17+
]
18+
19+
# Create a sampling params object.
20+
sampling_params = SamplingParams(max_tokens=10, temperature=0.0)
21+
# Create an LLM.
22+
llm = LLM(model="/models/GLM-5-FP8", tensor_parallel_size=8, pipeline_parallel_size=1, enforce_eager=False, load_format="fastsafetensors")
23+
24+
# Generate texts from the prompts.
25+
outputs = llm.generate(prompts, sampling_params)
26+
27+
for output in outputs:
28+
prompt = output.prompt
29+
generated_text = output.outputs[0].text
30+
print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
31+

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ scikit-build-core==0.11
88
pybind11
99
ninja
1010
cmake
11+
fastsafetensors

vllm_fl/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,15 @@ def register_model():
8585
)
8686
except Exception as e:
8787
logger.error(f"Register KimiK25 model error: {str(e)}")
88+
89+
# Register GLM-5 (GlmMoeDsa) model
90+
try:
91+
from vllm_fl.models.glm_moe_dsa import patch_is_deepseek_mla
92+
patch_is_deepseek_mla()
93+
94+
ModelRegistry.register_model(
95+
"GlmMoeDsaForCausalLM",
96+
"vllm_fl.models.glm_moe_dsa:GlmMoeDsaForCausalLM"
97+
)
98+
except Exception as e:
99+
logger.error(f"Register GlmMoeDsa model error: {str(e)}")

vllm_fl/models/glm_moe_dsa.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
"""Inference-only GLM-5 (GlmMoeDsa) model.
3+
4+
GLM-5 uses a DeepSeek V2/V3-style architecture with MLA (Multi-head Latent
5+
Attention) and Mixture of Experts. The HF model type is ``glm_moe_dsa`` and
6+
the architecture class is ``GlmMoeDsaForCausalLM``.
7+
8+
This thin wrapper inherits from vLLM's ``DeepseekV2ForCausalLM`` which already
9+
handles MLA and MoE. The DSA (Dynamic Sparse Attention) indexer requires
10+
deep_gemm FP8 kernels; when deep_gemm is unavailable, we disable the indexer
11+
by temporarily hiding the ``index_topk`` config attribute during construction.
12+
"""
13+
14+
import torch
15+
16+
from vllm.config import VllmConfig
17+
from vllm.model_executor.models.deepseek_v2 import (
18+
DeepseekV2ForCausalLM,
19+
Indexer,
20+
)
21+
from vllm.utils.import_utils import has_deep_gemm
22+
23+
24+
def _patched_indexer_forward(
25+
self, hidden_states: torch.Tensor, qr: torch.Tensor, positions, rotary_emb
26+
) -> torch.Tensor:
27+
"""Fixed Indexer.forward that handles RoPE output dimensions correctly."""
28+
q, _ = self.wq_b(qr)
29+
q = q.view(-1, self.n_head, self.head_dim)
30+
q_pe, q_nope = torch.split(
31+
q, [self.rope_dim, self.head_dim - self.rope_dim], dim=-1
32+
)
33+
34+
k, _ = self.wk(hidden_states)
35+
k = self.k_norm(k)
36+
k_pe, k_nope = torch.split(
37+
k, [self.rope_dim, self.head_dim - self.rope_dim], dim=-1
38+
)
39+
40+
q_pe, k_pe = rotary_emb(positions, q_pe, k_pe.unsqueeze(1))
41+
# RoPE can introduce extra leading dimensions during compilation,
42+
# so reshape back to token-flattened shapes.
43+
q_pe = q_pe.reshape(-1, self.n_head, self.rope_dim)
44+
k_pe = k_pe.reshape(-1, 1, self.rope_dim)
45+
46+
q = torch.cat([q_pe, q_nope], dim=-1)
47+
k = torch.cat([k_pe.squeeze(-2), k_nope], dim=-1)
48+
49+
# We only quant q here since k quant is fused with cache insertion.
50+
from vllm.model_executor.layers.quantization.utils.fp8_utils import (
51+
per_token_group_quant_fp8,
52+
)
53+
54+
q = q.view(-1, self.head_dim)
55+
q_fp8, q_scale = per_token_group_quant_fp8(
56+
q,
57+
self.quant_block_size,
58+
column_major_scales=False,
59+
use_ue8m0=self.scale_fmt is not None,
60+
)
61+
q_fp8 = q_fp8.view(-1, self.n_head, self.head_dim)
62+
q_scale = q_scale.view(-1, self.n_head, 1)
63+
64+
weights, _ = self.weights_proj(hidden_states)
65+
weights = (
66+
weights.unsqueeze(-1) * q_scale * self.softmax_scale * self.n_head**-0.5
67+
)
68+
weights = weights.squeeze(-1)
69+
70+
return torch.ops.vllm.sparse_attn_indexer(
71+
hidden_states,
72+
self.k_cache.prefix,
73+
self.k_cache.kv_cache[0],
74+
q_fp8,
75+
k,
76+
weights,
77+
self.quant_block_size,
78+
self.scale_fmt,
79+
self.topk_tokens,
80+
self.head_dim,
81+
self.max_model_len,
82+
self.max_total_seq_len,
83+
self.topk_indices_buffer,
84+
)
85+
86+
def patch_is_deepseek_mla():
87+
"""Patch ``ModelConfig.is_deepseek_mla`` to recognise ``glm_moe_dsa``."""
88+
from vllm.config.model import ModelConfig
89+
90+
_orig = ModelConfig.is_deepseek_mla.fget
91+
92+
@property # type: ignore[misc]
93+
def _patched(self):
94+
if (
95+
hasattr(self.hf_text_config, "model_type")
96+
and self.hf_text_config.model_type == "glm_moe_dsa"
97+
and getattr(self.hf_text_config, "kv_lora_rank", None) is not None
98+
):
99+
return True
100+
return _orig(self)
101+
102+
ModelConfig.is_deepseek_mla = _patched
103+
104+
# Monkey-patch the Indexer.forward to fix dimension mismatch in the
105+
# installed vLLM 0.13.0.
106+
Indexer.forward = _patched_indexer_forward
107+
108+
109+
class GlmMoeDsaForCausalLM(DeepseekV2ForCausalLM):
110+
"""GLM-5 model for causal language modelling."""
111+
112+
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
113+
config = vllm_config.model_config.hf_config
114+
115+
# The DSA indexer requires deep_gemm FP8 MQA kernels.
116+
# When deep_gemm is not available, disable the indexer by
117+
# temporarily removing the index_topk attribute so that
118+
# DeepseekV2Attention skips indexer construction.
119+
_saved_index_topk = getattr(config, "index_topk", None)
120+
self._indexer_disabled = False
121+
if _saved_index_topk is not None and not has_deep_gemm():
122+
delattr(config, "index_topk")
123+
self._indexer_disabled = True
124+
125+
try:
126+
super().__init__(vllm_config=vllm_config, prefix=prefix)
127+
finally:
128+
# Restore the config attribute
129+
if _saved_index_topk is not None and not hasattr(config, "index_topk"):
130+
config.index_topk = _saved_index_topk
131+
132+
def load_weights(self, weights):
133+
# When the DSA indexer is disabled, the model has no indexer
134+
# parameters, but the checkpoint still contains them.
135+
# Filter them out to avoid KeyError during weight loading.
136+
if self._indexer_disabled:
137+
weights = (
138+
(name, weight) for name, weight in weights
139+
if ".indexer." not in name
140+
)
141+
return super().load_weights(weights)

0 commit comments

Comments
 (0)