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

GLM-5 在 910C 六机场景的 vLLM-Ascend 推理性能优化方案

1. 背景描述

1.1 硬件版本

组件版本
硬件环境910C
服务器数6 机

1.2 软件版本

组件版本
vllm-ascend0.18.0.RC1
HDKAscend HDK 25.3.rc1
CANN8.5.0
模型GLM-5

1.3 模型描述

GLM-5 是智谱新一代的旗舰基座模型,面向智能体工程(Agentic Engineering)打造,能够在复杂系统工程与长程智能体任务中提供可靠生产力。在代码生成与智能体能力上,GLM-5 取得开源领域的最佳表现(SOTA),在真实编程场景的使用体验逼近 Claude Opus 4.5,擅长复杂系统工程与长程智能体任务,是通用智能体助手的理想基座。

1.4 模型结构

GLM-5 模型是一款大语言模型(LLM),采用 MLA(DSA)+ MoE 结构,总参数量为 7740 亿,推理时激活的参数为 400 亿。从 GLM 4.x 到 GLM-5,结构上最大的变化是注意力(Attention)部分从 GQA 变为了 DSA,与 DeepSeek V3.2 类似。

2. 解决方案和结果简述

2.1 问题根因

基于 vLLM-Ascend 框架的初始(0 day)镜像存在开箱性能差、吞吐低的问题。经特性叠加性能调优后,模型推理速度提升,单卡吞吐提升了 x 倍。

2.2 解决措施

通过接入 MLAPO 融合算子、muls_add 融合算子、MoE 大融合算子、共享专家多流特性、PD 分离特性、MTP 接受率提升、调整 PD 分离服务化参数等调优策略,实测在输入 65k 输出 1.5k 并发 80 的场景下,单卡总吞吐由 900+ 提升至 2300+ tokens/s,持续调优中。

3. 详细方案

3.1 权重下载

bf16:GLM-5 · 模型库

w4a8 / w8a8 量化:

  • GLM-5-w8a8
  • GLM-5-w4a8

ModelSlim 安装与量化命令:

# git clone https://gitcode.com/Ascend/msmodelslim
git clone -b test_model <modelslim-repo-url>
cd ./msmodelslim
bash install.sh
cd ../

# W8A8
msmodelslim quant \
    --model_path "glm-5-float-path" \
    --save_path "save-path" \
    --model_type GLM-5 \
    --device "npu:0" \
    --config_path "glm_5_w8a8_yaml_path" \
    --trust_remote_code True

# W4A8
msmodelslim quant \
    --model_path "glm-5-float-path" \
    --save_path "save-path" \
    --model_type GLM-5 \
    --device "npu:0" \
    --config_path "glm_5_w4a8_yaml_path" \
    --trust_remote_code True

3.1.1 glm_5_w4a8_yaml 文件

apiversion: modelslim_v1
metadata:
  config_id: glm_5_w4a8
  score: 90
  verified_model_types:
    - GLM-5
  label:
    w_bit: 4
    a_bit: 8
    is_sparse: False
    kv_cache: False

default_w4a8_dynamic: &default_w4a8_dynamic
  act:
    scope: "per_token"
    dtype: "int8"
    symmetric: True
    method: "minmax"
  weight:
    scope: "per_channel"
    dtype: "int4"
    symmetric: True
    method: "ssz"

default_w8a8_dynamic: &default_w8a8_dynamic
  act:
    scope: "per_token"
    dtype: "int8"
    symmetric: True
    method: "minmax"
  weight:
    scope: "per_channel"
    dtype: "int8"
    symmetric: True
    method: "minmax"

spec:
  process:
    - type: "quarot"
    - type: "flex_awq_ssz"
      qconfig:
        act:
          scope: "per_token"
          dtype: "int8"
          symmetric: True
          method: "minmax"
        weight:
          scope: "per_channel"
          dtype: "int4"
          symmetric: True
          method: "ssz"
          ext:
            step: 10
      enable_subgraph_type:
        - 'up-down'
      include:
        - "*"
      exclude:
        - "model.layers.0.*"
        - "model.layers.1.*"
        - "model.layers.2.*"
        - "*mlp.shared_experts.*"
    - type: "flex_smooth_quant"
      enable_subgraph_type:
        - 'norm-linear'
        - 'ov'
        - 'up-down'
      include:
        - "*self_attn*"
        - "model.layers.0.mlp.*"
        - "model.layers.1.mlp.*"
        - "model.layers.2.mlp.*"
        - "*mlp.shared_experts.*"
        - "*input_layernorm*"
      exclude:
        - "*post_attention_layernorm*"
    - type: "group"
      configs:
        - type: "linear_quant"
          qconfig: *default_w8a8_dynamic
          include:
            - "*self_attn*"
          exclude:
            - "*kv_b_proj"
            - "*wk"
            - "*weights_proj"
        - type: "linear_quant"
          qconfig: *default_w8a8_dynamic
          include:
            - "*mlp*"
          exclude:
            - "*gate"
            - "*mlp.experts.*"
        - type: "linear_quant"
          qconfig: *default_w8a8_dynamic
          include:
            - "model.layers.78.mlp.experts.*"
        - type: "linear_quant"
          qconfig: *default_w4a8_dynamic
          include:
            - "*mlp.experts.*"
          exclude:
            - "model.layers.78.*"
  dataset: qwen3_cot_w4a4.json
  save:
    - type: "ascendv1_saver"
      part_file_size: 4

3.1.2 glm_5_w8a8_yaml 文件

apiversion: modelslim_v1
metadata:
  config_id: glm_5_w8a8
  score: 90
  verified_model_types:
    - GLM-5
  label:
    w_bit: 8
    a_bit: 8
    is_sparse: False
    kv_cache: False

default_w8a8_dynamic: &default_w8a8_dynamic
  act:
    scope: "per_token"
    dtype: "int8"
    symmetric: True
    method: "minmax"
  weight:
    scope: "per_channel"
    dtype: "int8"
    symmetric: True
    method: "minmax"

default_w8a8: &default_w8a8
  act:
    scope: "per_tensor"
    dtype: "int8"
    symmetric: False
    method: "minmax"
  weight:
    scope: "per_channel"
    dtype: "int8"
    symmetric: True
    method: "minmax"

spec:
  process:
    - type: "quarot"
    - type: "flex_smooth_quant"
      enable_subgraph_type:
        - 'norm-linear'
        - 'ov'
      include:
        - "*"
    - type: "linear_quant"
      qconfig: *default_w8a8
      include:
        - "*self_attn*"
      exclude:
        - "*kv_b_proj"
        - "*wk"
        - "*weights_proj"
    - type: "linear_quant"
      qconfig: *default_w8a8_dynamic
      include:
        - "*mlp*"
      exclude:
        - "*gate"
  dataset: mix_calib.jsonl
  save:
    - type: "ascendv1_saver"
      part_file_size: 4

3.2 镜像制作

参考:GLM-5 Ascend 示例文档,基于 0 day 镜像更新如下版本号,并重新制作镜像:

组件仓库/版本提交
vllm<vllm-repo-url>,releases/v0.16.09fbaba936400bc2789a69442afce2e54bab4f99f
vllm-ascend<vllm-ascend-repo-url>,br_glme42379dc1b7da893ed15bb2f9d73599215a7eb5b
transformersv5.2.07d9754a05193eb79b1d86aa744b622b8068008cd

3.3 创建容器

docker run -itd --privileged --name=GLM5_test --net=host \
    --shm-size 500g \
    --device=/dev/davinci0 \
    --device=/dev/davinci1 \
    --device=/dev/davinci2 \
    --device=/dev/davinci3 \
    --device=/dev/davinci4 \
    --device=/dev/davinci5 \
    --device=/dev/davinci6 \
    --device=/dev/davinci7 \
    --device=/dev/davinci8 \
    --device=/dev/davinci9 \
    --device=/dev/davinci10 \
    --device=/dev/davinci11 \
    --device=/dev/davinci12 \
    --device=/dev/davinci13 \
    --device=/dev/davinci14 \
    --device=/dev/davinci15 \
    --device=/dev/davinci_manager \
    --device=/dev/hisi_hdc \
    --device /dev/devmm_svm \
    -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
    -v /usr/local/Ascend/firmware:/usr/local/Ascend/firmware \
    -v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi \
    -v /usr/local/sbin:/usr/local/sbin \
    -v /etc/hccn.conf:/etc/hccn.conf \
    -v /home:/home \
    -v /disk1:/disk1 \
    -v /disk2:/disk2 \
    -v /disk3:/disk3 \
    -v /opt:/opt \
    -v /home:/home \
    --entrypoint /bin/bash \
    m.daocloud.io/quay.io/ascend/vllm-ascend:glm5-a3

3.5 ACL Graph 图模式

由于单算子模式需要频繁地下发算子,会造成 Host 瓶颈。为缓解此问题,可采用 ACL Graph 图模式,实现一次捕获、多次重放,从而减少 CPU 和框架的调度开销,提高吞吐性能。

3.6 异步调度

异步调度特性 --async-scheduling 能够减少推理过程中 token 之间的空泡等待时间,提升整体推理性能。

启用方法:启动推理服务时添加以下启动选项。

--async-scheduling

3.7 流水优化

通过此环境变量可配置 task_queue 算子下发队列是否开启和优化等级。

  • 配置为 0 时:关闭 task_queue 算子下发队列优化。
  • 配置为 1 或未配置时:开启 task_queue 算子下发队列 Level 1 优化。

Level 1 优化:使能 task_queue 算子下发队列优化,将算子下发任务分为两段,一部分任务(主要是 aclnn 算子的调用)放在新增的二级流水上,一、二级流水通过算子队列传递任务,相互并行,通过部分掩盖减少整体的下发耗时,提升端到端性能。

详细介绍:TASK_QUEUE_ENABLE 环境变量

使能方法:

export TASK_QUEUE_ENABLE=1

3.8 高性能内存分配器

jemalloc 是一款内存分配器,与传统内存分配器(例如 glibc)相比,其最大优势在于减少内存碎片和提升多线程高并发场景下内存的分配效率,进而充分发挥多核多并发的优势。

在内存分配过程中,锁会造成线程等待,对性能影响很大。jemalloc 采用线程变量,每个线程有对应的内存管理器,内存分配在该线程内完成,无需和其他线程竞争锁。

详细参考:Ascend CANN 安装指导 - jemalloc

使能方法:

export LD_PRELOAD=/usr/local/Ascend/ascend-toolkit/latest/lib64/libjemalloc.so

3.9 通信算法 - AIV

开启 HCCL AIV 模式,代表通信算法的编排展开位置在 Device 侧的 Vector Core,执行也在 Vector Core。

详细参考:HCCL_OP_EXPANSION_MODE 环境变量

使能方法:

export HCCL_OP_EXPANSION_MODE="AIV"

3.10 MLAPO 算子适配 GLM-5 模型

PR 已合入至 0.17.0.rc1。

3.10.1 MLAPO 算子简介

MLA 的预处理阶段、Decoding 阶段会有大量小算子,单次执行时间会小于单次下发耗时,形成 host bound。加之拼接类算子、搬运类算子较多,融合是比较合理的优化途径。该方案将前处理过程中的 13 个小算子直接融合成一个超级大算子 MLAPO(MlaPreprocessOperation),进一步提升性能。

MLAPO 算子的完整流程可以分为以下几个步骤:

  1. RmsNorm / Preload 并行。
  2. Q + KV 的降维 Matmul。
  3. Q 的 RmsNorm。
  4. Q 的升维 Matmul / KV Rope & RmsNorm & Reshape and Cache 并行。
  5. K 的升维 Matmul / Q Rope 并行。

3.10.2 问题描述

MLAPO 算子最初专为 DeepSeek V3 模型开发。在代码中,DeepSeek V3 的 MLA 维度参数被作为编译期常量(constexpr)硬编码在 C++ 源码中。当 GLM-5(glm_moe_dsa)模型尝试使用同一算子时,由于其 MLA 维度参数与 DeepSeek V3 不同,导致精度错误。

3.10.3 解决方案

将维度参数通过 tiling 数据从 host 传递到 kernel,运行时动态读取。推导代码(op_host/mla_preprocess.h 中 mla_preprocess_tiling() 函数):

uint32_t qkNopeHeadDim = wuk.sizes()[1];               // DS=128, GLM5=192
uint32_t kvLoraRank    = wuk.sizes()[2];               // DS=GLM5=512
uint32_t qLoraRank     = gamma1.sizes()[0];            // DS=1536, GLM5=2048
uint32_t qkRopeHeadDim = kv_cache_rope.sizes().back(); // DS=GLM5=64

从这 4 个基础维度可派生出 kernel 内部需要的全部 9 个复合维度:

mm1OutSize          = qLoraRank + kvLoraRank + qkRopeHeadDim
splitSizeOne        = kvLoraRank + qkRopeHeadDim
splitSizeTwo        = qLoraRank
splitRmsNormSizeOne = kvLoraRank
splitRmsNormSizeTwo = qkRopeHeadDim
ropeSplitSizeOne    = qkRopeHeadDim
ropeSplitSizeTwo    = qkNopeHeadDim
hiddenStrateRope    = qkNopeHeadDim + qkRopeHeadDim
qkNopeHeadDim       = qkNopeHeadDim

GLM-5 适配 PR:vLLM-Ascend PR #6902

3.11 使能 fuse_muls_add 融合算子

已合入。

硬编码的方式未能适配 GLM-5 模型。通过读取 hf_text_config 内的 routed_scaling_factor 字段,可以灵活解决 muls_add 融合算子在 GLM-5 模型上未生效的问题。

相关 PR:

  • vLLM-Ascend PR #5518
  • vLLM-Ascend PR #6928

启动示例:

export HCCL_OP_EXPANSION_MODE="AIV"
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=10
export VLLM_USE_V1=1
export HCCL_BUFFSIZE=200
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export VLLM_ASCEND_BALANCE_SCHEDULING=1
export VLLM_TORCH_PROFILER_DIR="/path/to/profiling"
export VLLM_TORCH_PROFILER_WITH_STACK=0
export PYTHONPATH=/path/to/vllm:/path/to/vllm-ascend:${PYTHONPATH}

nohup vllm serve /path/to/GLM-5-w4a8 \
    --host <listen-host> \
    --port 8077 \
    --data-parallel-size 1 \
    --tensor-parallel-size 16 \
    --enable-expert-parallel \
    --seed 1024 \
    --served-model-name glm-5 \
    --max-num-seqs 8 \
    --max-model-len 199000 \
    --max-num-batched-tokens 4096 \
    --trust-remote-code \
    --gpu-memory-utilization 0.95 \
    --quantization ascend \
    --enable-chunked-prefill \
    --no-enable-prefix-caching \
    --reasoning-parser glm45 \
    --enable-auto-tool-choice \
    --tool-call-parser glm47 \
    --async-scheduling \
    --additional-config '{"ascend_compilation_config": {"enable_npugraph_ex": true, "fuse_qknorm_rope": true, "fuse_muls_add": true}, "multistream_overlap_shared_expert": true}' \
    --compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \
    --speculative-config '{"num_speculative_tokens": 3, "method": "deepseek_mtp"}' \
    >> glm5.log &

tail -2199f glm5.log

3.12 Flashcomm 特性使能

基于相同的集合通信逻辑,将 AllReduce 拆分为 ReduceScatter 和 AllGather,并选择通信算子的合适位置,实现低比特和低维度数据通信,从而有效降低通信数据量和通信时延,并消除模型中的冗余计算,提升推理性能。

使能方法:

export VLLM_ASCEND_ENABLE_FLASHCOMM1=1

3.13 共享专家多流开启

通过开启共享专家多流,共享专家与路由专家进行 CV 并行和通算并行,提升推理性能,单卡吞吐提升 5-10%。

使能方法:

{
  "multistream_overlap_shared_expert": true
}

3.14 MoE 大融合算子接入

MoE 大融合算子使能方法:

export VLLM_ASCEND_ENABLE_FUSED_MC2=1

GLM-5 启用失败原因:

经过 ModelSlim 后量化的 MoE 模型(如 MiniMax-M2.5、GLM-5),其 config.json 中缺少 moe_quantize / quantize 字段,导致 quant_type=None,进而无法启用 FUSED_MC2。

相关问题修复:vLLM-Ascend PR #7217

解决方案:

读取 quant_model_description.json 文件,将其 value 作为量化类型(如 W8A8_DYNAMIC),确保 FUSED_MC2 算子正常启用。

算子接入成功:

待补充图示。

3.15 MTP 采信率提升

基线:

待补充图示。

w8a8 权重采信率低。

3.15.1 v0.16.0 版本修复方法

叠加 MTP 采信率修复。启动命令的 --additional-config 中需要增加:

{
  "rot_path": "/path/to/rot.safetensors"
}

rot.safetensors 下载地址:<rot-safetensors-download-url>

注意:rot.safetensors 不能放在权重路径下。

3.15.2 v0.17.0.rc1 版本后修复方法

  1. rot.safetensors 需要放到模型权重路径下。
  2. quant_model_weights.safetensors.index.json 增加相关配置。
  3. quant_model_description.json 增加相关配置。

修复后:w8a8 权重 + MTP 采信率提升至 60%+。

3.17 混部启动命令

0.18.0.rc1 版本参考:vLLM-Ascend GLM-5 教程

3.17.1 单机

export PYTHONPATH=/vllm_ascend/vllm:${PYTHONPATH}
export VLLM_ASCEND_ENABLE_FUSED_MC2=1
export VLLM_ASCEND_ENABLE_MLAPO=1
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
export VLLM_VERSION=0.16.0

# 通过 ifconfig 获取本机信息。
# nic_name 为当前节点 local_ip 对应的网卡接口名称。
nic_name="<nic-name>"
local_ip="<node0-local-ip>"
export PYTHONPATH=/workspace/vllm:/workspace/vllm-ascend:${PYTHONPATH}

# node0_ip 的值必须与节点 0(主节点)中设置的 local_ip 一致。
node0_ip="<node0-ip>"

export VLLM_ASCEND_ENABLE_MLAPO=1
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_IF_IP=$local_ip
export GLOO_SOCKET_IFNAME=$nic_name
export TP_SOCKET_IFNAME=$nic_name
export HCCL_SOCKET_IFNAME=$nic_name
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=10
export VLLM_USE_V1=1
export HCCL_BUFFSIZE=256
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export VLLM_VERSION=0.16.0

nohup vllm serve /path/to/GLM-5-w4a8 \
    --host <listen-host> \
    --port 8088 \
    --data-parallel-size 1 \
    --data-parallel-address $node0_ip \
    --data-parallel-rpc-port 11276 \
    --tensor-parallel-size 16 \
    --quantization ascend \
    --seed 1024 \
    --served-model-name glm-5 \
    --enable-expert-parallel \
    --max-num-seqs 40 \
    --max-model-len 200000 \
    --max-num-batched-tokens 8192 \
    --trust-remote-code \
    --gpu-memory-utilization 0.92 \
    --enable-chunked-prefill \
    --async-scheduling \
    --additional-config '{"enable_npugraph_ex": true, "fuse_qknorm_rope": true, "fuse_muls_add": true, "multistream_overlap_shared_expert": true, "rot_path": "/path/to/rot.safetensors"}' \
    --compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY", "cudagraph_capture_sizes": [4, 8, 12, 16, 32, 40]}' \
    --speculative-config '{"num_speculative_tokens": 3, "method": "deepseek_mtp"}' \
    --enable-auto-tool-choice \
    --tool-call-parser glm47 \
    --reasoning-parser glm45 \
    >> log.log &

tail -200f log.log

3.17.2 双机

节点0:

export PYTHONPATH=/vllm_ascend/vllm:${PYTHONPATH}
export VLLM_ASCEND_ENABLE_FUSED_MC2=1
export VLLM_ASCEND_ENABLE_MLAPO=1
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
export VLLM_VERSION=0.16.0

# 通过 ifconfig 获取本机信息。
# nic_name 为当前节点 local_ip 对应的网卡接口名称。
nic_name="<nic-name>"
local_ip="<node0-local-ip>"
export PYTHONPATH=/workspace/vllm:/workspace/vllm-ascend:${PYTHONPATH}

# node0_ip 的值必须与节点 0(主节点)中设置的 local_ip 一致。
node0_ip="<node0-ip>"

export VLLM_ASCEND_ENABLE_MLAPO=1
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_IF_IP=$local_ip
export GLOO_SOCKET_IFNAME=$nic_name
export TP_SOCKET_IFNAME=$nic_name
export HCCL_SOCKET_IFNAME=$nic_name
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=10
export VLLM_USE_V1=1
export HCCL_BUFFSIZE=256
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export VLLM_VERSION=0.16.0

nohup vllm serve /path/to/GLM-5-w8a8 \
    --host <listen-host> \
    --port 8088 \
    --data-parallel-size 2 \
    --data-parallel-size-local 1 \
    --data-parallel-address $node0_ip \
    --data-parallel-rpc-port 11276 \
    --tensor-parallel-size 16 \
    --quantization ascend \
    --seed 1024 \
    --served-model-name glm-5 \
    --enable-expert-parallel \
    --max-num-seqs 40 \
    --max-model-len 200000 \
    --max-num-batched-tokens 8192 \
    --trust-remote-code \
    --gpu-memory-utilization 0.92 \
    --enable-chunked-prefill \
    --async-scheduling \
    --additional-config '{"enable_npugraph_ex": true, "fuse_qknorm_rope": true, "fuse_muls_add": true, "multistream_overlap_shared_expert": true, "rot_path": "/path/to/rot.safetensors"}' \
    --compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY", "cudagraph_capture_sizes": [4, 8, 12, 16, 32, 40]}' \
    --speculative-config '{"num_speculative_tokens": 3, "method": "deepseek_mtp"}' \
    --profiler-config \
    '{"profiler": "torch",
    "torch_profiler_dir": "/path/to/profiling",
    "torch_profiler_with_stack": false}' \
    --enable-auto-tool-choice \
    --tool-call-parser glm47 \
    --reasoning-parser glm45 \
    >> log.log &

tail -200f log.log

节点1:

export PYTHONPATH=/vllm_ascend/vllm:${PYTHONPATH}
export VLLM_ASCEND_ENABLE_FUSED_MC2=1
export VLLM_ASCEND_ENABLE_MLAPO=1
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
export VLLM_VERSION=0.16.0

# 通过 ifconfig 获取本机信息。
# nic_name 为当前节点 local_ip 对应的网卡接口名称。
nic_name="<nic-name>"
local_ip="<node1-local-ip>"
export PYTHONPATH=/workspace/vllm:/workspace/vllm-ascend:${PYTHONPATH}

# node0_ip 的值必须与节点 0(主节点)中设置的 local_ip 一致。
node0_ip="<node0-ip>"

export VLLM_ASCEND_ENABLE_MLAPO=1
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_IF_IP=$local_ip
export GLOO_SOCKET_IFNAME=$nic_name
export TP_SOCKET_IFNAME=$nic_name
export HCCL_SOCKET_IFNAME=$nic_name
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=10
export VLLM_USE_V1=1
export HCCL_BUFFSIZE=256
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export VLLM_VERSION=0.16.0

nohup vllm serve /path/to/GLM-5-w8a8 \
    --host <listen-host> \
    --port 8088 \
    --headless \
    --data-parallel-size 2 \
    --data-parallel-size-local 1 \
    --data-parallel-start-rank 1 \
    --data-parallel-address $node0_ip \
    --data-parallel-rpc-port 11276 \
    --tensor-parallel-size 16 \
    --quantization ascend \
    --seed 1024 \
    --served-model-name glm-5 \
    --enable-expert-parallel \
    --max-num-seqs 40 \
    --max-model-len 200000 \
    --max-num-batched-tokens 8192 \
    --trust-remote-code \
    --gpu-memory-utilization 0.92 \
    --enable-chunked-prefill \
    --async-scheduling \
    --additional-config '{"enable_npugraph_ex": true, "fuse_qknorm_rope": true, "fuse_muls_add": true, "multistream_overlap_shared_expert": true, "rot_path": "/path/to/rot.safetensors"}' \
    --compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY", "cudagraph_capture_sizes": [4, 8, 12, 16, 32, 40]}' \
    --speculative-config '{"num_speculative_tokens": 3, "method": "deepseek_mtp"}' \
    --profiler-config \
    '{"profiler": "torch",
    "torch_profiler_dir": "/path/to/profiling",
    "torch_profiler_with_stack": false}' \
    --enable-auto-tool-choice \
    --tool-call-parser glm47 \
    --reasoning-parser glm45 \
    >> log.log &

tail -200f log.log

3.18 PD 分离部署

3.18.1 P、D 节点 launch_online_dp.py 脚本

import argparse
import multiprocessing
import os
import subprocess
import sys


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--dp-size",
        type=int,
        required=True,
        help="Data parallel size.",
    )
    parser.add_argument(
        "--tp-size",
        type=int,
        default=1,
        help="Tensor parallel size.",
    )
    parser.add_argument(
        "--dp-size-local",
        type=int,
        default=-1,
        help="Local data parallel size.",
    )
    parser.add_argument(
        "--dp-rank-start",
        type=int,
        default=0,
        help="Starting rank for data parallel.",
    )
    parser.add_argument(
        "--dp-address",
        type=str,
        required=True,
        help="IP address for data parallel master node.",
    )
    parser.add_argument(
        "--dp-rpc-port",
        type=str,
        default=12345,
        help="Port for data parallel master node.",
    )
    parser.add_argument(
        "--vllm-start-port",
        type=int,
        default=9000,
        help="Starting port for the engine.",
    )
    return parser.parse_args()


args = parse_args()
dp_size = args.dp_size
tp_size = args.tp_size
dp_size_local = args.dp_size_local
if dp_size_local == -1:
    dp_size_local = dp_size
dp_rank_start = args.dp_rank_start
dp_address = args.dp_address
dp_rpc_port = args.dp_rpc_port
vllm_start_port = args.vllm_start_port


def run_command(visible_devices, dp_rank, vllm_engine_port):
    command = [
        "bash",
        "./run_dp_template.sh",
        visible_devices,
        str(vllm_engine_port),
        str(dp_size),
        str(dp_rank),
        dp_address,
        dp_rpc_port,
        str(tp_size),
    ]
    subprocess.run(command, check=True)


if __name__ == "__main__":
    template_path = "./run_dp_template.sh"
    if not os.path.exists(template_path):
        print(f"Template file {template_path} does not exist.")
        sys.exit(1)

    processes = []
    num_cards = dp_size_local * tp_size
    for i in range(dp_size_local):
        dp_rank = dp_rank_start + i
        vllm_engine_port = vllm_start_port + i
        visible_devices = ",".join(
            str(x) for x in range(i * tp_size, (i + 1) * tp_size)
        )
        process = multiprocessing.Process(
            target=run_command,
            args=(visible_devices, dp_rank, vllm_engine_port),
        )
        processes.append(process)
        process.start()

    for process in processes:
        process.join()

参考:GLM-5 PD 分离 A3 1*2P 1*4D 部署指导。

3.18.2 P0 节点 run_dp_template.sh 脚本

需要修改网卡名称及本机 IP。

# cp /path/to/recompute_scheduler.py /usr/local/python3.11.10/lib/python3.11/site-packages/vllm_ascend/core/recompute_scheduler.py
# cp /path/to/deepseek_mtp.py /usr/local/python3.11.10/lib/python3.11/site-packages/vllm/model_executor/models/deepseek_mtp.py
# export PYTHONPATH=/path/to/vllm-ascend:${PYTHONPATH}
export PYTHONPATH=/vllm_ascend/vllm:${PYTHONPATH}
export VLLM_VERSION=0.16.0
export VLLM_ASCEND_ENABLE_FUSED_MC2=1
export VLLM_ASCEND_ENABLE_MLAPO=0
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib

nic_name="<nic-name>"
local_ip="<p0-local-ip>"

export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_IF_IP=$local_ip
export GLOO_SOCKET_IFNAME=$nic_name
export TP_SOCKET_IFNAME=$nic_name
export HCCL_SOCKET_IFNAME=$nic_name

export OMP_PROC_BIND=false
export OMP_NUM_THREADS=10
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export VLLM_USE_V1=1
export HCCL_BUFFSIZE=256

export ASCEND_AGGREGATE_ENABLE=1
export ASCEND_TRANSPORT_PRINT=1
export ACL_OP_INIT_MODE=1
export ASCEND_A3_ENABLE=1
export VLLM_NIXL_ABORT_REQUEST_TIMEOUT=300000

export ASCEND_RT_VISIBLE_DEVICES=$1
export VLLM_ASCEND_ENABLE_FLASHCOMM1=0
export VLLM_VERSION=0.16.0

vllm serve /path/to/GLM-5-w8a8 \
    --host <listen-host> \
    --port $2 \
    --data-parallel-size $3 \
    --data-parallel-rank $4 \
    --data-parallel-address $5 \
    --data-parallel-rpc-port $6 \
    --tensor-parallel-size $7 \
    --enable-expert-parallel \
    --speculative-config '{"num_speculative_tokens": 3, "method": "deepseek_mtp"}' \
    --profiler-config \
    '{"profiler": "torch",
    "torch_profiler_dir": "/path/to/profiling",
    "torch_profiler_with_stack": false}' \
    --seed 1024 \
    --served-model-name dsv3 \
    --max-model-len 131072 \
    --additional-config '{"enable_npugraph_ex": true, "fuse_qknorm_rope": true, "fuse_muls_add": true, "multistream_overlap_shared_expert": true, "recompute_scheduler_enable": true, "rot_path": "/path/to/rot.safetensors"}' \
    --max-num-batched-tokens 4096 \
    --trust-remote-code \
    --max-num-seqs 64 \
    --quantization ascend \
    --gpu-memory-utilization 0.92 \
    --enforce-eager \
    --enable-auto-tool-choice \
    --tool-call-parser glm47 \
    --reasoning-parser glm45 \
    --kv-transfer-config \
    '{"kv_connector": "MooncakeConnectorV1",
    "kv_role": "kv_producer",
    "kv_port": "30000",
    "engine_id": "0",
    "kv_connector_extra_config": {
        "use_ascend_direct": true,
        "prefill": {
            "dp_size": 2,
            "tp_size": 16
        },
        "decode": {
            "dp_size": 16,
            "tp_size": 4
        }
    }}' 2>&1 | tee glm.log

3.18.3 P1 节点 run_dp_template.sh 脚本

与 P0 节点相比,需注意修改以下内容:

  • nic_name
  • local_ip
  • LD_PRELOAD
  • kv-transfer-config 中的 kv_port
  • kv-transfer-config 中的 engine_id

3.18.4 D 节点 run_dp_template.sh 脚本

需修改网卡名称及本机 IP。两个 D 节点脚本的区别仅在于 nic_name、local_ip 和 LD_PRELOAD。

# cp /path/to/recompute_scheduler.py /usr/local/python3.11.10/lib/python3.11/site-packages/vllm_ascend/core/recompute_scheduler.py
# cp /path/to/deepseek_mtp.py /usr/local/python3.11.10/lib/python3.11/site-packages/vllm/model_executor/models/deepseek_mtp.py
# export PYTHONPATH=/path/to/vllm-ascend:${PYTHONPATH}
export PYTHONPATH=/vllm_ascend/vllm:${PYTHONPATH}
export VLLM_ASCEND_ENABLE_FUSED_MC2=1
export VLLM_ASCEND_ENABLE_MLAPO=0
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
export VLLM_VERSION=0.16.0

nic_name="<nic-name>"
local_ip="<d-node-local-ip>"

export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_IF_IP=$local_ip
export GLOO_SOCKET_IFNAME=$nic_name
export TP_SOCKET_IFNAME=$nic_name
export HCCL_SOCKET_IFNAME=$nic_name

# Mooncake
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=10

export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export VLLM_USE_V1=1
export HCCL_BUFFSIZE=256

export ASCEND_AGGREGATE_ENABLE=1
export ASCEND_TRANSPORT_PRINT=1
export ACL_OP_INIT_MODE=1
export ASCEND_A3_ENABLE=1
export VLLM_NIXL_ABORT_REQUEST_TIMEOUT=300000

export TASK_QUEUE_ENABLE=1
export ASCEND_RT_VISIBLE_DEVICES=$1
export VLLM_VERSION=0.16.0

vllm serve /path/to/GLM-5-w8a8 \
    --host <listen-host> \
    --port $2 \
    --data-parallel-size $3 \
    --data-parallel-rank $4 \
    --data-parallel-address $5 \
    --data-parallel-rpc-port $6 \
    --tensor-parallel-size $7 \
    --enable-expert-parallel \
    --speculative-config '{"num_speculative_tokens": 3, "method": "deepseek_mtp"}' \
    --profiler-config \
    '{"profiler": "torch",
    "torch_profiler_dir": "/path/to/profiling",
    "torch_profiler_with_stack": false}' \
    --seed 1024 \
    --served-model-name dsv3 \
    --max-model-len 200000 \
    --max-num-batched-tokens 32 \
    --compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY", "cudagraph_capture_sizes": [4, 8, 12, 16, 20, 24, 28, 32]}' \
    --additional-config '{"enable_npugraph_ex": true, "fuse_qknorm_rope": true, "fuse_muls_add": true, "multistream_overlap_shared_expert": true, "recompute_scheduler_enable": true, "rot_path": "/path/to/rot.safetensors"}' \
    --trust-remote-code \
    --max-num-seqs 8 \
    --gpu-memory-utilization 0.92 \
    --async-scheduling \
    --quantization ascend \
    --enable-auto-tool-choice \
    --tool-call-parser glm47 \
    --reasoning-parser glm45 \
    --kv-transfer-config \
    '{"kv_connector": "MooncakeConnectorV1",
    "kv_role": "kv_consumer",
    "kv_port": "30100",
    "engine_id": "1",
    "kv_connector_extra_config": {
        "use_ascend_direct": true,
        "prefill": {
            "dp_size": 2,
            "tp_size": 16
        },
        "decode": {
            "dp_size": 16,
            "tp_size": 4
        }
    }}' 2>&1 | tee glm.log

3.18.5 server 文件

可以选择在每个节点创建 server.sh 后执行 bash server.sh,也可以直接在命令行执行。

P0:

python launch_online_dp.py \
    --dp-size 2 \
    --tp-size 16 \
    --dp-size-local 1 \
    --dp-rank-start 0 \
    --dp-address <p-master-ip> \
    --dp-rpc-port 12890 \
    --vllm-start-port 9100

P1:

python launch_online_dp.py \
    --dp-size 2 \
    --tp-size 16 \
    --dp-size-local 1 \
    --dp-rank-start 1 \
    --dp-address <p-master-ip> \
    --dp-rpc-port 12890 \
    --vllm-start-port 9100

D0:

python launch_online_dp.py \
    --dp-size 8 \
    --tp-size 4 \
    --dp-size-local 4 \
    --dp-rank-start 4 \
    --dp-address <d-master-ip> \
    --dp-rpc-port 12777 \
    --vllm-start-port 9100

D1:

python launch_online_dp.py \
    --dp-size 8 \
    --tp-size 4 \
    --dp-size-local 4 \
    --dp-rank-start 0 \
    --dp-address <d-master-ip> \
    --dp-rpc-port 12777 \
    --vllm-start-port 9100

3.18.6 请求转发 proxy.sh 脚本

port 为自定义服务端口,host 修改为 proxy 部署机器 IP。剩余需要替换 prefill 及 decode 的 IP,IP 个数和每个节点 DP 数相同。

unset http_proxy
unset https_proxy

python load_balance_proxy_server_example.py \
    --port 8000 \
    --host <proxy-host> \
    --prefiller-hosts \
        <prefill-host-0> \
        <prefill-host-1> \
    --prefiller-ports \
        9100 \
        9100 \
    --decoder-hosts \
        <decode-host-0> \
        <decode-host-0> \
        <decode-host-0> \
        <decode-host-0> \
        <decode-host-1> \
        <decode-host-1> \
        <decode-host-1> \
        <decode-host-1> \
    --decoder-ports \
        9100 9101 9102 9103 \
        9100 9101 9102 9103

load_balance_proxy_server_example.py 下载地址:vLLM-Ascend load_balance_proxy_server_example.py

3.19 PD 分离服务化参数调优

举例:80 并发,dp_size = 16。

  • max-num-seqs:单个 DP 最大请求个数应大于等于 总并发个数 / dp_size = 80 / 16 = 5,因此可以配置成 8。
  • cudagraph_capture_sizes:应修改为 [MTP + 1] * 单个 DP 域并发个数 的倍数,可以配置成 [4, 8, 12, 16, 20, 24, 28, 32]。
  • max-num-batched-tokens:单次 step 调度所有请求的 token 总数上限,应大于等于 max-num-seqs * [MTP + 1] = 8 * [3 + 1] = 32,才能跑满并发。

调优前:

max-num-seqs: 2
max-num-batched-tokens: 8

调优后:

max-num-seqs: 8
max-num-batched-tokens: 32

3.20 整体优化结果

待补充图示。

注意:性能数据 A3 1*2P 1*4D 以实测为准,仅供参考。

3.21 精度测试

模型数据集精度数据
GLM-5-w8a8GPQA-Diamond84-85,存在波动

4. 解决效果

通过叠加多种优化策略,GLM-5 模型的推理性能得到提升。测试结果显示,经初步性能调优后,基于 A3 实测在 GLM-5-w8a8 模型的单卡吞吐较开箱性能提升了 x 倍以上。