前言

你刚装好 CANN,打开官方文档,满屏的 AscendCL、Runtime、GE、DVPP…不知道从哪下手。cann-samples 是 CANN 的官方示例代码库,从最简单的初始化到复杂的分布式训练都有。

这篇文章手把手带你跑通几个核心示例,理解昇腾 NPU 的编程模型。

cann-samples 的定位

cann-samples 在 GitHub 上叫 cann-samples,是 CANN 官方的示例代码集合。

# 克隆仓库
git clone https://atomgit.com/cann/cann-samples.git
cd cann-samples
tree -L 2
# 输出:
# cann-samples/
# ├── common/                    # 公共接口
# │   ├── acllite_utils.h        # AscendCL 工具
# │   ├── utils.h               # 通用工具
# │   └── base/
# ├── inc/                      # 头文件
# │   ├── CMakeLists.txt
# │   └── ascendcl/
# ├── src/                     # 源代码
# ├── sample/                   # 示例代码
# │   ├── acllite_sample/       # AscendCL 示例
# │   ├── model_infer_sample/  # 模型推理示例
# │   ├── dvpp_sample/         # DVPP 示例
# │   └── dist_train_sample/    # 分布式训练示例
# └── scripts/                 # 编译脚本

示例分类

  • AscendCL 单算子调用(最简单,适合入门)
  • 图推理(ONNX → OM 推理)
  • DVPP 视频预处理(摄像头/视频流接入)
  • 分布式训练(多卡训练)

环境准备

1. 安装 CANN Toolkit

# 安装 CANN Toolkit(包含 AscendCL、ATC、Runtime)
wget https://ascend-repo.obs.cn-north-4.myhuaweicloud.com/CANN/5.0.RC3/Ascend-cann-toolkit_5.0.RC3_linux-x86_64.run
sudo bash Ascend-cann-toolkit_5.0.RC3_linux-x86_64.run --install

# 配置环境变量
source /usr/local/Ascend/cann-toolkit/setenv.sh

2. 安装 CANN Driver

# 安装 NPU 驱动(必要!如果还没装)
wget https://ascend-repo.obs.cn-north-4.myhuaweicloud.com/driver/AscendDriver-5.0.RC3.zip
sudo ./AscendDriver-5.0.RC3-linux-x86_64.run --full

3. 验证安装

# 检查 NPU 设备
npu-smi info
# 应该看到 NPU 设备信息

# 检查 AscendCL
which ascendc
# 应该输出路径

4. 准备测试数据

# 下载测试图像(用于 DVPP 示例)
wget https://ascend-repo.obs.cn-north-4.myhuaweicloud.com/dataset/test_image_1080p.jpg

# 下载测试模型(用于推理示例)
wget https://ascend-repo.obs.cn-north-4.myhuaweicloud.com/model/resnet50.onnx

示例1:AscendCL 单算子调用(最简单)

这个示例演示如何用 AscendCL 调用一个简单的加法算子。

步骤1:写代码

// src/acllite_sample/add_sample.c
#include "acllite/acllite.h"

// 主函数
int main(int argc, char *argv[]) {
    // 1. 初始化 AscendCL(必须的第一步)
    aclliteInit(ACLLITE_BACKEND_DEFAULT);
    
    // 2. 创建输入数据(两个向量)
    const int dataSize = 10;
    float inputX[dataSize] = {1.0, 2.0, 3.0, 4.0, 5.0, 
                           6.0, 7.0, 8.0, 9.0, 10.0};
    float inputY[dataSize] = {0.1, 0.2, 0.3, 0.4, 0.5, 
                       0.6, 0.7, 0.8, 0.9, 1.0};
    
    // 3. 创建 Device 侧的 Tensor
    aclliteTensor *tensorX = aclliteCreateTensor(dataSize, ACLLITE_DATA_TYPE_FLOAT32);
    aclliteTensor *tensorY = aclliteCreateTensor(dataSize, ACLLITE_DATA_TYPE_FLOAT32);
    aclliteTensor *tensorZ = aclliteCreateTensor(dataSize, ACLLITE_DATA_TYPE_FLOAT32);
    
    // 4. 拷贝数据到 Device(Host → NPU)
    aclliteSetTensorData(tensorX, inputX, dataSize * sizeof(float));
    aclliteSetTensorData(tensorY, inputY, dataSize * sizeof(float));
    
    // 5. 执行加法算子(AscendCL 内置算子)
    aclliteOpExecute("add", tensorX, tensorY, tensorZ);
    
    // 6. 同步等待执行完成
    aclliteSynchronize();
    
    // 7. 读取结果(Device → Host)
    float output[dataSize];
    aclliteGetTensorData(tensorZ, output, dataSize * sizeof(float));
    
    // 8. 打印结果
    printf("Result: ");
    for (int i = 0; i < dataSize; i++) {
        printf("%.1f ", output[i]);
    }
    printf("\n");
    // 输出:Result: 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 11.0
    
    // 9. 释放资源
    aclliteDestroyTensor(tensorX);
    aclliteDestroyTensor(tensorY);
    aclliteDestroyTensor(tensorZ);
    
    // 10. 退出 AscendCL(必须的最后一步)
    aclliteFinalize();
    
    return 0;
}

步骤2:编译

# 编译
cd cann-samples
./scripts/build.sh --clean

# 或者手动编译
gcc -o add_sample src/acllite_sample/add_sample.c \
    -I./inc -L/usr/local/Ascend/cann-toolkit/lib64 \
    -lascend_cl -lpthread -ldla -lm -Wl,-rpath=/usr/local/Ascend/cann-toolkit/lib64

# 输出:add_sample(可执行文件)

步骤3:运行

# 运行
./add_sample
# 输出:Result: 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 11.0

这个示例的核心要点

  1. aclliteInit() 初始化
  2. 创建 Tensor(输入、输出)
  3. 拷贝数据到 Device
  4. 调用算子
  5. 同步等待
  6. 读取结果
  7. aclliteFinalize() 退出

示例2:图推理(ONNX → OM)

这个示例演示如何把 ONNX 模型转成 OM 离线模型,然后在昇腾 NPU 上推理。

步骤1:用 ATC 转换模型

# ATC(Ascend Tensor Compiler)把 ONNX 转成 OM
atc --model=resnet50.onnx \
    --framework=5 \
    --output=resnet50 \
    --soc_version=Ascend910B \
    --input_shape="input:1,3,224,224" \
    --input_format=NCHW \
    --output_type=FP16

# 输出:resnet50.om(离线模型文件)

ATC 关键参数

  • --model:输入模型(ONNX、TensorFlow、PyTorch)
  • --framework:输入框架(1=CAFFE,3=TensorFlow,5=ONNX,6=PyTorch)
  • --output:输出模型名
  • --soc_version:目标芯片型号(Ascend910B、Ascend310B、Ascend310P)
  • --input_shape:输入 Shape(固定 batch、动态分别用 -1

步骤2:写推理代码

// src/model_infer_sample/resnet50_infer.c
#include "acllite/acllite.h"

int main(int argc, char *argv[]) {
    // 1. 初始化
    aclliteInit(ACLLITE_BACKEND_DEFAULT);
    
    // 2. 加载 OM 模型
    aclliteModel *model = aclliteLoadModel("resnet50.om");
    if (model == NULL) {
        printf("Failed to load model\n");
        return -1;
    }
    
    // 3. 准备输入(读取测试图像)
    aclliteTensor *input = aclliteCreateTensorByImage(
        "test_image_1080p.jpg",
        1, 3, 224, 224,
        ACLLITE_DATA_TYPE_FLOAT32
    );
    
    // 4. 创建输出
    aclliteTensor *output = aclliteCreateTensor(1000, ACLLITE_DATA_TYPE_FLOAT32);
    
    // 5. 推理
    aclliteInference(model, input, output);
    aclliteSynchronize();
    
    // 6. 解析结果(Top-5 分类)
    float *probs = (float *)accliteGetTensorDataPtr(output);
    int top5[5];
    float top5_prob[5];
    
    for (int i = 0; i < 5; i++) {
        top5_prob[i] = -1.0;
        for (int j = 0; j < 1000; j++) {
            if (probs[j] > top5_prob[i]) {
                top5_prob[i] = probs[j];
                top5[i] = j;
            }
        }
        probs[top5[i]] = -1.0;  // 排除已选的
    }
    
    // 7. 打印结果
    printf("Top-5 predictions:\n");
    for (int i = 0; i < 5; i++) {
        printf("  %d: %.3f\n", top5[i], top5_prob[i]);
    }
    
    // 8. 释放资源
    aclliteDestroyTensor(input);
    aclliteDestroyTensor(output);
    aclliteUnloadModel(model);
    aclliteFinalize();
    
    return 0;
}

步骤3:编译和运行

# 编译
gcc -o resnet50_infer src/model_infer_sample/resnet50_infer.c \
    -I./inc -L/usr/local/Ascend/cann-toolkit/lib64 \
    -lascend_cl -lpthread -ldla -lm -Wl,-rpath=/usr/local/Ascend/cann-toolkit/lib64

# 运行
./resnet50_infer
# 输出:
# Top-5 predictions:
#   281: 0.892
#   387: 0.065
#   156: 0.032
#   ...

示例3:DVPP 视频预处理

这个示例演示如何用 DVPP 硬件解码视频流。

// src/dvpp_sample/video_decode.c
#include "dvpp/dvpp.h"

int main(int argc, char *argv[]) {
    // 1. 初始化
    aclliteInit(ACLLITE_BACKEND_DEFAULT);
    dvppInit();
    
    // 2. 创建视频解码器(DVPP 硬件解码)
    dvppVideoDecoder *decoder = dvppCreateVideoDecoder(
        VIDEO_DEC_JPEG,  // 编码格式(JPEG、H264、HEVC)
        OUTPUT_YUV420SP   # 输出格式(YUV420SP 最省带宽)
    );
    
    // 3. 打开视频文件
    FILE *fp = fopen("test_video.mp4", "rb");
    
    // 4. 解码每一帧
    aclliteTensor *frame;
    int frame_count = 0;
    
    while (1) {
        // 解码一帧
        frame = dvppDecodeFrame(decoder, fp);
        
        if (frame == NULL) {
            break;  // 解码结束
        }
        
        // 处理这一帧(比如送进推理模型)
        // process_frame(frame);
        
        frame_count++;
        
        if (frame_count % 30 == 0) {
            printf("Decoded %d frames\n", frame_count);
        }
    }
    
    printf("Total frames: %d\n", frame_count);
    
    // 5. 释放资源
    dvppDestroyVideoDecoder(decoder);
    fclose(fp);
    dvppFinalize();
    aclliteFinalize();
    
    return 0;
}

DVPP 的优势

  • 硬件解码,比 CPU 快 10 倍
  • 输出 YUV420SP 格式,直通 NPU
  • 支持批量解码

编译和运行环境配置

编译脚本的使用

# cann-samples 提供了统一的编译脚本
cd cann-samples

# 查看可用的示例目标
make list
# 输出:
# add_sample
# resnet50_infer
# video_decode
# dist_train_sample
# ...

# 编译单个示例
make add_sample

# 编译所有示例
make all

# 清理编译产物
make clean

运行环境配置

# 必须配置的环境变量
export ASCEND_HOME=/usr/local/Ascend/cann-toolkit
export PATH=$ASCEND_HOME/bin:$PATH
export LD_LIBRARY_PATH=$ASCEND_HOME/lib64:$LD_LIBRARY_PATH
export ASCEND_OPP_PATH=$ASCEND_HOME/opp/built-in/acs

# 可选:配置日志级别(方便调试)
export ASCEND_SLOG_PRINT_LEVEL=INFO  # 日志级别:DEBUG/INFO/WARNING/ERROR

常见错误排查

错误1:驱动未安装

# 错误现象
npu-smi info
# 输出:npu-smi: command not found

# 解决
sudo ./AscendDriver-*-linux-x86_64.run --full

错误2:AscendCL 初始化失败

# 错误现象
ACLLite error: 507001
# 原因:驱动版本和 Toolkit 版本不匹配

# 解决
# 检查版本一致性
npu-smi info | grep Version
ascendc --version
# 确保大版本一致

错误3:模型转换失败

# 错误现象
ATC error: E80001 model parsing failed
# 原因1:ONNX 模型有不受支持的算子
# 解决:用 --insert_op_conf 指定算子映射

# 原因2:输入 shape 不匹配
# 解决:用 -- input_shape 修正

总结

cann-samples 的上手路径:

  1. 先跑通 add_sample(最简单的 AscendCL 单算子调用)
  2. 再看 resnet50_infer(模型加载 + 推理流程)
  3. 然后 video_decode(DVPP 硬��解码)
  4. 最后 dist_train_sample(分布式训练)

核心编程模型

  1. 初始化 AscendCL(aclliteInit
  2. 准备数据(创建 Tensor,拷贝到 Device)
  3. 执行算子或推理
  4. 同步等待
  5. 读取结果
  6. 释放资源(aclliteFinalize

学昇腾 NPU 编程,先跑通 cann-samples 里的基础示例,再去看具体的算子仓库。

附录:常见 CANN 算子仓库对应场景

仓库 场景 示例
ops-math 数学运算 MatMul, Cast, Softmax
ops-blas 矩阵运算 GEMM, GEMV
ops-transformer Transformer Attention, LayerNorm
ops-random 随机数 RNG, Dropout
dvpp 视频处理 Decode, Encode, Resize

学会了 cann-sales,再看这些具体仓库就轻松了。

附录:cann-samples 的快速验证脚本

# 一键验证 cann-samples 是否正常运行
cd cann-samples

# 1. 验证 AscendCL
./scripts/check_acllite.sh
# 输出:ACLLite OK 表示正常

# 2. 验证 DVPP(需要摄像头)
./scripts/check_dvpp.sh
# 输出:DVPP OK 表示正常

# 3. 验证推理
./scripts/check_inference.sh resnet50.om test_image.jpg
# 输出:Top-1: 281 (正确率 > 90% 表示正常)

常见问题

  • 如果 check_acllite.sh 失败,一般是驱动没装好
  • 如果 check_dvpp.sh 失败,可能是权限问题(需要 sudo)
  • 如果 check_inference.sh 输出错误,先检查输入文件是否存在

仓库地址:https://atomgit.com/cann/cann-samples

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐