Ascend-SACT/glm-4.7-flash
模型介绍文件和版本Pull Requests讨论分析
下载使用量0

GLM-4.7-Flash 在 Ascend NPU 上的 vLLM 适配说明

文档版本: v3.0 更新日期: 2026-03-26 适用环境: Ascend 910B / A2 / A3, TP=4 镜像版本: vllm-ascend:v0.17.0rc1

一、快速开始

1.1 环境准备

项目说明
镜像版本vllm-ascend-0.17.0rc1
模型路径/models/GLM-4.7-Flash
vLLM 仓库路径/vllm-workspace/vllm
vLLM-Ascend 仓库路径/vllm-workspace/vllm-ascend
补丁文件vllm-v0.17.0rc1-glm47flash.patch、vllm-ascend-v0.17.0rc1-glm47flash.patch

1.2 应用补丁

在当前仓库根目录执行以下命令,将两个补丁分别应用到 vllm 和 vllm-ascend:

cd /path/to/glm-4.7-flash
PATCH_DIR=$(pwd)

cd /vllm-workspace/vllm
git apply --check "${PATCH_DIR}/vllm-v0.17.0rc1-glm47flash.patch"
git apply "${PATCH_DIR}/vllm-v0.17.0rc1-glm47flash.patch"

cd /vllm-workspace/vllm-ascend
git apply --check "${PATCH_DIR}/vllm-ascend-v0.17.0rc1-glm47flash.patch"
git apply "${PATCH_DIR}/vllm-ascend-v0.17.0rc1-glm47flash.patch"

1.3 校验补丁是否生效

ls /vllm-workspace/vllm/vllm/model_executor/models/glm4_moe_lite*.py
ls /vllm-workspace/vllm/vllm/transformers_utils/configs/glm4_moe_lite.py

1.4 启动服务

Graph 基线

HCCL_OP_EXPANSION_MODE=AIV vllm serve /models/GLM-4.7-Flash \
  --served-model-name GLM-4.7-Flash \
  --tensor-parallel-size 4 \
  --max-model-len 32768 \
  --port 8000

EP + MTP 推荐快路径

HCCL_OP_EXPANSION_MODE=AIV vllm serve /models/GLM-4.7-Flash \
  --served-model-name GLM-4.7-Flash \
  --tensor-parallel-size 4 \
  --max-model-len 32768 \
  --enable-expert-parallel \
  --speculative-config '{"method":"mtp","num_speculative_tokens":1}' \
  --port 8013

如需工具调用和推理解析,可追加 --enable-auto-tool-choice --tool-call-parser glm47 --reasoning-parser glm45。

说明:

  • 推荐使用 --tensor-parallel-size 4,当前适配与主要测试结果均基于 TP=4。
  • 快速开始示例默认使用 --max-model-len 32768,优先保证首次启动成功、较低显存压力和更快的启动速度。
  • 如需长上下文,可在完成本地显存与性能复核后再提高 --max-model-len;模型上限约为 202752 tokens。
  • HCCL_OP_EXPANSION_MODE=AIV 用于支持 200k 级长上下文;即使快速开始先跑 32k,仍建议保留该环境变量。
  • 保守默认使用 graph 基线;当前最佳时延/吞吐折中为 MTP num_speculative_tokens=1。
  • num_speculative_tokens=2 可跑,但启动更慢,稳态 decode 不如 k=1。

二、模型分析

2.1 模型基本信息

参数值
模型名称GLM-4.7-Flash (glm4_moe_lite)
模型规模约 30B(MoE 架构)
注意力机制MLA(Multi-Head Latent Attention)
注意力头数20
KV 头数20(与注意力头数相同)
隐藏层维度2048
层数47

2.2 MLA 关键参数

qk_nope_head_dim = 192   # Query/Key 非位置编码维度
qk_rope_head_dim = 64    # Query/Key 的 RoPE 维度
v_head_dim = 256         # Value 维度
kv_lora_rank = 512       # KV 压缩秩
q_lora_rank = 768        # Query 压缩秩

2.3 与 DeepSeek MLA 的差异

参数DeepSeek V2/V3GLM-4.7-Flash
qk_nope_head_dim128192
qk_rope_head_dim6464
v_head_dim128256
注意力头数128(2 的幂次)20(非 2 的幂次)

核心结论: GLM-4.7-Flash 的 MLA 维度与 Ascend NPU 优化算子 npu_ring_mla 不兼容,不能直接复用 DeepSeek 系列的现成路径。

三、实现细节

3.1 问题诊断

  1. TP=8 无法使用,因为 20 个注意力头不能被 8 整除。
  2. TP=4 的 decode 路径仍会失败,因为本地头数 20 / 4 = 5,不是 2 的幂次,不满足 NPU MLA 算子的 group 约束。
  3. Prefill 路径不能继续使用 npu_ring_mla,因为该算子硬编码期望维度为 128 / 64 / 128,与 GLM 的 192 / 64 / 256 不一致。

3.2 Decode 路径: Head Padding

decode 阶段通过 head padding 将单卡本地头数从 5 补到 8,计算结束后再裁剪回原始头数:

# 在 AscendMLAImpl 内部,self.num_heads 表示单卡本地头数
local_num_heads = self.num_heads       # TP=4 时为 5
need_head_padding = not _is_power_of_2(local_num_heads)
padded_num_heads = _next_power_of_2(local_num_heads)  # 5 -> 8

# 对 Q 张量进行填充
if need_head_padding:
    q_nope = torch.nn.functional.pad(q_nope, (0, 0, 0, pad_heads))
    q_pe = torch.nn.functional.pad(q_pe, (0, 0, 0, pad_heads))

# 计算后裁剪回原始头数
if need_head_padding:
    attn_output = attn_output[:local_num_heads]

3.3 Prefill 路径: npu_fused_infer_attention_score

由于 npu_ring_mla 维度不兼容,npu_fusion_attention 的 causal mask 又无法正确生效,prefill 最终切换到 npu_fused_infer_attention_score:

def _forward_prefill_fallback(self, q_nope, q_pe, k_nope, k_pe, value, ...):
    # 合并 Q/K 的 nope 和 pe,满足 query_dim >= value_dim 约束
    query = torch.cat([q_nope, q_pe], dim=-1)  # [T, N, 256]
    key = torch.cat([k_nope, k_pe], dim=-1)    # [T, N, 256]

    # 使用 BNSD 格式 + sparse_mode=3,逐序列计算
    for seq_len in query_lens_list:
        causal_mask = self._get_prefill_fallback_causal_mask(seq_len, query.device)
        q_bnsd = query[start:end].transpose(0, 1).unsqueeze(0)  # [1, N, S, D]
        attn_out, lse = torch_npu.npu_fused_infer_attention_score(
            query=q_bnsd, key=k_bnsd, value=v_bnsd,
            input_layout="BNSD", sparse_mode=3, atten_mask=causal_mask,
            softmax_lse_flag=True,
        )

    # chunked-prefix: 从 paged KV cache 取回历史上下文,
    # 用 float32 logsumexp 风格合并,避免长序列重复问题
    if chunked_context is not None:
        for chunk_idx, toks in enumerate(chunked_context.seq_tot):
            # npu_paged_cache_load -> kv_b_proj -> attention -> merge
            merged_output = base_output * exp(base_lse - merged_lse) \
                          + update_output * exp(update_lse - merged_lse)

这个实现的关键约束如下:

配置结果
sparse_mode=0 + pre_tokens/next_tokens输出错误
sparse_mode=3 + causal mask(大小为 max(2048, seq_len))输出正确

3.4 关键修改文件

补丁文件路径修改说明
vllm-v0.17.0rc1-glm47flash.patchvllm/tokenizers/hf.py按模型 config.json 决定 fix_mistral_regex,避免数字/日期 token 拆分异常
vllm-v0.17.0rc1-glm47flash.patchvllm/config/speculative.py兼容 checkpoint 中 num_nextn_predict_layers: 0 但实际含 MTP 权重的情况
vllm-v0.17.0rc1-glm47flash.patchvllm/model_executor/models/glm4_moe_lite.py修正 get_spec_layer_idx_from_weight_name 以发现隐藏的 MTP 层
vllm-v0.17.0rc1-glm47flash.patchvllm/model_executor/models/glm4_moe_lite_mtp.pyMTP 从 speculative_config.draft_model_config 读取 drafter config
vllm-v0.17.0rc1-glm47flash.patchvllm/transformers_utils/config.py注册 glm4_moe_lite config 入口
vllm-v0.17.0rc1-glm47flash.patchvllm/transformers_utils/configs/__init__.py导出 Glm4MoeLiteConfig
vllm-v0.17.0rc1-glm47flash.patchvllm/transformers_utils/configs/glm4_moe_lite.py为目标 0.17.0rc1 环境补齐 Glm4MoeLiteConfig
vllm-ascend-v0.17.0rc1-glm47flash.patchvllm_ascend/attention/mla_v1.py添加 head padding、prefill fallback(含 chunked-prefix 支持)

3.5 Ascend 算子选型结论

  • npu_ring_mla:不可用。原因是维度硬编码为 128 / 64 / 128,无法适配 GLM 的 192 / 64 / 256。
  • npu_fusion_attention:不可用。原因是 TND 布局下 causal mask 无法正确生效,与 MLA metadata 也不匹配。
  • npu_fused_infer_attention_score:已验证可用。在 BNSD 布局、sparse_mode=3、动态 causal mask(大小为 max(2048, seq_len))条件下结果正确。

四、问题与解决

4.1 问题列表

#问题描述典型报错解决方案
1TP=8 头数不整除20 must be divisible by 8改用 TP=4
2NPU 算子要求 group 为 2 的幂次group num should be in 1,2,4,8...对 decode 路径做 head padding(5 -> 8)
3npu_ring_mla 与 GLM 维度不兼容AtbRingMLAGetWorkspaceSize failed改用 npu_fused_infer_attention_score
4目标 0.17.0rc1 环境缺失 glm4_moe_lite.py config 文件启动时 KeyError: glm4_moe_lite补丁新增该文件并注册
5checkpoint num_nextn_predict_layers: 0 但含 MTP 权重MTP drafter 初始化失败补丁在 speculative config 中兜底为 1
6MTP drafter 误读 target confignum_nextn_predict_layers 被视为 0改从 speculative_config.draft_model_config 读取
7fix_mistral_regex=True 导致数字/日期 token 逐位拆分数值输出异常按 config.json 中 transformers_version 自动决定
8长序列重复prefill fallback 丢失 chunked-prefix 上下文补丁增加 chunked-prefix logsumexp 合并

4.2 常见运行问题

  • HCCL 端口冲突:可执行 pkill -9 -f vllm && sleep 10 后重启服务。
  • 显存不足:降低 --max-model-len 或 --max-num-seqs。
  • TP 选择错误:当前更推荐 TP=4;如果使用其他 TP,需要保证 20 个注意力头能够整除对应 TP,并重新验证图编译与性能表现。

五、测试结果

本节包含基础功能验证、开启 MTP 的在线服务吞吐测试,以及长上下文在线服务测试。不同表格可能来自不同测试场景,表内已注明关键配置。

5.1 功能测试

以下为基础功能验证。vllm serve 启动后,直接调用 OpenAI 兼容接口 /v1/chat/completions 即可;若你使用的是 Graph 基线,端口为 8000,若使用 EP + MTP 快路径,端口为 8013。请求体中的 "model" 需要与启动参数 --served-model-name 保持一致。

BASE_URL=http://127.0.0.1:8000  # 若使用 EP + MTP 快路径则改为 http://127.0.0.1:8013

curl -s "${BASE_URL}/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "GLM-4.7-Flash",
    "messages": [
      {"role": "user", "content": "What is the capital of France?"}
    ],
    "temperature": 0,
    "max_tokens": 32
  }' | python -m json.tool

正常情况下,可在返回结果的 choices[0].message.content 中看到 Paris。

如需再做一个最小续写验证,可将请求体改为:

{
  "model": "GLM-4.7-Flash",
  "messages": [
    {"role": "user", "content": "Continue this sentence in one short line: San Francisco is a"}
  ],
  "temperature": 0,
  "max_tokens": 32
}

5.2 MTP 在线服务性能

以下数据为 2026-03-26 在本地 Ascend 环境复测的 vllm bench serve 结果:TP=4,request-rate=inf,temperature=0,--ignore-eos,1k input / 1k output。

场景基线MTP k=1MTP k=3观察
1k/1k@115.1 tok/s31.4 tok/s25.5 tok/sk=1 最高
1k/1k@16219.8 tok/s370.7 tok/s359.9 tok/sk=1 最高

结论:

  • 在当前 vllm-ascend-0.17.0rc1 + 本补丁 + 本地 Ascend 环境中,k=1 在本次复测的单并发和 16 并发场景下都优于 k=3。
  • Graph 基线结果与文档原值接近复现;MTP 吞吐结果对环境较敏感,建议以本地实测为准。

5.3 长上下文测试(在线服务,ACLGraph 基线,TP=4)

以下数据为 2026-03-26 在本地 Ascend 环境复测的 vllm bench serve 结果:TP=4,未启用 MTP,--max-model-len 202752,request-rate=inf,max-concurrency=1,temperature=0,--ignore-eos。

输入 Token 数输出 Token 数耗时吞吐量
10k1k68.4s14.6 tok/s
50k2k144.7s13.8 tok/s
100k4k298.9s13.4 tok/s
150k32k2348.9s13.6 tok/s

5.4 长上下文测试(在线服务,MTP k=1,TP=4)

以下数据为 2026-03-26 在本地 Ascend 环境复测的 vllm bench serve 结果:TP=4,启用 MTP k=1,--max-model-len 202752,request-rate=inf,max-concurrency=1,temperature=0,--ignore-eos。

输入 Token 数输出 Token 数耗时吞吐量TTFTTPOT
10k1k34.7s28.8 tok/s1344.0 ms33.4 ms
50k2k75.2s26.6 tok/s8140.9 ms33.6 ms
100k4k157.7s25.4 tok/s18376.3 ms34.8 ms
150k32k2123.4s15.1 tok/s27353.4 ms65.5 ms

5.5 测试结论

  • 本地在线服务复测中,MTP k=1 在 1k/1k 的单并发和 16 并发场景下都优于 k=3,相比 graph 基线带来约 69% - 108% 的吞吐提升。
  • TP=4 配置下已验证支持 150k+ tokens 输入,模型最大上下文长度约为 202752 tokens。
  • 在长上下文在线服务测试中,MTP k=1 对 10k/1k、50k/2k、100k/4k 三组请求带来约 89% - 97% 的吞吐提升,但 150k/32k 仅提升约 11%。
  • 从本次复测结果看,超长输出场景下 speculative draft 的接受率会在解码后段明显走低,因此 150k/32k 的 MTP 收益显著小于前面三组长上下文请求。