昇腾算子包安装与模型部署测试
一、需求背景
X公司当前使用N公司H20计算卡做DeepSeek推理业务,希望使用昇腾卡做相关测试对比,如果性能较优、则考虑选择昇腾产品。
当前,我们需要基于客户的使用场景,进行对应的性能测试、并得到测试输出结果,以便争取客户。
1.1 测试条件
- 客户自主提供星火测试脚本,并非使用昇腾
benchmark_tools,因此需要一定的适配工作。 - 测试模型为满血版DeepSeek-R1。
- 需要使用昇腾侧最新提供的算子包来做测试验证。
- 使用两台Ascend Snt9B服务器,部署模式为Lite Server。
- 最大token为8k,输入输出分别为1000与100,并发从1逐步增加到64。
1.2 评估维度
- 输出token总吞吐量 -
output_throughput - 首token时延TP99(ms)-
p99_ttft_ms - 平均首token时延(ms)-
mean_ttft_ms - 增量时延TP99(ms)-
p99_tpot_ms - 平均增量时延(ms)-
mean_tpot_ms
二、测试实践
2.1 环境检查
在部署前,我们需要检查当前机器环境是否满足条件,参考文档:
2.2 模型权重文件拉取
一般而言,我们需要利用OBS通来从公网下载对应的模型权重文件。
这里为了节省时间,我们直接使用scp从其他机器节点拉取即可。
通常情况下,我们会将模型拉取放在宿主机的/home下,也可以创建一个单独的DeepSeek文件夹存放。
2.3 获取推理镜像
这里,我们使用命令:
docker pull registry-cbu.huawei.com/atelier/pytorch_2_1_ascend:pytorch_2.1.0-cann_8.0.0-py_3.10-hce_2.0.2412-aarch64-snt9b-xxxxx
在拉取时,可能遇到相关问题,参考文档:
2.4 启动容器
启动命令:
docker run -itd \
--device=/dev/davinci0 \
--device=/dev/davinci1 \
--device=/dev/davinci2 \
--device=/dev/davinci3 \
--device=/dev/davinci4 \
--device=/dev/davinci5 \
--device=/dev/davinci6 \
--device=/dev/davinci7 \
-v /etc/localtime:/etc/localtime \
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
-v /etc/ascend_install.info:/etc/ascend_install.info \
--device=/dev/davinci_manager \
--device=/dev/devmm_svm \
--device=/dev/hisi_hdc \
-v /var/log/npu/:/usr/slog \
-v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi \
-v /sys/fs/cgroup:/sys/fs/cgroup:ro \
- v ${dir}:${container_work_dir} \
--net=host \
--name ${container_name} \
${image_id} \
/bin/bash
对于容器的启动参数,需要注意的是:
${dir}为宿主机中权重文件所在的路径,这里不需要完全等同,只需要包含权重文件的路径即可:- 例如,我们将权重文件放在了
/home/DeepSeek目录下,那么这里${dir}可以直接指定为/home
- 例如,我们将权重文件放在了
${container_work_dir}为需要挂载到的容器目录,一般而言我们可以指定为/home/DeepSeek,或者/data/models,需要注意的是:- 该目录不能挂载到容器中的
/home/ma-user目录,此目录为ma-user用户家目录。如果容器挂载到/home/ma-user下,拉起容器时会与基础镜像冲突,导致基础镜像不可用。 - 上述目录的指定与映射,可以实现容器与宿主机的文件共享,能够让容器读取到权重文件,也方便我们将容器内生成的测试数据导出到宿主机中。
- 该目录不能挂载到容器中的
${container_name}为容器名称,我们自定义即可,无特殊要求。{image_id}为容器启动时需要使用到的镜像id,即我们在2.3步骤获取的推理镜像id,可以通过docker images来查询获取。
2.5 进入容器
2.5.1 root进入容器
docker exec -u root -it ${container_name} /bin/bash
2.5.2 NPU卡挂载检查
启动推理服务之前检查卡是否被占用、端口是否被占用,是否有对应运行的进程。
npu-smi info
2.5.3 检查卡间通信
分别在两台机器容器内执行:
for i in $(seq 0 7);do hccn_tool -i $i -ip -g;done
而后测试一台机器能否访问访问到另一台机器的卡:
hccn_tool -i 0 -ping -g address 29.81.xx.xx
2.6 安装算子包
2.6.1 下载算子包
wget https://cmc-nkg-artifactory.cmc.tools.huawei.com/artifactory/hwcloud-artifact/cloudbuild2.0/10110504/20E000NV-2R1K-E680-0000-913VXAB0zhOu/xxxxx/AscendCloud-6.5.913-xxxx.zip
2.6.2 解压安装OPP
unzip -o -d /home/ma-user AscendCloud-6.5.913-xxx.zip
unzip -o -d /home/ma-user AscendCloud-OPP-6.5.913-xxxx.zip
mkdir AscendCloud-OPP-folder
unzip -o -d /home/ma-user/AscendCloud-OPP-folder AscendCloud-OPP-6.5.913-torch-2.1.0-py310-aarch64-xxxx.zip
# 执行命令:
pip install ascend_cloud_ops_custom-*.whl
pip install ascend_cloud_ops_atb-*.whl
pip install ascend_cloud_ops_cann-*.whl
bash ascend_cloud_ops_custom_opp-*.run
bash ascend_cloud_ops_ascend_turbo-*.run
2.6.3 解压安装LLM
mkdir AscendCloud-LLM-folder
unzip -o -d /home/ma-user/AscendCloud-LLM-folder AscendCloud-LLM-6.5.913-xxxx.zip
# 执行命令配置代理,注意不需要改任何内容:
git config --global http.version HTTP/1.1
git config --global http.sslVerify false
git config --global http.postBuffer 1244288000
export proxy=http://10.155.192.138:8080
export http_proxy=$proxy
export https_proxy=${http_proxy}
export no_proxy=127.0.0.1,localhost,mirrors.tools.huawei.com,mirrors.myhuaweicloud.com,7.223.xxx.xxx
pip config set global.index-url http://7.223.xxx.xxx/pypi/simple
pip config set install.trusted-host 7.223.xxx.xxx
pip config set global.timeout 120
2.6.4 LLM安装构建
# 进入到 llm_inference/ascend_vllm,并执行build.sh
cd llm_inference/ascend_vllm
sh build.sh
需要注意的是,在执行build.sh时,可能会出现下面报错:
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
error: 6392 bytes of body are still expected
fetch-pack: unexpected disconnect while reading sideband packet
fatal: early EOF
fatal: fetch-pack: invalid index-pack output
这种问题可能是网络波动,也可能是http.postBuffer不足。
针对该问题,我们再次执行:
git config --global http.postBuffer 1244288000
而后多重试几次sh build.sh,一般而言都可以成功。
2.7 创建模型启动脚本
这里,我们在容器内同一个目录下创建两个脚本:
- run_vllm.sh
- ray_worker_checker.py
2.7.1 run_vllm.sh
# 服务启动脚本:
source /home/ma-user/.bashrc
# 获取通信网卡名称
RAY_HEAD_IP=$2
VPC_PREFIX=$(echo "$RAY_HEAD_IP" | cut -d'.' -f1-2)
POD_INET_IP=$(hostname -I | tr ' ' '\n' | grep -o "^$VPC_PREFIX\.[0-9]\+\.[0-9]\+" | head -n 1)
POD_NETWORK_IFNAME=$(ifconfig | grep -B 1 "$POD_INET_IP" | head -n 1 | awk '{print $1}' | sed 's/://')
# 指定通信网卡
export GLOO_SOCKET_IFNAME=$POD_NETWORK_IFNAME
export TP_SOCKET_IFNAME=$POD_NETWORK_IFNAME
# 指定HCCL
export HCCL_SOCKET_IFNAME=$POD_NETWORK_IFNAME
export RAY_EXPERIMENTAL_NOSET_ASCEND_RT_VISIBLE_DEVICES=1
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
# 配置通信算法的编排展开位置在Device侧的AI Vector Core计算单元
export HCCL_OP_EXPANSION_MODE=AIV
# 指定可使用的卡
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
# 开启MTP需要注释掉MERGE_QKV环境变量
export MERGE_QKV=1
export FUSED_EXPERTS_V2=1
echo "start actual vllm serve............................"
if [ "$1" == "head" ]; then
ray start --head --num-gpus=8
#等待ray worker连接
CURDIR=$(cd "$(dirname "$0")";pwd)
python3 $CURDIR/ray_worker_checker.py
python -m vllm.entrypoints.openai.api_server --model ${container_model_path} \
--max-num-seqs=256 \
--max-model-len=8192 \
--max-num-batched-tokens=8192 \
--tensor-parallel-size=16 \
--block-size=128 \
--host=0.0.0.0 \
--port=8092 \
--gpu-memory-utilization=0.95 \
--num-scheduler-steps=8 \
--multi-step-stream-outputs=true \
--trust-remote-code \
--distributed-executor-backend=ray \
--enable-reasoning \
--reasoning-parser deepseek_r1 \
--served-model-name=DeepSeek-R1 \
--guided-decoding-backend lm-format-enforcer
fi
if [ "$1" == "worker" ]; then
mkdir -p ~/.cache/huggingface/
sudo cp -rf ${container_model_path}/modules/ ~/.cache/huggingface/modules/
command="ray start --address='$RAY_HEAD_IP:6379' --num-gpus=8 &> /dev/null"
echo $command
while true; do
eval $command
if [ $? -eq 0 ]; then
echo "succeed to connect to ray head node"
break
else
echo "failed to connect to ray head node, wait 5s....."
sleep 5
fi
done
echo "hold process..."
while :; do sleep 2073600; done
fi
在上述脚本中,需要注意的是:
${container_model_path}是容器中模型存放的具体地址,例如在上面我们启动容器时,我们指定了与宿主机映射的地址为/home/DeepSeek,那么此时实际地址就是/home/DeepSeek/DeepSeek-R1-w8a8-0210-50。max-model-len与max-num-batched-tokens分别表示推理时输入+输出tokens数量的最大值,以及prefill阶段,最多会使用多少token。- 在当前的客户需求下,我们需要测试的token为8k,那么数值就是8 * 1024 = 8192。
- 以此类推,如果时32k的场景,数值就是32 * 1024。
port,服务部署的端口,这里我们使用的时8092。served-model-name=DeepSeek-R1,指定当前使用的模型名称,这样后面在做接口与性能测试时,就不需要指定完整的模型路径地址了。enable-reasoning,是否支持Reasoning,对于DeepSeek-R1需要添加该配置。reasoning-parser deepseek_r1,是否支持Reasoning Content,对于DeepSeek-R1需要添加该配置。
2.7.2 ray_worker_checker.py
import ray
import time
def wait_ray_workers_ready():
target_count = 2 # 需用户手动根据节点个数修改。4机部署为4,2机部署为2。
ray.init(address='auto')
while True:
try:
nodes = ray.nodes()
if len(nodes) == target_count:
print("ray workers are ready.")
break
else:
print("ray workers not ready, check 10s later....")
time.sleep(10)
except Exception as e:
print(e)
if __name__ == "__main__":
wait_ray_workers_ready()
需要注意的时,上述所有步骤均需要在两台节点上完成,步骤内容一致。
2.8 启动服务
服务启动层面,我们需要先启动主节点:
sh run_vllm.sh head ${主节点ip}
等到主节点启动成功、显示等待worker连接时,再在worker节点上启动:
sh run_vllm.sh worker ${主节点ip}
此时我们只需要等待即可,具体启动进度可参考主节点终端页面的显示输出。
2.8.1 相关问题
在启动集群部署服务脚本时,经常会启动到一半报错:
ResourceWarning: Implicitly cleaning up
或者类似socket未关闭之类的问题。
究其原因,就是上一次启动的服务没有完全关闭,导致本次启动失败。
目前来看的解决方法,就是退出容器后,直接restart两个节点的容器,然后重新以root账户进入、执行run_vllm.sh脚本,一般而言就能解决问题、成功启动。
2.9 修改客户测试脚本
一般而言,我们在做性能测试时,使用的是LLM算子包中的bench_tools,不过这里,由于用户在H20上的测试使用的是个人脚本,为了拉齐结论,这里我们也同样需要使用该脚本。
脚本总体内容较多,这里我们重点看启动部分脚本:
2.9.1 bench_loop.sh
#!/bin/bash
set -ex
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
echo 'SCRIPT_DIR: ' $SCRIPT_DIR
# 定义 input 和 output 长度
INPUT_LEN=1000
OUTPUT_LEN=100
declare -a request_rates=(inf)
declare -a max_concurrency=(2)
declare -a num_prompts=(100)
DATA_JSON_PATH=$SCRIPT_DIR/ShareGPT_V3_unfiltered_cleaned_split.json
echo $DATA_JSON_PATH
if [ ! -f $DATA_JSON_PATH ]; then
wget https://vemlp-demo-models.tos-cn-beijing.volces.com/vemlp-third-party-warehouse/ShareGPT_V3_unfiltered_cleaned_split.json
mv ShareGPT_V3_unfiltered_cleaned_split.json $SCRIPT_DIR
fi
# export OPENAI_API_KEY="xxx"
i=0
RATE=${request_rates[$i]}
CONCURRENCY=${max_concurrency[$i]}
PROMPTS=${num_prompts[$i]}
echo "Running benchmark with request rate $RATE, concurrency $CONCURRENCY, prompts $PROMPTS"
python3 $SCRIPT_DIR/loop_test.py --backend vllm \
--dataset-name random \
--model DeepSeek-R1 \
--tokenizer $SCRIPT_DIR/tokenizer/ \
--dataset-path $DATA_JSON_PATH \
--random-input-len $INPUT_LEN \
--random-output-len $OUTPUT_LEN \
--random-range-ratio 1 \
--request-rate $RATE \
--max-concurrency $CONCURRENCY \
--num-prompts $PROMPTS \
--base-url http://主节点ip:端口号
上述脚本中,需要注意的是:
- INPUT_LEN=1000与OUTPUT_LEN=100,分别表示文本的出入长度与输出长度。
max_concurrency为最大并发,这里该参数并不生效,并发量是在loop_test.py脚本里控制的。export OPENAI_API_KEY是当我们基于MaaS模式测试时,需要填写的。model DeepSeek-R1为我们启动服务时指定的served-model-name。base-url为主节点的访问ip地址,以及启动服务时的端口号。
关于星火脚本,更详细的信息可参考:
2.10 发起性能测试
完成脚本内容的修改后,我们直接在目录中执行:
bash bench_loop.sh
脚本一共会从1并发执行到64并发,总时长大约一个半小时。
需要注意的是,这里我们要在容器内、使用bash来执行。
2.11 数据转换与分析
在测试完成后,会在目录中生成一个jsonl文件,此时我们可以使用Python脚本、将其转换为csv表格,脚本内容:
import json
import csv
filename = './测试数据/0314/test_2/vllm_0314_100_1000_100.jsonl'
with open(filename, 'r', encoding='utf-8') as file:
lines = file.readlines()
lines = [line.strip() for line in lines]
json_objects = [json.loads(line) for line in lines]
headers = set()
for obj in json_objects:
headers.update(obj.keys())
with open('100-1.csv', 'w', newline='', encoding='utf-8') as csvfile:
csvwriter = csv.DictWriter(csvfile, fieldnames=list(headers))
csvwriter.writeheader()
for obj in json_objects:
csvwriter.writerow(obj)
需要注意的是,由于测试数据是按天来命名,因此如果测试过程跨天(凌晨12点),会自动分割、生成两份测试数据。
昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链
更多推荐

所有评论(0)