从 0 到 1:10 分钟跑通第一个 Ascend ACL 推理程序
【昇腾NPU零基础ACL推理开发指南】 本文提供从环境搭建到模型推理的完整流程,帮助开发者在昇腾NPU上快速实现首个ACL推理程序。主要内容包括: 环境配置 确认NPU硬件驱动安装 安装CANN Toolkit和NNRt运行时 关键环境变量配置(LD_LIBRARY_PATH等) 模型转换 ONNX转OM格式使用ATC工具 常见转换失败原因分析(版本匹配/输入shape) C++开发实践 ACL推
第一次在昇腾 NPU 上跑推理,很多人卡在第一步:环境装好了,ATC 模型转换也成功了,一跑推理程序就报 aclInit failed 或者 load model failed。
我当年第一次跑 ACL 推理,环境装了 3 遍,模型转了 5 遍,推理程序编译通过但运行就 core dump。最后发现是环境变量没配全——LD_LIBRARY_PATH 少了 /usr/local/Ascend/nnrt/latest/acllib/lib64,导致运行时找不到 libascendcl.so。
这篇文章把我踩过的坑全部列出来,你照着做,10 分钟内必能跑通。
第一步:CANN 环境安装
1.1 确认硬件和环境
先确认你有昇腾 NPU(910/910B/310 都行),且系统是 Ubuntu 18.04/20.04 或 CentOS 7.x。
# 看有没有 NPU 设备
ls /dev/davinci*
# 有输出(比如 /dev/davinci0)说明驱动装好了
没输出?先装驱动。去昇腾社区下载对应版本的驱动([https://www.hiascend.com/hardware/firmware-drivers]),按文档装完重启。
1.2 安装 CANN Toolkit 和 NNRt
CANN 有两个包:Toolkit(开发用,含编译工具链)和 NNRt(运行时,含 ACL 库)。
# 下载 CANN 8.0.RC1 版本(示例,具体版本看你 NPU 驱动版本)
# 去 https://www.hiascend.com/software/cann/community-history 下载
# 安装 Toolkit(开发机装)
./Ascend-cann-toolkit_8.0.RC1_linux-x86_64.run --full
# 安装 NNRt(运行机装,如果开发运行同一台机器,两个都装)
./Ascend-cann-nnrt_8.0.RC1_linux-x86_64.run --full
90% 新手都会踩的坑 No.1:装完不配环境变量。 装完 CANN 一定要配环境变量,否则编译时找不到头文件,运行时找不到库。
第二步:环境变量配置
这是最容易出问题的地方。很多人以为 /etc/profile 里配一次就完事了,其实每次开新终端都要 source。
创建环境变量配置文件 ~/.cannrc:
# ~/.cannrc
export ASCEND_HOME=/usr/local/Ascend
export CANN_HOME=$ASCEND_HOME/nnrt/latest
export PATH=$ASCEND_HOME/toolkit/latest/bin:$PATH
export LD_LIBRARY_PATH=$CANN_HOME/acllib/lib64:$ASCEND_HOME/driver/lib64:$LD_LIBRARY_PATH
export ASCEND_OPP_PATH=$ASCEND_HOME/nnrt/latest/opp
每次开新终端都要 source:
source ~/.cannrc
或者写进 ~/.bashrc(一劳永逸):
echo "source ~/.cannrc" >> ~/.bashrc
验证环境变量是否配好:
# 看能不能找到 ATC 工具
which atc
# 输出应该是 /usr/local/Ascend/toolkit/latest/bin/atc
# 看能不能找到 ACL 库
ldconfig -p | grep ascendcl
# 输出应该有一行 libascendcl.so
90% 新手都会踩的坑 No.2:
LD_LIBRARY_PATH配了但没生效。 用ldconfig -p | grep ascendcl验证。如果没输出,说明库路径没配进去。检查~/.cannrc里的路径是否真实存在(比如nnrt/latest是不是软链接,有时候装完叫nnrt/8.0.RC1)。
第三步:模型转换(ATC)
ACL 推理需要 OM 格式的模型(离线模型)。你手里的 ONNX/PyTorch/TensorFlow 模型需要先转成 OM。
3.1 准备 ONNX 模型
如果手头没有 ONNX 模型,用 PyTorch 导出一个 ResNet-50:
# export_resnet50.py
import torch
import torchvision
model = torchvision.models.resnet50(pretrained=False)
model.eval()
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy_input, "resnet50.onnx",
input_names=["input"], output_names=["output"])
3.2 用 ATC 转 OM
atc --model=resnet50.onnx \
--framework=5 \
--output=resnet50 \
--input_format=NCHW \
--input_shape="input:1,3,224,224" \
--log=info
参数解释(解释 WHY 而非 WHAT):
--framework=5:ONNX 的格式编号是 5(1=Caffe, 3=TensorFlow, 5=ONNX, 6=PyTorch)--input_format=NCHW:NPU 要求输入是 NCHW 格式(跟 PyTorch 一致)--output=resnet50:输出的 OM 模型叫resnet50.om(自动加.om后缀)
转换成功会看到 ATC run success,生成 resnet50.om 文件。
90% 新手都会踩的坑 No.3:模型转换成功,但推理时报
load model failed。 原因:ATC 转换时用的 CANN 版本,跟推理程序编译时链接的 CANN 版本不一致。解决:保证转换和运行用同一个 CANN 版本(比如都是 8.0.RC1)。
第四步:写 ACL 推理代码(C++)
这是核心。ACL 推理分 5 步:初始化 → 加载模型 → 准备输入 → 执行推理 → 解析输出。
4.1 目录结构
acl_inference/
├── CMakeLists.txt # 编译配置
├── main.cpp # 主程序
├── resnet50.om # 模型文件(ATC 转换生成)
└── test_image.bin # 输入数据(二进制文件)
4.2 完整 C++ 代码
// main.cpp
#include <acl/acl.h>
#include <acl/ops/acl_dvpp.h>
#include <iostream>
#include <fstream>
#include <vector>
// 检查 ACL 返回值的宏(不写 try-catch,直接判错)
#define CHECK_RET(ret, msg) \
if ((ret) != ACL_SUCCESS) { \
std::cerr << msg << ", ret = " << ret << std::endl; \
return -1; \
}
int main() {
// === 第 1 步:初始化 ACL ===
aclError ret = aclInit(nullptr);
CHECK_RET(ret, "aclInit failed");
// 指定要用的 NPU 设备(0 号设备)
ret = aclrtSetDevice(0);
CHECK_RET(ret, "aclrtSetDevice failed");
// === 第 2 步:加载 OM 模型 ===
uint32_t model_id = 0;
ret = aclmdlLoadFromFile("resnet50.om", &model_id);
CHECK_RET(ret, "aclmdlLoadFromFile failed");
// 获取模型描述信息(输入/输出的 shape、数据类型)
aclmdlDesc *model_desc = aclmdlCreateDesc(model_id);
ret = aclmdlGetDesc(model_desc, model_id);
CHECK_RET(ret, "aclmdlGetDesc failed");
// === 第 3 步:准备输入数据 ===
// 读二进制输入文件(假设已经把图片预处理成了 1×3×224×224 的 float32 数组)
std::ifstream infile("test_image.bin", std::ios::binary);
std::vector<float> input_data(1 * 3 * 224 * 224);
infile.read(reinterpret_cast<char*>(input_data.data()),
input_data.size() * sizeof(float));
infile.close();
// 申请 NPU 显存(输入数据要从 Host 拷到 NPU)
size_t input_size = aclmdlGetInputSizeByIndex(model_desc, 0);
void *input_dev = nullptr;
ret = aclrtMalloc(&input_dev, input_size, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret, "aclrtMalloc failed");
// 把 Host 数据拷到 NPU
ret = aclrtMemcpy(input_dev, input_size,
input_data.data(), input_size,
ACL_MEMCPY_HOST_TO_DEVICE);
CHECK_RET(ret, "aclrtMemcpy failed");
// 创建输入 dataset(ACL 要求用 dataset 封装输入输出)
aclmdlDataset *input_dataset = aclmdlCreateDataset();
aclDataBuffer *input_buffer = aclCreateDataBuffer(input_dev, input_size);
ret = aclmdlAddDatasetBuffer(input_dataset, input_buffer);
CHECK_RET(ret, "aclmdlAddDatasetBuffer failed");
// === 第 4 步:执行推理 ===
// 创建输出 dataset
aclmdlDataset *output_dataset = aclmdlCreateDataset();
for (size_t i = 0; i < aclmdlGetNumOutputs(model_desc); i++) {
size_t output_size = aclmdlGetOutputSizeByIndex(model_desc, i);
void *output_dev = nullptr;
ret = aclrtMalloc(&output_dev, output_size, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret, "aclrtMalloc output failed");
aclDataBuffer *output_buffer = aclCreateDataBuffer(output_dev, output_size);
ret = aclmdlAddDatasetBuffer(output_dataset, output_buffer);
CHECK_RET(ret, "aclmdlAddDatasetBuffer output failed");
}
// 执行模型推理
ret = aclmdlExecute(model_id, input_dataset, output_dataset);
CHECK_RET(ret, "aclmdlExecute failed");
// === 第 5 步:解析输出 ===
// 把输出从 NPU 拷回 Host
for (size_t i = 0; i < aclmdlGetNumOutputs(model_desc); i++) {
aclDataBuffer *output_buffer = aclmdlGetDatasetBuffer(output_dataset, i);
void *output_dev = aclGetDataBufferAddr(output_buffer);
size_t output_size = aclGetDataBufferSize(output_buffer);
std::vector<float> output_data(output_size / sizeof(float));
ret = aclrtMemcpy(output_data.data(), output_size,
output_dev, output_size,
ACL_MEMCPY_DEVICE_TO_HOST);
CHECK_RET(ret, "aclrtMemcpy output failed");
// 打印前 10 个输出值(真实场景要接后处理,比如 argmax 取分类结果)
std::cout << "Output " << i << " (first 10 values): ";
for (int j = 0; j < 10 && j < output_data.size(); j++) {
std::cout << output_data[j] << " ";
}
std::cout << std::endl;
}
// === 清理资源 ===
ret = aclmdlUnload(model_id);
CHECK_RET(ret, "aclmdlUnload failed");
ret = aclrtResetDevice(0);
CHECK_RET(ret, "aclrtResetDevice failed");
ret = aclFinalize();
CHECK_RET(ret, "aclFinalize failed");
std::cout << "Inference success!" << std::endl;
return 0;
}
4.3 代码关键解释
为什么用 aclmdlDataset 封装输入输出? ACL 的接口设计是"面向 Dataset"的——一个模型可能有多个输入(比如 GPT 的 input_ids 和 attention_mask),用 Dataset 封装可以一次性传多个输入。
为什么输入数据要从 Host 拷到 NPU? NPU 只能直接访问自己的显存(HBM)。Host 内存的数据必须显式拷贝(用 aclrtMemcpy)。
为什么 aclrtMalloc 要用 ACL_MEM_MALLOC_HUGE_FIRST? NPU 的 HBM 支持大页内存(Huge Page),用这个标志申请内存会优先用大页,性能好 10-15%。
第五步:编译
写 CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(acl_inference)
set(CMAKE_CXX_STANDARD 14)
# 找 CANN 包(装在 /usr/local/Ascend)
find_package(Ascend REQUIRED)
# 包含 ACL 头文件路径
include_directories(${ASCEND_INCLUDE_DIRS})
# 编可执行文件
add_executable(acl_inference main.cpp)
# 链 ACL 库
target_link_libraries(acl_inference ${ASCEND_LIBRARIES})
编译:
mkdir build && cd build
cmake ..
make -j
90% 新手都会踩的坑 No.4:编译通过,但运行时报
error while loading shared libraries: libascendcl.so。 原因:LD_LIBRARY_PATH没配全。运行时的库路径要在~/.cannrc里配好(见第二步)。
第六步:运行
# 确保环境变量已 source
source ~/.cannrc
# 把 resnet50.om 拷到运行目录
cp ../resnet50.om .
# 运行(需要有 NPU 权限,加 sudo 或把用户加入 HwAiUser 组)
./acl_inference
成功输出:
Output 0 (first 10 values): 0.0023 -0.0156 0.0089 ...
Inference success!
为什么模型能转换成功但运行失败?
这是新手问的最多的问题。我总结了 4 个原因:
原因 1:CANN 版本不匹配
ATC 转换时用的 CANN 版本,跟推理程序编译/运行时用的 CANN 版本不一致。OM 模型格式可能变了,导致 aclmdlLoadFromFile 失败。
排查:
# 看 ATC 版本
atc --version
# 看推理程序链接的 ACL 库版本
ldd acl_inference | grep ascendcl
解决:统一版本,重新转换模型、重新编译程序。
原因 2:NPU 驱动版本跟 CANN 不匹配
CANN 8.0 要求驱动版本 >= 24.1.0。如果驱动太老,ACL 初始化就失败(aclInit failed)。
排查:
# 看驱动版本
npu-smi info
解决:升级驱动到 CANN 要求的版本。
原因 3:输入 Shape 跟模型要求的不一致
ATC 转换时指定了 input_shape="input:1,3,224,224",但推理时输入数据的 shape 不对(比如你传了 1,3,256,256 的数据),导致 aclmdlExecute 失败。
排查:打印输入数据的尺寸,跟 ATC 转换时指定的 shape 对比。
解决:推理前把输入数据 resize/crop 到模型要求的 shape。
原因 4:权限问题
运行推理程序需要访问 /dev/davinci0 设备文件,普通用户没权限。
排查:
ls -l /dev/davinci0
# 如果 owner 是 root,你需要 sudo 或加入 HwAiUser 组
解决:
sudo usermod -aG HwAiUser $USER
# 注销重新登录,就有权限了
90% 新手都会踩的坑(完整版)
| 坑编号 | 问题描述 | 原因 | 解决方法 |
|---|---|---|---|
| 1 | 装完 CANN 找不到头文件/库 | 环境变量没配 | 写 ~/.cannrc,每次开终端 source |
| 2 | 编译通过,运行时 libascendcl.so 找不到 |
LD_LIBRARY_PATH 没配全 |
`ldconfig -p |
| 3 | 模型转换成功,推理时 load model failed |
CANN 版本不匹配 | 统一转换和运行用的 CANN 版本 |
| 4 | aclInit failed |
驱动版本太老 | 升级驱动到 CANN 要求的版本 |
| 5 | 推理输出全是 0 或 NaN | 输入数据没归一化 | 图片预处理要跟训练时一致(比如 ImageNet 的 mean/std) |
排错方法总结
遇到问题,按这个顺序排查:
- 看返回值:所有 ACL 接口都返回
aclError,用CHECK_RET宏检查 - 看环境变量:
echo $LD_LIBRARY_PATH确认库路径 - 看设备状态:
npu-smi info确认 NPU 在线 - 看模型信息:
atc --mode=display_model_info --om=resnet50.om确认模型输入/输出 shape - 简化复现:先跑 CANN 自带的样例(比如
/usr/local/Ascend/nnrt/latest/samples/inference/modelInference/)
工程经验:第一次跑不通别慌。ACL 的错误码很详细(比如
ACL_ERROR_INVALID_RESOURCE = 107002),去昇腾社区搜错误码,90% 的问题都有现成答案。
https://atomgit.com/cann/runtime https://atomgit.com/cann/asc-devkit https://atomgit.com/cann/cann-samples
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐



所有评论(0)