摘要: 理论最终要服务于实践。本文将带领大家动手编写第一个完整的AscendCL程序。我们将从最基本的AscendCL初始化和去初始化入手,并实践Device内存的申请与释放,让你亲身感受AscendCL的开发流程。


  1. 项目创建与准备

在开始AscendCL编程之前,首先需要创建一个规范的项目目录结构。建议按照以下步骤操作:

(1) 创建项目目录:

mkdir -p ascend_hello_world/src ascend_hello_world/build
cd ascend_hello_world

这里创建了两个子目录:src用于存放源代码,build用于存放编译生成的文件。

(2) 创建源码文件:

touch src/main.c

在src目录下创建主程序文件main.c,这将包含我们的AscendCL示例代码。

(3) 创建编译配置文件:

touch CMakeLists.txt

创建CMake构建脚本,用于配置项目的编译选项和依赖关系。

  1. 代码实现

下面是一个完整的AscendCL基础程序实现,包含四个关键步骤:

#include "acl/acl.h"
#include <stdio.h>

#define MEM_SIZE (1024)

int main() {
    // 1. 初始化AscendCL运行时环境
    aclError ret = aclInit(NULL);
    if (ret != ACL_SUCCESS) {
        printf("aclInit failed, error code = %d\n", ret);
        return -1;
    }

    // 2. 申请Device内存
    void* devPtr = NULL;
    ret = aclrtMalloc(&devPtr, MEM_SIZE, ACL_MEM_MALLOC_NORMAL_ONLY);
    if (ret != ACL_SUCCESS) {
        printf("aclrtMalloc failed, error code = %d\n", ret);
        aclFinalize();
        return -1;
    }
    printf("Successfully allocated %d bytes on device\n", MEM_SIZE);

    // 3. 释放Device内存
    ret = aclrtFree(devPtr);
    if (ret != ACL_SUCCESS) {
        printf("aclrtFree failed, error code = %d\n", ret);
    }

    // 4. 去初始化AscendCL
    ret = aclFinalize();
    if (ret != ACL_SUCCESS) {
        printf("aclFinalize failed, error code = %d\n", ret);
        return -1;
    }

    return 0;
}

这个示例展示了AscendCL编程的基本流程,详细说明了如何使用AscendCL API进行设备内存管理:

  1. 环境初始化
    首先调用aclInit(const char *configPath)函数初始化运行环境,其中configPath参数可以指定配置文件路径(如nullptr表示使用默认配置)。这是所有AscendCL程序的必要第一步。

  2. 设备内存分配
    使用aclrtMalloc(void **devPtr, size_t size, aclrtMemMallocPolicy policy)在设备上分配内存:

    • devPtr:指向设备内存指针的指针
    • size:要分配的内存大小(示例中通过宏MEM_SIZE定义为1024字节)
    • policy:内存分配策略(如ACL_MEM_MALLOC_HUGE_FIRST
  3. 内存释放
    完成计算后,通过aclrtFree(void *devPtr)释放设备内存,传入之前分配的内存指针。

  4. 资源清理
    最后调用aclFinalize()清理所有分配的资源和运行环境。

  5. 错误处理
    每个API调用后都通过检查返回状态(如ACL_ERROR_NONE)确保操作成功执行,这是开发可靠应用的必备实践。例如:

    aclError ret = aclrtMalloc(&devPtr, MEM_SIZE, ACL_MEM_MALLOC_HUGE_FIRST);
    if (ret != ACL_ERROR_NONE) {
        // 错误处理逻辑
    }
    

该流程适用于各种AI计算场景,如图像处理、模型推理等需要设备内存管理的场合。合理的内存分配策略(如HUGE_FIRST)可以优化内存使用效率。

代码实现详解

基础程序框架

#include "acl/acl.h"  // AscendCL核心头文件
#include <stdio.h>    // 标准输入输出

#define MEM_SIZE (1024)  // 定义内存分配大小

int main() {
    // 1. 初始化AscendCL运行时环境
    aclError ret = aclInit(NULL);
    if (ret != ACL_SUCCESS) {
        printf("aclInit failed, error code = %d\n", ret);
        return -1;
    }

    // 2. 申请Device内存
    void* devPtr = NULL;
    ret = aclrtMalloc(&devPtr, MEM_SIZE, ACL_MEM_MALLOC_NORMAL_ONLY);
    if (ret != ACL_SUCCESS) {
        printf("aclrtMalloc failed, error code = %d\n", ret);
        aclFinalize();
        return -1;
    }
    printf("Successfully allocated %d bytes on device\n", MEM_SIZE);

    // 3. 释放Device内存
    ret = aclrtFree(devPtr);
    if (ret != ACL_SUCCESS) {
        printf("aclrtFree failed, error code = %d\n", ret);
    }

    // 4. 去初始化AscendCL
    ret = aclFinalize();
    if (ret != ACL_SUCCESS) {
        printf("aclFinalize failed, error code = %d\n", ret);
        return -1;
    }

    return 0;
}

关键API详解

增强版示例

#include <stdio.h>
#include "acl/acl.h"

#define MEMORY_SIZE (100)  // 定义内存大小常量

int main() {
    // 初始化
    const char *aclConfigPath = "";  // 空字符串表示默认配置
    aclError ret = aclInit(aclConfigPath);
    if (ret != ACL_SUCCESS) {
        printf("AscendCL init failed, error code = %d\n", ret);
        return -1;
    }
    printf("AscendCL init success!\n");

    // 1. 指定运算的Device
    ret = aclrtSetDevice(0);  // 使用第0号Device
    if (ret != ACL_SUCCESS) {
        printf("Set Device failed, error code = %d\n", ret);
        aclFinalize();
        return -1;
    }

    // 2. 申请Device内存
    void* devPtr = NULL;
    ret = aclrtMalloc(&devPtr, MEMORY_SIZE, ACL_MEM_MALLOC_NORMAL_ONLY);
    if (ret != ACL_SUCCESS) {
        printf("Malloc device memory failed, error code = %d\n", ret);
        aclrtResetDevice(0);
        aclFinalize();
        return -1;
    }
    printf("Malloc device memory success! Address: %p\n", devPtr);

    // ... 核心业务逻辑(如数据拷贝、模型执行)...
    printf("Now we can do AI computation here...\n");

    // 3. 释放Device内存
    ret = aclrtFree(devPtr);
    if (ret != ACL_SUCCESS) {
        printf("Free device memory failed, error code = %d\n", ret);
    } else {
        printf("Free device memory success!\n");
    }

    // 资源清理
    ret = aclrtResetDevice(0);  // 重置当前Device
    if (ret != ACL_SUCCESS) {
        printf("Reset Device failed, error code = %d\n", ret);
    }

    aclFinalize();  // 去初始化
    printf("Program exit.\n");
    return 0;
}

编译与运行指南

CMake配置详解

# 指定CMake最低版本要求
cmake_minimum_required(VERSION 3.10)

# 项目基本信息
project(HELLO_WORLD)

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

# 查找必需的依赖包
find_package(OpenMP REQUIRED)

# 设置头文件路径
include_directories(
    /usr/local/Ascend/ascend-toolkit/latest/include/  # AscendCL头文件路径
)

# 设置链接库路径
link_directories(
    /usr/local/Ascend/ascend-toolkit/latest/lib64/  # AscendCL库文件路径
)

# 添加可执行文件
add_executable(hello_world src/main.c)

# 链接必要的库
target_link_libraries(hello_world
    ascendcl    # AscendCL核心库
    pthread     # POSIX线程库
    dl          # 动态链接库
)

编译流程

运行程序

./hello_world

预期输出示例:

AscendCL init success!
Malloc device memory success! Address: 0xXXXXXX
Now we can do AI computation here...
Free device memory success!
Program exit.

开发最佳实践

错误处理规范

资源管理要点

常见问题

  1. // main.c
    #include <stdio.h>
    #include “acl/acl.h”
    
    #define MEMORY_SIZE (100)
    
    int main() {
        // 初始化
        const char *aclConfigPath = “”;
        aclError ret = aclInit(aclConfigPath);
        if (ret != ACL_SUCCESS) {
            printf(“AscendCL init failed, error code = %d\n”, ret);
            return -1;
        }
        printf(“AscendCL init success!\n”);
    
        // 1. 指定运算的Device
        ret = aclrtSetDevice(0); // 使用第0号Device
        if (ret != ACL_SUCCESS) {
            printf(“Set Device failed, error code = %d\n”, ret);
            aclFinalize();
            return -1;
        }
    
        // 2. 申请Device内存
        void* devPtr = NULL;
        ret = aclrtMalloc(&devPtr, MEMORY_SIZE, ACL_MEM_MALLOC_NORMAL_ONLY);
        if (ret != ACL_SUCCESS) {
            printf(“Malloc device memory failed, error code = %d\n”, ret);
            aclrtResetDevice(0);
            aclFinalize();
            return -1;
        }
        printf(“Malloc device memory success! Address: %p\n”, devPtr);
    
        // ... 这里本应是我们的核心业务逻辑(如数据拷贝、模型执行)...
        printf(“Now we can do AI computation here...\n”);
    
        // 3. 释放Device内存
        ret = aclrtFree(devPtr);
        if (ret != ACL_SUCCESS) {
            printf(“Free device memory failed, error code = %d\n”, ret);
        } else {
            printf(“Free device memory success!\n”);
        }
    
        // 资源清理
        ret = aclrtResetDevice(0); // 重置当前Device
        if (ret != ACL_SUCCESS) {
            printf(“Reset Device failed, error code = %d\n”, ret);
        }
    
        aclFinalize(); // 去初始化
        printf(“Program exit.\n”);
        return 0;
    }
    3. 编译与运行
    3.1 编写CMakeLists.txt
    # 指定CMake最低版本
    cmake_minimum_required(VERSION 3.10)
    # 指定项目名称
    project(HELLO_WORLD)
    
    # 设置C++标准
    set(CMAKE_CXX_STANDARD 11)
    
    # 查找必需的依赖包
    find_package(OpenMP REQUIRED)
    
    # 设置头文件路径
    include_directories(
        /usr/local/Ascend/ascend-toolkit/latest/include/
    )
    
    # 设置链接库路径
    link_directories(
        /usr/local/Ascend/ascend-toolkit/latest/lib64/
    )
    
    # 添加可执行文件
    add_executable(hello_world main.c)
    
    # 链接库
    target_link_libraries(hello_world
        ascendcl
        pthread
        dl
    )

    AscendCL 项目创建与开发指南

    项目创建与准备

    项目目录结构规划

    在开始AscendCL编程之前,建议采用以下标准化的项目目录结构:

    ascend_hello_world/
    ├── CMakeLists.txt        # 项目构建配置文件
    ├── build/               # 编译输出目录
    └── src/                 # 源代码目录
        └── main.c           # 主程序文件
    

    这种结构清晰地区分了源代码、构建文件和配置文件,符合现代软件开发的最佳实践。

    详细创建步骤

  2. 创建项目根目录

    mkdir -p ascend_hello_world/src ascend_hello_world/build
    cd ascend_hello_world
    

    这里使用-p参数确保父目录不存在时自动创建,避免错误。

  3. 创建源代码文件

    touch src/main.c
    

    这将创建一个空的C源文件,我们将在其中编写AscendCL程序。

  4. 创建构建配置文件

    touch CMakeLists.txt
    

    CMake是跨平台的构建系统,可以简化项目配置和编译过程。

  5. 环境初始化 (aclInit)

    • 参数configPath:可指定配置文件路径,NULL表示使用默认配置
    • 必须在所有AscendCL操作之前调用
    • 典型应用场景:AI模型推理前的环境准备
  6. 设备内存分配 (aclrtMalloc)

    • void **devPtr:双指针,用于返回分配的内存地址
    • size_t size:分配的内存大小(单位字节)
    • aclrtMemMallocPolicy policy:内存分配策略,常见选项:
      • ACL_MEM_MALLOC_NORMAL_ONLY:普通内存
      • ACL_MEM_MALLOC_HUGE_FIRST:优先分配大页内存
  7. 内存释放 (aclrtFree)

    • 必须与aclrtMalloc成对使用
    • 典型错误:内存泄漏(忘记释放)或重复释放
  8. 环境清理 (aclFinalize)

    • 释放所有AscendCL分配的资源
    • 应在程序退出前调用
  9. 创建构建目录

    mkdir build && cd build
    

  10. 生成构建系统

    cmake ..
    

    这会根据CMakeLists.txt生成Makefile等构建文件

  11. 编译项目

    make
    

    编译成功后会在build目录下生成可执行文件hello_world

  12. 检查所有API返回值

    aclError ret = aclrtMalloc(&devPtr, size, policy);
    if (ret != ACL_SUCCESS) {
        printf("Error %d in aclrtMalloc\n", ret);
        // 清理已分配的资源
        return -1;
    }
    

  13. 错误代码处理

    • 常见的错误代码:
      • ACL_ERROR_INVALID_PARAM:参数错误
      • ACL_ERROR_MEMORY_ALLOCATION:内存分配失败
      • ACL_ERROR_RT_FAILURE:运行时错误
  14. 资源申请与释放

    • 遵循"谁申请,谁释放"原则
    • 示例:
      void* devPtr = NULL;
      aclrtMalloc(&devPtr, size);  // 申请
      /* 使用内存 */
      aclrtFree(devPtr);           // 释放
      

  15. 生命周期管理

    • aclInitaclFinalize是程序的生命周期边界
    • 典型模式:
      aclInit();       // 程序开始
      /* 主逻辑 */
      aclFinalize();   // 程序结束
      

  16. 设备管理

    • 使用aclrtSetDevice设置当前设备
    • 退出前使用aclrtResetDevice重置设备状态
  17. 重复初始化

    // 错误示例
    for(int i=0; i<10; i++) {
        aclInit();  // 这将导致错误
    }
    

  18. 内存泄漏

    void* ptr1, ptr2;
    aclrtMalloc(&ptr1, size);  // 分配内存1
    aclrtMalloc(&ptr2, size);  // 分配内存2
    aclrtFree(ptr1);           // 只释放了内存1,内存2泄漏
    

  19. 错误处理不完整

    // 不推荐的写法
    aclrtMalloc(&ptr, size);  // 没有检查返回值
    

  20. mkdir build
    cd build
    cmake ..
    make

    编译成功后,会在build目录下生成可执行文件hello_world

3.3 运行程序 在终端执行编译生成的可执行文件:

./hello_world

程序运行输出示例:

AscendCL init success!      # 表示AscendCL初始化成功
Malloc device memory success! Address: 0xXXXXXX  # 成功申请设备内存,显示分配地址
Now we can do AI computation here...  # 表示可以在此处进行AI计算
Free device memory success!  # 成功释放设备内存
Program exit.               # 程序正常退出

  1. 代码解析与要点

错误处理:

  • 每一个AscendCL API调用后都应使用类似以下代码检查返回值:
aclError ret = aclrtMalloc(...);
if (ret != ACL_SUCCESS) {
    // 错误处理逻辑
    printf("Failed to malloc device memory, error code: %d\n", ret);
    return -1;
}

  • 这是写出健壮程序的基础,可以及时发现API调用异常

资源管理:

  • 遵循"谁申请,谁释放"的原则,确保资源不泄露
  • aclrtMalloc与aclrtFree必须成对出现,例如:
void* devPtr = nullptr;
aclrtMalloc(&devPtr, size);  // 申请
// ...使用内存...
aclrtFree(devPtr);           // 释放

生命周期管理:

  • aclInit和aclFinalize是程序的起点和终点
  • 一个进程内只需调用一次,通常:
    • aclInit在程序开始时调用
    • aclFinalize在程序退出前调用
  • 错误示例:在循环中重复调用aclInit会导致错误

结语
恭喜你!你已经成功运行了第一个昇腾AI程序。虽然它还没有进行任何实际的AI计算,但它已经完整地走遍了AscendCL应用的核心生命周期。在下一篇中,我们将在此基础上,加载一个真实的模型,完成一次图像分类推理。


2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖。
报名链接:https://www.hiascend.com/developer/activities/cann20252

Logo

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

更多推荐