在昇腾8卡上极限部署 Qwen3-235B MoE

在昇腾8卡上极限部署 Qwen3-235B MoE


​​

🌈你好呀!我是 是Yu欸

🚀 感谢你的陪伴与支持~ 欢迎添加文末好友​​

🌌 在所有感兴趣的领域扩展知识,不定期掉落福利资讯(*^▽^*)


写在最前面

版权声明:本文为原创,遵循 CC 4.0 BY-SA 协议。转载请注明出处。

**摘要:**Qwen3-235B-A22B 是 2025 年开源界的"巨无霸"MoE 模型,其 BF16 全量权重高达 470GB。

对于单节点 8 张 Atlas 800 A2 (910B 64GB) 显卡来说,总显存仅 512GB。扣除系统开销,剩余空间不足 40GB。

如何在如此极限的显存条件下,不仅让模型跑起来,还能实现低延迟推理?本文将记录通过 vLLM 配合 HCCL AIV 加速、算子融合 及 NVMe 存储优化,达成高性能推理的全过程。

(本文专注于实践问题教程,以及优化性能效果的初步验证。企业级环境还是推荐8卡以上,或者多节点部署。)


一、NPU 驱动与固件安装

1.1 查看自己服务器版本信息

复制代码
echo '==/etc/os-release==' && cat /etc/os-release 2>/dev/null || true && echo '\n==lsb_release -a==' && lsb_release -a 2>/dev/null || true && echo '\n==/etc/issue==' && cat /etc/issue 2>/dev/null || true && echo '\n==uname -a==' && uname -a && echo '\n==uname -r==' && uname -r && echo '\n==arch==' && arch && echo '\n==dpkg arch==' && dpkg --print-architecture 2>/dev/null || true && echo '\n==kernel packages (dpkg) sample==' && (dpkg -l | grep linux-image || true)
  • DMI / 主机名:Huawei Atlas 900 RCK A2 Compute Node;Hostname: test-910b-01
  • 发行版:Ubuntu 22.04.4 LTS (Jammy)
  • 架构:aarch64 / arm64
  • 内核:5.15.0-101-generic
  • 已安装内核包示例:linux-image-5.15.0-101-generic

1.2 获取安装包

官方渠道:访问昇腾社区 → 然后根据自己的服务器系统 CPU 架构、昇腾芯片型号官网下载:

复制代码
https://support.huawei.com/enterprise/zh/ascend-computing/ascend-hdk-pid-252764743/software

需要下载的驱动固件如下:

复制代码
Ascend-hdk-910b-npu-driver_25.3.rc1_linux-aarch64.run
Ascend-hdk-910b-npu-firmware_7.8.0.2.212.run

1.3 安装前准备

复制代码
# 授予执行权限
chmod +x Ascend-hdk-910b-npu-driver_25.3.rc1_linux-aarch64.run
chmod +x Ascend-hdk-910b-npu-firmware_7.8.0.2.212.run
# 确认权限(应以root用户执行)
[[ $(id -u) -eq 0 ]] || echo "请切换到root用户执行安装"

1.4 安装驱动

复制代码
需要注册登录,否则报错:

ERR_NO:0x0091;ERR_DES:HwHiAiUser not exists! Please add HwHi                                                        AiUser

准备软件包-软件安装-CANN社区版8.0.0.alpha001开发文档-昇腾社区

添加用户:
切换到root用户下,执行如下命令创建HwHiAiUser用户。

groupadd HwHiAiUser //创建HwHiAiUser用户属组

useradd -g HwHiAiUser -d /home/HwHiAiUser -m HwHiAiUser //创建HwHiAiUser用户,其属组为HwHiAiUse

passwd HwHiAiUser

设置:123456
复制代码
# 完整安装(包含工具链)
./Ascend-hdk-910b-npu-driver_25.3.rc1_linux-aarch64.run \
--full --install-for-all

成功标志:终端显示 [INFO]Device startup success

1.5 安装固件

复制代码
./Ascend-hdk-910b-npu-firmware_7.8.0.2.212.run --full

成功标志:显示 Firmware package installed successfully! 并提示重启生效

1.6 安装验证

复制代码
# 重启系统使驱动生效
reboot

# 检查设备状态
npu-smi info

预期输出:应显示所有NPU设备的详细信息,包括:

  • 固件版本号
  • 内存与功耗状态

🏗️ 二、 基础设施建设:打破 I/O 瓶颈

在处理 TB 级模型时,普通的系统盘(SATA SSD)加载速度慢且空间不足,必须利用服务器的高性能存储资源。

2.1 存储扩容:3.5T NVMe 裸盘挂载

💡 为什么需要 NVMe?

大模型推理的第一步是加载权重。对于 470GB 的文件,普通硬盘读取可能需要 20-30 分钟,而高性能 NVMe 可以将此缩短至几分钟。此外,如果不挂载数据盘,系统盘很容易被模型权重撑爆导致死机。

第一步:设备发现与规划

首先检查系统磁盘状态。我们发现系统盘 sda2 仅剩 396G,而两块 3.5T 的 NVMe 盘处于闲置状态。

Bash

复制代码
root@test-910b-03:~# lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,TYPE
NAME      SIZE FSTYPE  MOUNTPOINT TYPE
sda     446.6G                    disk
├─sda2  445.6G ext4    /          part
...
nvme0n1   3.5T ext4               disk  <-- 目标盘1
nvme1n1   3.5T ext4               disk  <-- 目标盘2
复制代码
root@test-910b-03:~# lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,TYPE
NAME      SIZE FSTYPE  MOUNTPOINT TYPE
sda     446.6G                    disk
├─sda1    953M vfat    /boot/efi  part
├─sda2  445.6G ext4    /          part
└─sda3     65M iso9660            part
nvme0n1   3.5T ext4               disk
nvme1n1   3.5T ext4               disk
root@test-910b-03:~# df -hT
Filesystem     Type   Size  Used Avail Use% Mounted on
tmpfs          tmpfs  151G  4.7M  151G   1% /run
/dev/sda2      ext4   439G   25G  396G   6% /
tmpfs          tmpfs  753G     0  753G   0% /dev/shm
tmpfs          tmpfs  5.0M     0  5.0M   0% /run/lock
/dev/sda1      vfat   952M  6.4M  945M   1% /boot/efi
tmpfs          tmpfs  151G     0  151G   0% /run/user/0
root@test-910b-03:~# cat /proc/partitions
major minor  #blocks  name

 259        0 3750738264 nvme0n1
 259        1 3750738264 nvme1n1
   8        0  468320256 sda
   8        1     975872 sda1
   8        2  467276800 sda2
   8        3      66543 sda3

规划策略

  • nvme0n1 -> 挂载至 /root/wyt/data/models/qwen3_origin_weight (用于存放 Qwen3-235B)
  • nvme1n1 -> 挂载至 /root/wyt/data/models/deepseek32w8a8_origin_weight (预留给 DeepSeek模型)
第二步:创建挂载点与执行挂载

由于磁盘已有文件系统(ext4),无需格式化,直接挂载即可。

Bash

复制代码
# 1. 创建专用目录
mkdir -p /root/wyt/data/models/qwen3_origin_weight
mkdir -p /root/wyt/data/models/deepseek32w8a8_origin_weight

# 2. 执行挂载命令
mount /dev/nvme0n1 /root/wyt/data/models/qwen3_origin_weight
mount /dev/nvme1n1 /root/wyt/data/models/deepseek32w8a8_origin_weight
第三步:验证挂载与读写测试 (关键)

挂载看似简单,但必须确认路径正确具备读写权限,否则后续下载权重或 Docker 映射时会报错。

状态检查

Bash

复制代码
root@test-910b-03:~# df -hT | grep nvme
/dev/nvme0n1   ext4   3.5T   28K  3.3T   1% /root/wyt/data/models/qwen3_origin_weight
/dev/nvme1n1   ext4   3.5T   28K  3.3T   1% /root/wyt/data/models/deepseek32w8a8_origin_weight

读写权限"冒烟测试":

我们通过写入一个测试文件来确保磁盘不是 Read-only 状态:

Bash

复制代码
root@test-910b-03:~# echo ok_nvme0 > /root/wyt/data/models/qwen3_origin_weight/.verify_write_nvme0 && echo ok_nvme1 > /root/wyt/data/models/deepseek32w8a8_origin_weight/.verify_write_nvme1 ; echo '---cat nvme0 file---' ; cat /root/wyt/data/models/qwen3_origin_weight/.verify_write_nvme0 || true ; echo '---cat nvme1 file---' ; cat /root/wyt/data/models/deepseek32w8a8_origin_weight/.verify_write_nvme1 || true ; echo '---ls mounts---' ; ls -ld /root/wyt/data/models/qwen3_origin_weight /root/wyt/data/models/deepseek32w8a8_origin_weight ; echo '---lsblk---' ; lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,TYPE ; echo '---df for nvme---' ; df -hT | egrep 'nvme0n1|nvme1n1' || true ; echo '---mount lines---' ; mount | egrep 'nvme0n1|nvme1n1' || true
---cat nvme0 file---
ok_nvme0
---cat nvme1 file---
ok_nvme1
---ls mounts---
drwxr-xr-x 3 root root 4096 Dec 23 13:49 /root/wyt/data/models/deepseek32w8a8_origin_weight
drwxr-xr-x 3 root root 4096 Dec 23 13:49 /root/wyt/data/models/qwen3_origin_weight
---lsblk---
NAME      SIZE FSTYPE  MOUNTPOINT                                         TYPE
sda     446.6G                                                            disk
├─sda1    953M vfat    /boot/efi                                          part
├─sda2  445.6G ext4    /                                                  part
└─sda3     65M iso9660                                                    part
nvme0n1   3.5T ext4    /root/wyt/data/models/qwen3_origin_weight          disk
nvme1n1   3.5T ext4    /root/wyt/data/models/deepseek32w8a8_origin_weight disk
---df for nvme---
/dev/nvme0n1   ext4   3.5T   32K  3.3T   1% /root/wyt/data/models/qwen3_origin_weight
/dev/nvme1n1   ext4   3.5T   32K  3.3T   1% /root/wyt/data/models/deepseek32w8a8_origin_weight
---mount lines---
/dev/nvme0n1 on /root/wyt/data/models/qwen3_origin_weight type ext4 (rw,relatime)
/dev/nvme1n1 on /root/wyt/data/models/deepseek32w8a8_origin_weight type ext4 (rw,relatime)

注:如果挂载报错或需要永久挂载,请参考文末的【踩坑指南】。

2.2 环境与 Conda 配置

对于 Aarch64 架构,标准的 Anaconda 安装流程如下:

Bash

复制代码
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-aarch64.sh
bash Miniconda3-latest-Linux-aarch64.sh
conda create -n py310 python=3.10 -y && conda activate py310

2.3 源码准备

下载昇腾官方推理仓,这对于理解底层算子优化至关重要。

Bash

复制代码
git clone https://gitcode.com/cann/cann-recipes-infer.git
cd cann-recipes-infer
pip3 install -r ./models/qwen3-moe/requirements.txt

2.4 权重下载:断点续传方案

💡 直接使用 git clone 下载 500GB 的 Hugging Face 仓库经常会因为网络波动中断,且断点续传体验极差。我们编写了一个 Python 脚本,利用 huggingface_hub 库的 snapshot_download 接口,实现Hash 校验、断点续传和国内镜像加速。

下载脚本 ( qwen3_download.py**) 核心逻辑:**

  1. 镜像加速 :支持 HF_ENDPOINT 环境变量。
  2. 实体文件 :设置 local_dir_use_symlinks=False,避免软链接带来的跨磁盘移动问题。
  3. 鲁棒性:网络超时自动重试。

执行下载:

Bash

复制代码
source /root/miniconda3/bin/activate py310
# 设置国内镜像站
export HF_ENDPOINT=https://hf-mirror.com
# 设置密钥
export HF_TOKEN="hf_xxx..."   # 请替换为你的 token
# 启动下载 (目标路径为 NVMe 挂载点)
python3 qwen3_download.py --target /root/wyt/data/models/qwen3_origin_weight

针对 Hugging Face 下载频繁中断的问题,我们编写了 qwen3_download.py

  • 优化逻辑 :每次下载都会进行 snapshot_download 的 Hash 校验。
  • 本地化 :通过 local_dir_use_symlinks=False 下载实体文件,避免移动目录后软链接失效。
  • 镜像站 :配合 HF_ENDPOINT=``https://hf-mirror.com 提升国内下载速度。

执行下载

复制代码
source /root/miniconda3/bin/activate py310
export HF_ENDPOINT=https://hf-mirror.com
export HF_TOKEN="hf_your_token"# 确保目录是我们刚才挂载的 NVMe 盘路径
python3 qwen3_download.py --target /root/wyt/data/models/qwen3_origin_weight

针对 500GB 文件的下载难题,我们优化了 qwen3_download.py 脚本:

  • 断点续传 :显式设置 resume_download=True

  • 实体文件local_dir_use_symlinks=False,直接下载到我们刚才挂载的 NVMe 盘中。

    #!/usr/bin/env python3

    """
    可靠的 Hugging Face 模型快照下载脚本 (优化版)
    优化点:

    1. 修复逻辑:不再因目录非空就跳过下载,确保触发 snapshot_download 的断点续传检查。
    2. 增强鲁棒性:失败时不再返回 0 (假成功),除非文件确实已同步。
    3. 参数优化:显式开启 resume_download,并禁用软链接以获得完整权重文件。
      """

    import os
    import sys
    import time
    import argparse
    import logging
    from huggingface_hub import snapshot_download, HfApi
    from huggingface_hub.utils import HfHubHTTPError

    def setup_logging(logfile):
    handlers = [logging.StreamHandler(sys.stdout)]
    try:
    # 确保护展目录存在
    log_dir = os.path.dirname(logfile)
    if log_dir and not os.path.exists(log_dir):
    os.makedirs(log_dir, exist_ok=True)
    handlers.insert(0, logging.FileHandler(logfile))
    except Exception:
    pass
    logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s', handlers=handlers)

    def check_token(token):
    if not token:
    logging.warning('HF_TOKEN not set in environment variables.')
    return False
    try:
    api = HfApi()
    info = api.whoami(token=token)
    logging.info('HF_TOKEN valid. User: %s', info.get('name', 'unknown'))
    return True
    except Exception as e:
    logging.warning('HF_TOKEN check failed (Wait for download to verify actual permissions): %s', e)
    return False

    def main():
    parser = argparse.ArgumentParser(description='Resilient snapshot downloader for HF models')
    parser.add_argument('--repo-id', default='Qwen/Qwen3-235B-A22B', help='Hugging Face repo id')
    parser.add_argument('--target', default='/root/wyt/data/models/qwen3_origin_weight', help='Local target directory')
    # --force 现在的含义是:即使遇到 checksum 不匹配也强制重新下载(通常不需要用)
    parser.add_argument('--force', action='store_true', help='Force re-download (rarely needed with resume_download)')
    parser.add_argument('--repo-type', default='model', help='repo_type: model, dataset, or space')
    parser.add_argument('--retry-interval', type=int, default=10, help='Seconds to wait between retries')
    parser.add_argument('--max-retries', type=int, default=0, help='0 = infinite retries')
    parser.add_argument('--log', default='/root/wyt/qwen3_download.log', help='Log file path')
    args = parser.parse_args()

    复制代码
      setup_logging(args.log)
    
      token = os.environ.get('HF_TOKEN')
      logging.info('>>> Starting Job: Download %s to %s', args.repo_id, args.target)
      
      # 简单的 Token 检查(非阻塞)
      if token:
          check_token(token)
    
      # 确保目标目录存在
      try:
          os.makedirs(args.target, exist_ok=True)
      except Exception as e:
          logging.error('Cannot create target dir %s: %s', args.target, e)
          return 3
    
      if not os.access(args.target, os.W_OK):
          logging.error('Target dir %s is not writable', args.target)
          return 4
    
      # --- 核心修改:移除 "目录非空则直接返回" 的逻辑 ---
      # 只要运行脚本,就应该调用 snapshot_download 进行完整性校验。
      # snapshot_download 内部会自动跳过已下载且 Hash 匹配的文件。
    
      attempt = 0
      while True:
          attempt += 1
          try:
              logging.info(f'[Attempt {attempt}] Starting snapshot_download...')
              
              # 核心调用
              path = snapshot_download(
                  repo_id=args.repo_id, 
                  local_dir=args.target, 
                  token=token, 
                  repo_type=args.repo_type,
                  resume_download=True,           # 显式开启断点续传
                  local_dir_use_symlinks=False,   # 下载实体文件(方便移动和训练读取)
                  force_download=args.force,      # 除非手动指定,否则不强制重新下载
                  # max_workers=8                 # 可选:如果没开启 hf_transfer,可以增加并发数
              )
              
              logging.info('SUCCESS: Download finished. Local path: %s', path)
              print(path) # 输出路径供外部调用
              return 0
    
          except Exception as e:
              # 捕获所有异常,分析是否致命
              logging.exception('ERROR during snapshot_download (attempt %d): %s', attempt, e)
              
              estr = str(e).lower()
              
              # --- 鉴权错误处理逻辑 ---
              # 如果是 401/403,通常是 Token 没权限或者 Repo 不存在。
              # 但你也提到了 CAS 403 可能是网络/镜像站问题,所以这里策略稍微宽松一点:
              # 如果是 401 (Unauthorized),绝对是 Token 错,直接退出。
              # 如果是 403 (Forbidden),有可能是 IP 被封或 Token 权限不够。
              
              if '401' in estr or 'unauthorized' in estr:
                  logging.error('Fatal Authentication Error (401). Check your HF_TOKEN.')
                  return 2
              
              # 针对你遇到的 403,如果是 CAS 链接失效,重试几次可能也没用,但也可能是瞬时的
              # 这里选择继续重试,或者你可以设置尝试 10 次后退出
              
              if args.max_retries > 0 and attempt >= args.max_retries:
                  logging.error('Max retries (%d) reached. Exiting with failure.', args.max_retries)
                  return 1
    
              # 失败后不要立即返回 0 (假成功)!
              # 除非你非常确定旧文件能用,否则这里应该继续循环重试。
              
              logging.info('Waiting %d seconds before retrying...', args.retry_interval)
              time.sleep(args.retry_interval)

    if name == 'main':
    try:
    sys.exit(main())
    except KeyboardInterrupt:
    logging.info("\nInterrupted by user. Exiting.")
    sys.exit(130)


🐳 三、 容器化部署:vLLM 的"极限配置"

在 8 卡 A2 环境启动 235B 模型,极易遇到 No available memory。我们需要精细控制每一 MB 的显存。

3.1 启动前检查

在启动 Docker 之前,必须确保宿主机的驱动和硬件状态正常。

  1. 检查 NPU 状态 : 执行 npu-smi info,确认 8 张卡全部在线,且型号为 910B。
  2. 确认权重路径 : 根据你之前的信息,权重位于 /root/wyt/data/models/qwen3_origin_weight/qwen3_snapshot。请确保该目录下包含 config.json 和所有的 .safetensors 文件。

请务必确认目录下是否有以下文件。如果缺少,vLLM 无法初始化:(反复提醒,就是因为这里我最开始掉了一个权重转换的文件,导致没跑起来hh)

Bash

复制代码
ls -lh /root/wyt/data/models/qwen3_origin_weight/qwen3_snapshot

必须包含:

  • config.json
  • tokenizer.jsontokenizer_config.json
  • 大量的 .safetensors 文件

3.2 启动 Docker

使用昇腾专版 vLLM 镜像。

关键参数: --shm-size=512g (通信必需)、--device /dev/davinci_manager (NPU管理)。

Bash

复制代码
# 定义变量
export IMAGE=quay.io/ascend/vllm-ascend:v0.12.0rc1
export MODEL_PATH=/root/wyt/data/models/qwen3_origin_weight/qwen3_snapshot

docker run -itd \
    --name vllm-ascend-qwen3 \
    --net=host \
    --privileged \
    --shm-size=512g \
    --device /dev/davinci0:/dev/davinci0 \
    --device /dev/davinci1:/dev/davinci1 \
    --device /dev/davinci2:/dev/davinci2 \
    --device /dev/davinci3:/dev/davinci3 \
    --device /dev/davinci4:/dev/davinci4 \
    --device /dev/davinci5:/dev/davinci5 \
    --device /dev/davinci6:/dev/davinci6 \
    --device /dev/davinci7:/dev/davinci7 \
    --device /dev/davinci_manager \
    --device /dev/devmm_svm \
    --device /dev/hisi_hdc \
    -v /usr/local/dcmi:/usr/local/dcmi \
    -v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \
    -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
    -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
    -v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
    -v /etc/ascend_install.info:/etc/ascend_install.info \
    -v $MODEL_PATH:$MODEL_PATH \
    $IMAGE bash

# 进入容器
docker exec -it vllm-ascend-qwen3 bash

再次进入容器在这里,并清理环境

Bash

复制代码
# 进入你之前启动的 vLLM 容器
docker exec -it vllm-ascend-qwen3 bash

# 容器内操作:清理可能的显存残留
pkill -9 python3
python3 -c "import torch; import torch_npu; torch.npu.empty_cache()"source /usr/local/Ascend/ascend-toolkit/set_env.sh

3.3 容器内环境初始化

进入容器后,配置昇腾运行环境和通信超时时间(防止加载大模型超时)。

Bash

复制代码
# 1. 加载 CANN
source /usr/local/Ascend/ascend-toolkit/set_env.sh

# 2. 增加 HCCL 超时时间 (加载 470G 权重很大,时间较长)
export HCCL_CONNECT_TIMEOUT=1200
export HCCL_EXEC_TIMEOUT=1200

# 3. 开启内存扩展 (应对碎片化)
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

3.4 拉起 vLLM 服务 (极致模式)

这是最关键的一步。我们采用**"极致模式"** 启动,优先确保不 OOM。

Bash

复制代码
python3 -m vllm.entrypoints.openai.api_server \
    --model /root/wyt/data/models/qwen3_origin_weight/qwen3_snapshot \
    --tensor-parallel-size 8 \
    --trust-remote-code \
    --gpu-memory-utilization 0.94 \
    --max-model-len 2048 \
    --max-num-seqs 16 \
    --distributed-executor-backend mp \
    --dtype bfloat16 \
    --enforce-eager \
    --port 8000

🔧 参数解析:

  • --data-parallel-size 1 和 --tensor-parallel-size 8 是数据并行(DP)和张量并行(TP)大小的常用设置。
  • --max-model-len 表示上下文长度,即单个请求的输入加输出的最大值。
  • --max-num-seqs 表示每个DP组允许处理的最大请求数。如果发送到服务的请求数量超过此限制,多余的请求将处于等待状态,不会被调度。请注意,等待状态花费的时间也会计入TTFT和TPOT等指标。因此,在测试性能时,通常建议--max-num-seqs * --data-parallel-size >= 实际总并发量。
  • --max-num-batched-tokens 表示模型单步处理的最大token数。目前vLLM v1调度默认启用ChunkPrefill/SplitFuse,这意味着
    • (1) 如果请求的输入长度大于--max-num-batched-tokens,则会根据--max-num-batched-tokens分多轮计算;
    • (2) 解码请求优先调度,仅当有可用容量时才调度预填充请求。
    • 通常情况下,如果--max-num-batched-tokens设置得较大,整体延迟会较低,但对GPU显存(激活值使用)的压力会更大。
  • --gpu-memory-utilization表示vLLM将用于实际推理的HBM比例。其主要功能是计算可用的kv_cache大小。在预热阶段(vLLM中称为profile run),vLLM会记录输入大小为--max-num-batched-tokens的推理过程中的峰值GPU内存使用量。然后,可用的kv_cache大小计算公式为:--gpu-memory-utilization * HBM大小 - 峰值GPU内存使用量。因此,--gpu-memory-utilization值越大,可使用的kv_cache越多。然而,由于预热阶段的GPU内存使用量可能与实际推理时不同(例如,由于EP负载不均),将--gpu-memory-utilization设置得过高可能导致实际推理时出现OOM(显存不足)问题。默认值为0.9
  • --enable-expert-parallel表示启用EP。请注意,vLLM不支持ETP和EP的混合方法;也就是说,MoE可以使用纯EP或纯TP。
  • --no-enable-prefix-caching表示禁用了前缀缓存。要启用它,请删除此选项。
  • --quantization "ascend"表示使用了量化。要禁用量化,请移除此选项。
  • --compilation-config包含与aclgraph图模式相关的配置。最重要的配置是"cudagraph_mode"和"cudagraph_capture_sizes",它们具有以下含义:"cudagraph_mode":表示特定的图模式。目前支持"PIECEWISE"和"FULL_DECODE_ONLY"。图模式主要用于降低算子调度的开销。目前推荐使用"FULL_DECODE_ONLY"。
  • "cudagraph_capture_sizes":表示不同级别的图模式。默认值为[1, 2, 4, 8, 16, 24, 32, 40,..., --max-num-seqs]。在图模式下,不同级别的图的输入是固定的,级别之间的输入会自动填充到下一个级别。目前推荐使用默认设置。仅在某些场景下,才有必要单独设置此项以获得最佳性能。
  • export VLLM_ASCEND_ENABLE_FLASHCOMM1=1表示启用Flashcomm1优化。目前,此优化仅支持tp_size > 1的场景下的MoE。
  • --gpu-memory-utilization 0.94: 生死线。权重占了 92%,预留 94% 给 vLLM,剩下 6% 给系统内核和 PyTorch 开销。
  • --max-model-len 2048: 克制。上下文越长,KV Cache 越大。先跑通 2k,再谈 32k。
  • --enforce-eager: 空间换时间。禁用 CUDA Graph / NPU Graph 捕获,虽然慢一点,但能节省 2-4GB 的图编译显存峰值。

3.5 服务验证

服务启动后(可能需要 5-10 分钟加载权重),打开另一个终端测试:

Bash

复制代码
curl http://localhost:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "/root/wyt/data/models/qwen3_origin_weight/qwen3_snapshot",
        "messages": [
            {"role": "user", "content": "你好,你是谁?"}
        ],
        "temperature": 0.6
    }'

🚀 四、 极致调优:从"跑通"到"高性能"

跑通只是起点。为了逼近极致性能,我们需要开启昇腾特有的加速"黑科技"。

4.1 优化一:降级 V0 引擎

目前在 A2 平台上,vLLM 的 V0 引擎在多进程 (MP) 模式下对 MoE 的支持比 V1 更稳定,显存碎片更少。

Bash

复制代码
export VLLM_USE_V1=0

分布式策略:张量并行(TP)与计算分解。针对 235B 这种规模,使用 TP8(张量并行大小为 8)将模型分摊到所有卡上。

  • Attention TP 深度优化
    • 切分策略 :对 QKV 头进行切分,确保每张卡独立计算一部分 Query 头。在 GQA(分组查询注意力)场景下,通过公式 max(num_kv_heads // tp_size, 1) 确保每张卡至少分配到一个 Key/Value 头。
    • 计算分解 :实战中将 Q、K、V 的线性层合并为一次 merged_qkv_proj 矩阵乘法。这不仅减少了算子下发的开销,更提升了 NPU Cube 核的算力利用率。
  • MoE TP 优化与 SwiGLU 融合
    • 专家层切分 :对 MoE 层的 gate_proj(门控)与 up_proj(上投影)进行列切分,并利用 torch_npu.npu_swiglu 融合算子。
    • 技术原理 :传统的计算需要拆分为多步,而 npu_swiglu 能在一次调用中完成"分块、SiLU 激活、乘法"三步操作,极大降低了中间激活值的显存占用,这对于显存捉襟见肘的 235B 模型至关重要。

4.2 优化二:使能 HCCL AIV 加速与算子融合

这是提升性能的核心。

  • AIV 展开:利用 NPU 的 Vector Core 加速集合通信(AllReduce),降低 TP8 通信延迟。
    • 在传统的分布式计算中,Cube 核负责计算,Vector 核在通信时往往闲置。开启此模式后,NPU 会利用 Vector Core 来加速集合通信操作。
    • 这意味着计算与通信在硬件层面实现了更高程度的并行,从而显著降低了 TP8 模式下的跨卡通信延迟。
  • GMM 融合:使用 GroupedMatmul 算子并行计算多个 MoE 专家,大幅提升吞吐。
    • MoE 模型的核心在于"专家"。当专家数量(如 64 个)较多时,传统的循环计算会导致算子下发频繁,效率极低。
    • 使能 torch_npu.npu_grouped_matmul 融合算子。它的核心原理是"一次搬入,多方计算":将不同专家的计算任务打包,通过 GMM 同时进行多个专家的矩阵乘法。这大幅提升了 HBM(高带宽显存)的读取效率,减少了重复的数据搬运。
  • 路由(Routing)链路优化
    • npu_moe_gating_top_k_softmax:代替原有的"先 TopK 再 Softmax"多步操作,在 Vector 核内一气呵成完成 Token 分派。
    • npu_moe_init_routing & npu_moe_finalize_routing:这两个算子负责将不同专家计算完成后的 Token 重新排布并加权求和。通过底层的硬件级同步,消除了 CPU 与 NPU 之间的非必要交互,保证了计算流的连续性。

在启动 vLLM 前设置 环境变量

Bash

复制代码
# 使能 AIV 展开模式:利用 Vector Core 加速通信
export HCCL_OP_EXPANSION_MODE=AIV

# 参考文档:开启集合通信的算法展开优化
export HCCL_DETERMINISTIC=True

4.3 优化三:极限显存压榨

如果基准测试稳定,可以尝试更激进的配置:

Bash

复制代码
python3 -m vllm.entrypoints.openai.api_server \
    ...
    --gpu-memory-utilization 0.98 \  # 压榨到 98%
    --swap-space 16 \                # 增加 CPU 交换空间
    --max-model-len 512              # 进一步缩短上下文以测试吞吐极限

📊 五、 测评实战:性能对比

我们编写了 benchmark.py 进行流式推理测试,对比优化前后的效果。

  1. 图编译耗时 :由于本次测评去掉 --enforce-eager 后,第一次推理会非常慢(因为在做算子图编译),请参考以第二次及以后的推理时间作为最终数据。

  2. A2 与 A3 的差距:优化特性文档数据是基于 A3 的,A3 的计算密度更高。在 A2 上,在 235B 这种规模下已经是很好的成绩。

    root@test-910b-03:/mnt# python3 benchmark.py
    发送测试请求: 你好,你是谁?

    ==============================
    统计结果:

    • 首字延迟 (TTFT): 1540.41 ms
    • 每 Token 延迟 (TPOT): 198.62 ms
    • 生成吞吐量: 4.78 tokens/s
      ==============================
      root@test-910b-03:/mnt# python3 benchmark.py
      发送测试请求: 你好,你是谁?

    ==============================
    统计结果:

    • 首字延迟 (TTFT): 605.26 ms
    • 每 Token 延迟 (TPOT): 197.35 ms
    • 生成吞吐量: 4.99 tokens/s
复制代码
root@test-910b-03:/mnt# python3 benchmark.py
发送测试请求: 你好,你是谁?


==============================
统计结果:
- 首字延迟 (TTFT): 1678.09 ms
- 每 Token 延迟 (TPOT): 212.19 ms
- 生成吞吐量: 4.47 tokens/s
==============================
root@test-910b-03:/mnt# python3 benchmark.py
发送测试请求: 你好,你是谁?


==============================
统计结果:
- 首字延迟 (TTFT): 623.60 ms
- 每 Token 延迟 (TPOT): 216.23 ms
- 生成吞吐量: 4.56 tokens/s
==============================

|-----------------------|---------------------|-------------------|----------|
| 指标 | 基础模式 (Baseline) | 优化模式 (Opt) | 提升幅度 |
| 首字延迟 (TTFT) | ~623 ms | ~605 ms | ↓ 6% |
| 每 Token 延迟 (TPOT) | ~212 ms | ~197 ms | ↓ 7% |
| 生成吞吐量 | 4.47 tokens/s | 4.99 tokens/s | ↑ 11% |

结论:

  • TTFT 降低 :得益于 HCCL_OP_EXPANSION_MODE=AIV,跨卡通信延迟显著降低,首字响应速度提升明显。
  • 稳定性:在 235B 超大参数规模下,A2 平台依然保持了约 5 tokens/s 的稳定输出,能够满足对话类应用的交互需求。

🛠️ 六、 踩坑指南 (FAQ)

在实战过程中,我们解决了一系列环境问题,在此汇总以供参考。

Q1: 安装驱动报错 HwHiAiUser not exists

现象:运行驱动安装脚本报错 ERR_NO:0x0091;ERR_DES:HwHiAiUser not exists!。

原因:NPU 驱动运行需要特定的用户组。

解决:

Bash

复制代码
groupadd HwHiAiUser
useradd -g HwHiAiUser -d /home/HwHiAiUser -m HwHiAiUser
# 再次运行安装脚本即可
./Ascend-hdk-910b-npu-driver_*.run --full --install-for-all

Q2: 驱动安装报错 Text file busy

现象:./Ascend-hdk...run: /bin/bash: bad interpreter: Text file busy。

原因:有其他进程(如 sftp)正在占用该安装包文件。

解决:

Bash

复制代码
# 查找占用进程
lsof ./Ascend-hdk-910b-npu-driver_*.run
# 杀掉占用进程 (PID 替换为实际查到的)
kill -9 <PID>

Q3: Hugging Face Network is unreachable

现象:下载 LongBench 数据集时报 ConnectionError。

解决:

  1. 手动下载 :在有网环境下载 data.zip,上传至 /root/wyt/data/dataset/LongBench/
  1. 解压与配置

Bash

复制代码
unzip -o data.zip -d /root/wyt/data/dataset/LongBench/
# 修改 LongBench.py 中的路径指向本地 /data 目录

Q4: 挂载磁盘只有只读权限?

现象:挂载 NVMe 后无法写入。

解决:如果磁盘之前未正确分区,需要重建分区表。

Bash

复制代码
df -h /root/yu/deepseek/DeepSeek-V3.2-Exp-w8a8
lsblk
fdisk /dev/nvme0n1

在提示符下输入 g(创建新的空 GPT 分区表),然后 y 确认。
创建新分区:
输入 n(添加新分区),然后按默认值(分区号 1,起始扇区默认,结束扇区默认,使用整个磁盘)。
写入并退出:
输入 w(写入表到磁盘并退出)。

lsblk
blkid /dev/nvme0n1p1
blkid /dev/nvme1n1
mkdir -p /mnt/temp
mount /dev/nvme1n1 /mnt/temp
rsync -av /root/yu/deepseek/DeepSeek-V3.2-Exp-w8a8/ /mnt/temp/


umount /mnt/temp
rm -rf /root/yu/deepseek/DeepSeek-V3.2-Exp-w8a8/*
mount /dev/nvme1n1 /root/yu/deepseek/DeepSeek-V3.2-Exp-w8a8
cat /etc/fstab
echo "UUID=d1e142b8-aa57-4f6e-bed0-be5024308b5a /root/yu/deepseek/DeepSeek-V3.2-Exp-w8a8 ext4 defaults 0 2" >> /etc/fstab
df -h /root/yu/deepseek/DeepSeek-V3.2-Exp-w8a8

🔮 七、 结语与展望

在昇腾 910B 上部署 Qwen3-235B 是一次对硬件极限的挑战。在显存空间仅剩不足 10% 的极限环境下,通过 GMM 专家融合HCCL AIV 通信加速 以及 SwiGLU 算子下沉,我们成功搭建起了一个高效、稳定的推理环境,验证了昇腾架构对超大规模 MoE 模型的支持能力。

希望这篇博文能帮助更多在昇腾平台上探索大模型前沿的开发者!

参考文档:

Qwen3-MoE模型在NPU实现低时延推理

https://atomgit.com/cann/cann-recipes-infer/blob/master/models/qwen3-moe/README.md

基于Atlas A3训练/推理集群的Qwen3-MoE模型低时延推理性能优化实践

https://atomgit.com/cann/cann-recipes-infer/blob/master/docs/models/qwen3-moe/qwen3_moe_optimization.md

多节点Ray (Qwen/Qwen3-235B-A22B)

https://docs.vllm.com.cn/projects/ascend/en/latest/tutorials/multi_node_ray.html

vllm-ascend安装

https://docs.vllm.com.cn/projects/ascend/en/latest/installation.html

Qwen3-235B-A22B的vllm部署指南

https://docs.vllm.com.cn/projects/ascend/en/latest/tutorials/Qwen3-235B-A22B.html#google_vignette

相关推荐
意疏17 小时前
Llama-2-7b大模型在昇腾NPU上的部署与性能测评报告
昇腾
破烂pan1 天前
模型格式 × GPU × 显存 × vLLM 参数模板对照表
vllm
Token_w3 天前
快速入门 vLLM-Ascend:开源仓结构、环境部署与基础配置
昇腾
技术路上的探险家3 天前
vLLM常用启动参数的详细解释
python·大模型·qwen·vllm
LucianaiB3 天前
昇腾NPU实战:Llama-2-7B大模型的部署全流程与性能深度分析
cpu·llama·昇腾
倔强的石头1063 天前
昇腾NPU实战:国产之光Qwen2.5-7B-Instruct在AtomGit环境下的硬核部署与稳定性测评
qwen·昇腾
每天都要写算法(努力版)4 天前
【混合注意力模型的 KV Cache 设计与统一管理实践解析】
llm·vllm·kv cache
是店小二呀4 天前
在 AtomGit 昇腾 Atlas 800T上解锁 SGLang:零成本打造高性能推理服务
人工智能·pytorch·深度学习·npu
是店小二呀4 天前
解构 Qwen2 在昇腾 Atlas 800T 上的极限性能:基于 SGLang 的深度评测
人工智能·npu