CANN应用开发完整流程

CANN应用开发完整流程

引言

CANN(Compute Architecture for Neural Networks)是一套完整的AI计算架构,为开发者提供了从模型转换、算子开发到应用部署的全栈解决方案。本文将从零开始,详细介绍CANN应用开发的完整流程,帮助开发者快速上手。

CANN应用开发全景图

复制代码
┌───────────────────────────────────────────────────────────────────┐
│                    CANN应用开发完整流程                            │
├───────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐│
│  │   模型开发阶段   │ -> │   模型转换阶段   │ -> │   应用开发阶段   ││
│  │                 │    │                 │    │                 ││
│  │ • 框架选择       │    │ • ATC转换       │    │ • AscendCL API  ││
│  │ • 模型训练       │    │ • OM模型生成    │    │ • 数据预处理    ││
│  │ • 模型导出       │    │ • 模型优化      │    │ • 推理执行      ││
│  └─────────────────┘    └─────────────────┘    └─────────────────┘│
│                                   │                               │
│                                   ▼                               │
│                          ┌─────────────────┐                      │
│                          │   编译部署阶段   │                      │
│                          │                 │                      │
│                          │ • 交叉编译       │                      │
│                          │ • 运行时配置     │                      │
│                          │ • 性能调优       │                      │
│                          └─────────────────┘                      │
│                                                                   │
└───────────────────────────────────────────────────────────────────┘

第一阶段:模型准备

1.1 支持的模型框架

CANN支持主流深度学习框架的模型转换:

框架 版本要求 导出格式 转换工具
TensorFlow 2.x PB/frozen_graph ATC
PyTorch 1.8+ ONNX/PTH ATC
Caffe - caffemodel/prototxt ATC
MindSpore - MindIR 内置支持
ONNX - onnx ATC

1.2 模型导出示例

PyTorch模型导出
python 复制代码
import torch
import torch.onnx
from torchvision.models import resnet50, ResNet50_Weights

# 1. 加载预训练模型
model = resnet50(weights=ResNet50_Weights.DEFAULT)
model.eval()

# 2. 创建示例输入
dummy_input = torch.randn(1, 3, 224, 224)

# 3. 导出为ONNX格式
torch.onnx.export(
    model,                          # 模型
    dummy_input,                    # 示例输入
    "resnet50.onnx",                # 输出文件名
    export_params=True,             # 导出参数
    opset_version=11,               # ONNX算子集版本
    do_constant_folding=True,       # 常量折叠优化
    input_names=['input'],          # 输入节点名
    output_names=['output']         # 输出节点名
)

print("ONNX model exported successfully!")
TensorFlow模型导出
python 复制代码
import tensorflow as tf
from tensorflow.keras.applications import ResNet50

# 1. 加载模型
model = ResNet50(weights='imagenet')

# 2. 保存为SavedModel格式
tf.saved_model.save(model, "resnet50_savedmodel")
print("SavedModel exported successfully!")

# 3. 或者冻结为PB格式
tf.compat.v1.reset_default_graph()
with tf.compat.v1.Session() as sess:
    # 加载模型定义
    with tf.compat.v1.gfile.GFile("resnet50.pb", "rb") as f:
        graph_def = tf.compat.v1.GraphDef()
        graph_def.ParseFromString(f.read())
        tf.import_graph_def(graph_def, name="")

    # 冻结图
    output_graph_def = tf.compat.v1.graph_util.convert_variables_to_constants(
        sess,
        tf.compat.v1.get_default_graph().as_graph_def(),
        ["output"]
    )

    with tf.compat.v1.gfile.GFile("resnet50_frozen.pb", "wb") as f:
        f.write(output_graph_def.SerializeToString())

print("Frozen PB model exported successfully!")

第二阶段:模型转换

2.1 ATC工具介绍

ATC(Ascend Tensor Compiler)是CANN提供的模型转换工具,用于将第三方框架模型转换为CANN支持的OM(Offline Model)格式。

2.2 基础转换命令

bash 复制代码
# ATC基础命令格式
atc --framework=<框架类型> \
    --model=<模型文件> \
    --output=<输出OM文件名> \
    --soc_version=<处理器型号> \
    [其他选项]

2.3 转换示例

ONNX模型转换
bash 复制代码
#!/bin/bash

# 设置环境变量
export ASCEND_HOME=/usr/local/Ascend
export PATH=${ASCEND_HOME}/ascend-toolkit/latest/compiler/ccec_compiler/bin:${PATH}
export LD_LIBRARY_PATH=${ASCEND_HOME}/ascend-toolkit/latest/lib64:${LD_LIBRARY_PATH}

# ONNX转OM
atc \
    --framework=ONNX \
    --model=resnet50.onnx \
    --output=resnet50 \
    --soc_version=Ascend910A \
    --input_format=NCHW \
    --input_shape="input:1,3,224,224" \
    --out_nodes="output:0" \
    --log=error \
    --enable_small_channel=1

echo "ONNX to OM conversion completed!"
TensorFlow模型转换
bash 复制代码
#!/bin/bash

# TensorFlow PB转OM
atc \
    --framework=3 \
    --model=resnet50_frozen.pb \
    --output=resnet50_tf \
    --soc_version=Ascend910A \
    --input_shape="input:1,224,224,3" \
    --out_nodes="output:0" \
    --log=error \
    --enable_small_channel=1

echo "TensorFlow to OM conversion completed!"
Caffe模型转换
bash 复制代码
#!/bin/bash

# Caffe模型转OM
atc \
    --framework=0 \
    --model=resnet50.prototxt \
    --weight=resnet50.caffemodel \
    --output=resnet50_caffe \
    --soc_version=Ascend910A \
    --input_format=NCHW \
    --input_shape="data:1,3,224,224" \
    --out_nodes="prob:0" \
    --log=error

echo "Caffe to OM conversion completed!"

2.4 高级转换选项

bash 复制代码
atc \
    --framework=ONNX \
    --model=model.onnx \
    --output=model \
    --soc_version=Ascend910A \
    # 输入配置
    --input_format=NCHW \
    --input_shape="input:1,3,640,640;mask:1,1,80,80" \
    # 输出配置
    --out_nodes="output:0;cls:0" \
    # 精度配置
    --precision_mode=allow_fp32_to_fp16 \
    # 优化选项
    --enable_small_channel=1 \
    --op_select_implmode=high_performance \
    --optypelist_for_implmode="Gelu" \
    # 其他选项
    --log=info \
    --dump_model=./dump_model

第三阶段:应用开发

3.1 项目结构

复制代码
cann_application/
├── build/
│   └── CMakeLists.txt
├── src/
│   ├── main.cpp
│   ├── model_loader.cpp
│   ├── data_preprocess.cpp
│   └── postprocess.cpp
├── include/
│   ├── model_loader.h
│   ├── data_preprocess.h
│   └── postprocess.h
├── models/
│   └── resnet50.om
├── data/
│   └── test_images/
├── CMakeLists.txt
└── README.md

3.2 完整代码实现

model_loader.h
cpp 复制代码
#pragma once

#include "acl/acl.h"
#include "acl/acl_mdl.h"
#include <string>
#include <memory>
#include <vector>

class ModelLoader {
public:
    ModelLoader();
    ~ModelLoader();

    // 加载模型
    aclError Load(const char* modelPath);

    // 创建输入数据集
    aclError CreateInput(void* data, size_t size);

    // 创建输出数据集
    aclError CreateOutput();

    // 执行推理
    aclError Execute();

    // 获取输出数据
    aclError GetOutputData(std::vector<void*>& outputs,
                           std::vector<size_t>& sizes);

    // 获取模型信息
    size_t GetInputSize() const { return inputSize_; }
    size_t GetOutputNum() const { return outputNum_; }

    // 卸载模型
    void Unload();

private:
    uint32_t modelId_;
    aclmdlDesc* modelDesc_;
    aclmdlDataset* inputDataset_;
    aclmdlDataset* outputDataset_;
    size_t inputSize_;
    size_t outputNum_;
    bool loaded_;
};
model_loader.cpp
cpp 复制代码
#include "model_loader.h"
#include <iostream>

ModelLoader::ModelLoader()
    : modelId_(0),
      modelDesc_(nullptr),
      inputDataset_(nullptr),
      outputDataset_(nullptr),
      inputSize_(0),
      outputNum_(0),
      loaded_(false) {
}

ModelLoader::~ModelLoader() {
    Unload();
}

aclError ModelLoader::Load(const char* modelPath) {
    // 1. 加载模型文件
    aclError ret = aclmdlLoadFromFile(modelPath, &modelId_);
    if (ret != ACL_ERROR_NONE) {
        std::cerr << "aclmdlLoadFromFile failed, ret=" << ret << std::endl;
        return ret;
    }

    // 2. 创建模型描述
    modelDesc_ = aclmdlCreateDesc();
    if (modelDesc_ == nullptr) {
        std::cerr << "aclmdlCreateDesc failed" << std::endl;
        return ACL_ERROR_FAILURE;
    }

    // 3. 获取模型描述信息
    ret = aclmdlGetDesc(modelDesc_, modelId_);
    if (ret != ACL_ERROR_NONE) {
        std::cerr << "aclmdlGetDesc failed, ret=" << ret << std::endl;
        return ret;
    }

    // 4. 获取输入输出信息
    inputSize_ = aclmdlGetInputSizeByIndex(modelDesc_, 0);
    outputNum_ = aclmdlGetDatasetNumOutputs(modelDesc_);

    std::cout << "Model loaded: " << modelPath << std::endl;
    std::cout << "Model ID: " << modelId_ << std::endl;
    std::cout << "Input size: " << inputSize_ << std::endl;
    std::cout << "Output num: " << outputNum_ << std::endl;

    loaded_ = true;
    return ACL_ERROR_NONE;
}

aclError ModelLoader::CreateInput(void* data, size_t size) {
    // 创建输入数据集
    inputDataset_ = aclmdlCreateDataset();
    if (inputDataset_ == nullptr) {
        std::cerr << "aclmdlCreateDataset for input failed" << std::endl;
        return ACL_ERROR_FAILURE;
    }

    // 创建数据Buffer
    aclDataBuffer* dataBuffer = aclCreateDataBuffer(data, size);
    if (dataBuffer == nullptr) {
        std::cerr << "aclCreateDataBuffer failed" << std::endl;
        return ACL_ERROR_FAILURE;
    }

    // 添加到数据集
    aclError ret = aclmdlAddDatasetBuffer(inputDataset_, dataBuffer);
    if (ret != ACL_ERROR_NONE) {
        std::cerr << "aclmdlAddDatasetBuffer failed, ret=" << ret << std::endl;
        return ret;
    }

    return ACL_ERROR_NONE;
}

aclError ModelLoader::CreateOutput() {
    // 创建输出数据集
    outputDataset_ = aclmdlCreateDataset();
    if (outputDataset_ == nullptr) {
        std::cerr << "aclmdlCreateDataset for output failed" << std::endl;
        return ACL_ERROR_FAILURE;
    }

    // 为每个输出分配内存
    for (size_t i = 0; i < outputNum_; i++) {
        size_t outputSize = aclmdlGetOutputSizeByIndex(modelDesc_, i);

        void* outputBuffer = nullptr;
        aclError ret = aclrtMalloc(&outputBuffer, outputSize,
                                   ACL_MEM_MALLOC_HUGE_FIRST);
        if (ret != ACL_ERROR_NONE) {
            std::cerr << "aclrtMalloc failed, ret=" << ret << std::endl;
            return ret;
        }

        aclDataBuffer* dataBuffer = aclCreateDataBuffer(outputBuffer, outputSize);
        if (dataBuffer == nullptr) {
            std::cerr << "aclCreateDataBuffer failed" << std::endl;
            return ACL_ERROR_FAILURE;
        }

        ret = aclmdlAddDatasetBuffer(outputDataset_, dataBuffer);
        if (ret != ACL_ERROR_NONE) {
            std::cerr << "aclmdlAddDatasetBuffer failed, ret=" << ret << std::endl;
            return ret;
        }
    }

    return ACL_ERROR_NONE;
}

aclError ModelLoader::Execute() {
    aclError ret = aclmdlExecute(modelId_, inputDataset_, outputDataset_);
    if (ret != ACL_ERROR_NONE) {
        std::cerr << "aclmdlExecute failed, ret=" << ret << std::endl;
        return ret;
    }

    return ACL_ERROR_NONE;
}

aclError ModelLoader::GetOutputData(std::vector<void*>& outputs,
                                     std::vector<size_t>& sizes) {
    outputs.clear();
    sizes.clear();

    for (size_t i = 0; i < outputNum_; i++) {
        aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(outputDataset_, i);
        if (dataBuffer == nullptr) {
            return ACL_ERROR_FAILURE;
        }

        void* data = aclGetDataBufferAddr(dataBuffer);
        size_t size = aclGetDataBufferSizeV2(dataBuffer);

        outputs.push_back(data);
        sizes.push_back(size);
    }

    return ACL_ERROR_NONE;
}

void ModelLoader::Unload() {
    if (outputDataset_ != nullptr) {
        // 释放输出内存
        for (size_t i = 0; i < outputNum_; i++) {
            aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(outputDataset_, i);
            if (dataBuffer != nullptr) {
                void* data = aclGetDataBufferAddr(dataBuffer);
                if (data != nullptr) {
                    aclrtFree(data);
                }
            }
        }
        aclmdlDestroyDataset(outputDataset_);
        outputDataset_ = nullptr;
    }

    if (inputDataset_ != nullptr) {
        aclmdlDestroyDataset(inputDataset_);
        inputDataset_ = nullptr;
    }

    if (modelDesc_ != nullptr) {
        aclmdlDestroyDesc(modelDesc_);
        modelDesc_ = nullptr;
    }

    if (modelId_ != 0) {
        aclmdlUnload(modelId_);
        modelId_ = 0;
    }

    loaded_ = false;
}
main.cpp
cpp 复制代码
#include "model_loader.h"
#include "data_preprocess.h"
#include "postprocess.h"
#include <iostream>
#include <fstream>
#include <vector>

class ResourceManager {
private:
    int32_t deviceId_;
    aclrtContext context_;
    aclrtStream stream_;

public:
    aclError Init(int32_t deviceId = 0) {
        deviceId_ = deviceId;

        // 设置设备
        aclError ret = aclrtSetDevice(deviceId_);
        if (ret != ACL_ERROR_NONE) {
            std::cerr << "aclrtSetDevice failed, ret=" << ret << std::endl;
            return ret;
        }

        // 创建Context
        ret = aclrtCreateContext(&context_, deviceId_);
        if (ret != ACL_ERROR_NONE) {
            std::cerr << "aclrtCreateContext failed, ret=" << ret << std::endl;
            return ret;
        }

        // 创建Stream
        ret = aclrtCreateStream(&stream_);
        if (ret != ACL_ERROR_NONE) {
            std::cerr << "aclrtCreateStream failed, ret=" << ret << std::endl;
            return ret;
        }

        std::cout << "Resource initialized!" << std::endl;
        return ACL_ERROR_NONE;
    }

    aclrtStream GetStream() const { return stream_; }

    void Destroy() {
        if (stream_ != nullptr) {
            aclrtDestroyStream(stream_);
        }
        if (context_ != nullptr) {
            aclrtDestroyContext(context_);
        }
        aclrtResetDevice(deviceId_);
    }
};

int main(int argc, char* argv[]) {
    if (argc < 3) {
        std::cout << "Usage: " << argv[0] << " <model_path> <image_path>" << std::endl;
        return -1;
    }

    const char* modelPath = argv[1];
    const char* imagePath = argv[2];

    // 1. 初始化ACL
    aclError ret = aclInit(nullptr);
    if (ret != ACL_ERROR_NONE) {
        std::cerr << "aclInit failed, ret=" << ret << std::endl;
        return -1;
    }

    // 2. 初始化资源
    ResourceManager resourceManager;
    ret = resourceManager.Init();
    if (ret != ACL_ERROR_NONE) {
        aclFinalize();
        return -1;
    }

    // 3. 加载模型
    ModelLoader modelLoader;
    ret = modelLoader.Load(modelPath);
    if (ret != ACL_ERROR_NONE) {
        resourceManager.Destroy();
        aclFinalize();
        return -1;
    }

    // 4. 创建输出
    ret = modelLoader.CreateOutput();
    if (ret != ACL_ERROR_NONE) {
        modelLoader.Unload();
        resourceManager.Destroy();
        aclFinalize();
        return -1;
    }

    // 5. 图像预处理
    std::vector<float> inputData;
    ret = PreprocessImage(imagePath, inputData, modelLoader.GetInputSize());
    if (ret != ACL_ERROR_NONE) {
        modelLoader.Unload();
        resourceManager.Destroy();
        aclFinalize();
        return -1;
    }

    // 6. 分配Device内存并拷贝数据
    void* deviceInput = nullptr;
    ret = aclrtMalloc(&deviceInput, inputData.size() * sizeof(float),
                       ACL_MEM_MALLOC_HUGE_FIRST);
    if (ret != ACL_ERROR_NONE) {
        modelLoader.Unload();
        resourceManager.Destroy();
        aclFinalize();
        return -1;
    }

    ret = aclrtMemcpy(deviceInput, inputData.size() * sizeof(float),
                      inputData.data(), inputData.size() * sizeof(float),
                      ACL_MEMCPY_HOST_TO_DEVICE);
    if (ret != ACL_ERROR_NONE) {
        aclrtFree(deviceInput);
        modelLoader.Unload();
        resourceManager.Destroy();
        aclFinalize();
        return -1;
    }

    // 7. 创建输入并推理
    ret = modelLoader.CreateInput(deviceInput, inputData.size() * sizeof(float));
    if (ret != ACL_ERROR_NONE) {
        aclrtFree(deviceInput);
        modelLoader.Unload();
        resourceManager.Destroy();
        aclFinalize();
        return -1;
    }

    ret = modelLoader.Execute();
    if (ret != ACL_ERROR_NONE) {
        aclrtFree(deviceInput);
        modelLoader.Unload();
        resourceManager.Destroy();
        aclFinalize();
        return -1;
    }

    // 8. 获取输出并后处理
    std::vector<void*> outputs;
    std::vector<size_t> outputSizes;
    ret = modelLoader.GetOutputData(outputs, outputSizes);
    if (ret != ACL_ERROR_NONE) {
        aclrtFree(deviceInput);
        modelLoader.Unload();
        resourceManager.Destroy();
        aclFinalize();
        return -1;
    }

    // 拷贝输出到Host
    std::vector<float> outputData(outputSizes[0] / sizeof(float));
    ret = aclrtMemcpy(outputData.data(), outputSizes[0],
                      outputs[0], outputSizes[0],
                      ACL_MEMCPY_DEVICE_TO_HOST);
    if (ret == ACL_ERROR_NONE) {
        // 后处理
        Postprocess(outputData);
    }

    // 9. 清理资源
    aclrtFree(deviceInput);
    modelLoader.Unload();
    resourceManager.Destroy();
    aclFinalize();

    std::cout << "\nInference completed!" << std::endl;
    return 0;
}

第四阶段:编译部署

4.1 CMakeLists.txt

cmake 复制代码
cmake_minimum_required(VERSION 3.10)
project(CANN_Application)

# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 设置CANN路径
set(ASCEND_HOME "/usr/local/Ascend")
set(ASCEND_TOOLKIT_ROOT "${ASCEND_HOME}/ascend-toolkit/latest")

# 包含目录
include_directories(
    ${ASCEND_TOOLKIT_ROOT}/include
    ${ASCEND_TOOLKIT_ROOT}/compiler/include
    ${CMAKE_SOURCE_DIR}/include
)

# 链接目录
link_directories(
    ${ASCEND_TOOLKIT_ROOT}/lib64
)

# 源文件
add_executable(cann_app
    src/main.cpp
    src/model_loader.cpp
    src/data_preprocess.cpp
    src/postprocess.cpp
)

# 链接库
target_link_libraries(cann_app
    acl
    acl_cblas
    ge_runner
    runtime
    pthread
    dl
)

# 安装规则
install(TARGETS cann_app DESTINATION bin)
install(FILES models/resnet50.om DESTINATION models)

4.2 编译脚本

bash 复制代码
#!/bin/bash

# 编译脚本
BUILD_DIR="build"
mkdir -p ${BUILD_DIR}
cd ${BUILD_DIR}

cmake ..
make -j$(nproc)

if [ $? -eq 0 ]; then
    echo "Build success!"
    echo "Executable: ${BUILD_DIR}/cann_app"
else
    echo "Build failed!"
    exit 1
fi

4.3 运行脚本

bash 复制代码
#!/bin/bash

# 设置环境变量
export ASCEND_HOME=/usr/local/Ascend
export LD_LIBRARY_PATH=${ASCEND_HOME}/ascend-toolkit/latest/lib64:${LD_LIBRARY_PATH}
export PATH=${ASCEND_HOME}/ascend-toolkit/latest/compiler/ccec_compiler/bin:${PATH}

# 运行程序
./build/cann_app models/resnet50.om data/test_images/test.jpg

性能优化建议

1. 内存复用

cpp 复制代码
// 复用输入输出内存,减少分配开销
class MemoryPool {
    std::vector<void*> buffers_;
public:
    void* Allocate(size_t size) {
        for (auto* buf : buffers_) {
            // 检查是否可以复用
        }
        void* buf = nullptr;
        aclrtMalloc(&buf, size, ACL_MEM_MALLOC_HUGE_FIRST);
        buffers_.push_back(buf);
        return buf;
    }
};

2. 异步执行

cpp 复制代码
// 使用Stream实现异步推理
aclError AsyncInference() {
    // 异步拷贝数据
    aclrtMemcpyAsync(...);

    // 异步执行推理
    aclmdlExecuteAsync(...);

    // 在其他Stream处理下一帧
    // ...
}

3. 批处理

cpp 复制代码
// 批量处理提高吞吐量
aclError BatchInference(const std::vector<void*>& inputs) {
    // 将多个输入打包成batch
    // 一次推理处理多个样本
}

常见问题排查

问题 可能原因 解决方案
模型加载失败 路径错误/权限问题 检查文件路径和权限
推理结果异常 输入预处理错误 验证输入数据格式
内存分配失败 内存不足 减少batch size
性能不佳 未使用DVPP 使用硬件加速预处理

总结

本文详细介绍了CANN应用开发的完整流程:

  1. 模型准备:从各框架导出模型
  2. 模型转换:使用ATC工具转换为OM格式
  3. 应用开发:完整的推理代码实现
  4. 编译部署:CMake构建和运行脚本
  5. 性能优化:内存复用、异步执行等技巧

通过这套完整流程,开发者可以快速将深度学习模型部署到CANN平台上,实现高效的AI推理。

参考资料

相关推荐
加密狗复制模拟2 天前
破解加密狗时间限制介绍
安全·软件工程·个人开发
Next_Tech_AI3 天前
别用 JS 惯坏了鸿蒙
开发语言·前端·javascript·个人开发·ai编程·harmonyos
PM老周4 天前
2026年常用瀑布管理工具有哪些?
阿里云·云计算·团队开发·产品经理·个人开发
我的golang之路果然有问题4 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
东东5165 天前
果园预售系统的设计与实现spingboot+vue
前端·javascript·vue.js·spring boot·个人开发
芯岭技术郦6 天前
XL2411蓝牙透传模组简要说明
个人开发·射频工程
东东5167 天前
学院个人信息管理系统 (springboot+vue)
vue.js·spring boot·后端·个人开发·毕设
东东5167 天前
xxx食堂移动预约点餐系统 (springboot+微信小程序)
spring boot·微信小程序·小程序·毕业设计·个人开发·毕设
晚风_END9 天前
postgresql数据库|连接池中间件pgbouncer的部署和配置详解
数据库·后端·spring·postgresql·中间件·个人开发