玩转 CANN:在 Notebook 中实战 Python 版 ResNet-50
文章目录
- 摘要
- 一、 引言:“玩转” CANN 的正确姿势
- 二、 环境配置与准备工作
- 步骤 1:配置并启动 Notebook 环境
- 步骤 2:打开终端
- 步骤 3:克隆 CANN 官方示例仓库
- 步骤 4:进入 Python 示例目录
- 步骤 5:下载官方 .om 模型
- 步骤 6:准备数据文件
- 三、 核心实操(在 Notebook 单元格中进行)
- 步骤 1:创建新的 Notebook
- 步骤 2:逐个单元格执行 Python 代码
- 单元格 1:导入必要的库
- 单元格 2:定义常量和路径
- 单元格 3:初始化 ACL (已修正)
- 单元格 4:加载模型
- 单元格 5:准备输入数据
- 单元格 6:准备输出数据
- 单元格 7:执行推理(核心)
- 单元格 8:获取并解析结果
- 单元格 9:清理所有资源
- 四、 总结与展望
摘要
随着 AI 技术的飞速发展,异构计算架构成为推动创新的核心动力。华为 CANN(Compute Architecture for Neural Networks)作为面向 AI 场景的统一架构,为开发者提供了强大的算力支持和高效的开发工具链。本文将紧扣“昇腾应用既玩”的主题,以“保姆级”指南的形式,带领读者在 Jupyter Notebook 环境中,利用 acl-python 库(AscendCL 的 Python 接口)实战部署一个经典的 ResNet-50 图像分类模型。我们将从环境准备、官方示例克隆开始,一步步详解 ACL 初始化的每一步含义,直至模型推理成功,直观感受 CANN 架构的便捷性与高性能。
一、 引言:“玩转” CANN 的正确姿势
CANN 是华为专为 AI 场景打造的异构计算架构。它像一个“超级翻译官”,“向上”能听懂 PyTorch、TensorFlow 等主流框架的指令,“向下”能精准指挥昇腾(Ascend)系列 NPU 高效工作。
对于开发者而言,“玩转” CANN 不仅仅是停留在框架层调用 model.to('npu')。更深入的“玩法”,是直接调用它的核心——AscendCL(昇腾计算语言)。AscendCL 就像是 CANN 开放给开发者的“底层 API”,它让我们能更精细地控制内存分配、模型加载和推理执行。
本次实操,我们将选择最受 AI 开发者喜爱的 Jupyter Notebook 环境,利用 CANN 官方 samples 仓库中提供的 Python 示例,来一场“看得见、摸得着”的 ResNet-50 推理实战。
二、 环境配置与准备工作
在开始实操之前,我们首先需要一个已经配置好 CANN 驱动和 NPU 硬件的 Notebook 环境。昇腾 AI 社区或 GitCode 平台通常会提供此类免费的“云端实验室”。
步骤 1:配置并启动 Notebook 环境
当你找到入口并点击“立即体验”或“新建 Notebook”时,你通常会看到一个资源确认界面(如下图所示)。

请务必按如下规则配置:
- Notebook 计算类型: 必须选择 NPU。
- (第一个下拉框): 选择任意 Ascend 处理器,例如
Ascend 910。 - 容器镜像: (最关键) 必须选择一个预装了 CANN 的镜像。
- 如下图所示,在下拉菜单中,我们推荐选择
euler2.9-py38-torch2.1.0-cann8.0-openmind0.6-notebook。
- 如下图所示,在下拉菜单中,我们推荐选择
- 存储大小: [限时免费] 50G 即可。
原因: 它包含了
cann8.0核心包和py38(Python 3.8) 环境,非常适合我们的acl-python实操。
配置完成后,点击“立即启动”。稍等片刻,系统将为你准备好一个 JupyterLab 实例。
(接下来的步骤,都是在这个启动好的 JupyterLab 环境中进行的)

步骤 2:打开终端
在 JupyterLab 界面的顶部菜单栏,点击 文件 -> 新建 -> 终端。这是我们执行 Linux 命令的主窗口。

步骤 3:克隆 CANN 官方示例仓库
CANN 官方在 GitCode 上维护了大量示例,这是我们学习的最佳起点。
在弹出的黑色终端窗口中,输入以下命令:
# 克隆仓库
git clone https://gitee.com/ascend/samples.git
步骤 4:进入 Python 示例目录
samples 仓库中包含了 C++ 和 Python 两套示例。由于我们在 Notebook 中操作,因此选择 Python 目录。
# 注意:这次我们进入的是 python 目录,而不是 cplusplus!
cd samples/python/level2_simple_inference/1_classification/resnet50_imagenet_classification
这个路径 level2_simple_inference 意味着它是一个中等难度的“简单推理”示例,非常适合入门。

步骤 5:下载官方 .om 模型
(注意:我们跳过了 pip install 步骤,因为我们选择的容器镜像已预装所有依赖。)
CANN 无法直接运行 .pth 或 .onnx 模型,它需要的是经过 ATC(Ascend Tensor Compiler) 工具转换后的 .om(Offline Model) 离线模型。官方示例很贴心地为我们准备好了转换后的模型。
# 使用 -P ~/model 选项,将模型下载到你“主目录”下的 model 文件夹中
wget -P ~/model https://obs-9be7.obs.cn-east-2.myhuaweicloud.com/003_Atc_Models/AE/ATC%20Model/resnet50/resnet50.om
值得注意的是: 这个
resnet50.om模型是内置了 AIPP(AI 预处理) 功能的。这意味着模型本身就能完成图片的缩放、色域转换等操作,我们后续在 Python 中无需再使用 OpenCV 或 PIL 进行繁琐的预处理,可以直接将.jpg文件喂给 NPU!
看最后一行,100% 并且 saved,文件已经保存到 /home/service/model/resnet50.om
步骤 6:准备数据文件
平台在主目录 (~) 下还提供了 dataset 文件夹。我们需要将示例中的 dog1.jpg 复制过去。
请继续在当前目录,执行以下命令:
# 使用绝对路径,从 samples 仓库复制图片到 dataset 目录
cp ~/samples/python/level2_simple_inference/data/dog1.jpg ~/dataset/
(执行后,你可以在左侧文件浏览器的 dataset 目录中看到 dog1.jpg)
三、 核心实操(在 Notebook 单元格中进行)
文件都准备好了!现在,我们回到 JupyterLab 的文件浏览器(左侧面板)。
步骤 1:创建新的 Notebook
-
在左侧文件浏览器中,导航到你刚刚进入的目录:
samples/python/level2_simple_inference/1_classification/resnet50_imagenet_classification

-
在顶部菜单栏,点击 文件 -> 新建 -> 笔记本。(当提示选择内核时,选择 Python 3)。

-
右键点击新创建的
Untitled.ipynb文件,将其重命名为my_test.ipynb。

步骤 2:逐个单元格执行 Python 代码
现在,双击打开 my_test.ipynb。你将看到一个可以输入代码的单元格。请按顺序复制以下代码块到不同的单元格中,然后点击“运行”按钮(或按 Shift + Enter)来逐个执行它们。
单元格 1:导入必要的库
import acl # 导入 acl-python 库
import numpy as np
import os # 导入 os 模块,用于处理路径
from PIL import Image # PIL 用于后续显示图片(可选)print("导入库成功!")

这是 Python 程序的起点。我们导入 acl(核心库)、numpy(用于数据转换)和 os(用于路径操作)。
单元格 2:定义常量和路径
# --- 1. 定义常量和路径 ---
import os # 再次导入 os 确保此单元格可独立运行# 获取主目录(即 File Browser 里的 '/')的绝对路径
# 例如:/home/service
HOME_DIR = os.path.expanduser('~') # 模型路径(拼接主目录和 model 文件夹)
MODEL_PATH = os.path.join(HOME_DIR, "model/resnet50.om")
# 图片路径(拼接主目录和 dataset 文件夹)
IMAGE_PATH = os.path.join(HOME_DIR, "dataset/dog1.jpg") # NPU 设备 ID
DEVICE_ID = 0print(f"主目录绝对路径: {HOME_DIR}")
print(f"模型路径: {MODEL_PATH}")
print(f"图片路径: {IMAGE_PATH}")

我们将所有配置项(如模型路径、图片路径、设备 ID)定义为常量,便于后续修改和维护。执行这个单元格,你应该能看到被正确解析的绝对路径被打印出来。
单元格 3:初始化 ACL (已修正)
# --- 2. 初始化 ACL ---
print("开始初始化 ACL...")# 1. ACL 初始化
# [修正] 直接调用,失败时将自动引发异常
acl.init()# 2. 分配 Device
acl.rt.set_device(DEVICE_ID)# 3. 创建 Context
context, ret = acl.rt.create_context(DEVICE_ID)
# [注] create_context 仍然返回元组(tuple),我们保留它,但不再检查 retprint("ACL 初始化成功!")

这是最重要的步骤之一。我们要开始与昇腾 NPU“对话”了。
单元格 4:加载模型
# --- 3. 加载模型 ---
print(f"开始加载模型: {MODEL_PATH}...")# [修正] load_from_file 返回 model_id, ret 元组
model_id, ret = acl.mdl.load_from_file(MODEL_PATH)# 获取模型描述(用于了解输入输出)
model_desc = acl.mdl.create_desc()
acl.mdl.get_desc(model_desc, model_id)print(f"加载模型成功。")

初始化完成后,我们把 .om 模型文件从硬盘加载到 NPU 的内存中。
单元格 5:准备输入数据
# --- 4. 准备输入数据 ---
print(f"开始准备输入图片: {IMAGE_PATH}...")# 1. 获取模型 AIPP 配置的输入 buffer 大小
input_size = acl.mdl.get_input_size_by_index(model_desc, 0)
print(f"模型 AIPP 输入 buffer 大小: {input_size} bytes")# 2. 读取 .jpg 图片文件到内存 (Host 侧)
# (我们使用修正后的 IMAGE_PATH)
with open(IMAGE_PATH, 'rb') as f:image_data = f.read()
image_size = len(image_data)# 3. 申请 NPU 上的内存 (Device 侧)
input_device, ret = acl.rt.malloc(input_size, acl.rt.ACL_MEM_MALLOC_HUGE_FIRST)# 4. 拷贝图片数据到 NPU 内存 (Host -> Device)
# 注意:我们拷贝的是原始 .jpg 数据流
ret = acl.rt.memcpy(input_device, input_size, image_data, image_size, acl.rt.ACL_MEMCPY_HOST_TO_DEVICE)
# [修正] 使用 acl.rt.ACL_SUCCESS
if ret != acl.rt.ACL_SUCCESS:print(f"memcpy H2D failed, ret={ret}")# 5. 创建 aclmdlDataset 结构
input_dataset = acl.mdl.create_dataset()
# [修正] 使用 acl.mdl.create_data_buffer
input_databuffer = acl.mdl.create_data_buffer(input_device, input_size)
acl.mdl.add_dataset_buffer(input_dataset, input_databuffer)print(f"成功将图片加载到 NPU 内存。")

这里我们能深刻体会到 AIPP 的便捷性。
单元格 6:准备输出数据
# --- 5. 准备输出数据 ---
print("开始准备输出 buffer...")output_dataset = acl.mdl.create_dataset()# 1. 获取模型输出大小 (ResNet-50 是 1000 个 float)
output_size = acl.mdl.get_output_size_by_index(model_desc, 0)
print(f"模型输出大小: {output_size} bytes") # 应该会打印 4000# 2. 申请 NPU 上的内存 (Device 侧)
output_device, ret = acl.rt.malloc(output_size, acl.rt.ACL_MEM_MALLOC_HUGE_FIRST)# 3. 创建 aclmdlDataset 结构
# [修正] 使用 acl.mdl.create_data_buffer
output_databuffer = acl.mdl.create_data_buffer(output_device, output_size)
acl.mdl.add_dataset_buffer(output_dataset, output_databuffer)print("准备输出 buffer 成功。")

与输入类似,我们还需要在 Device 侧为 NPU 准备一块“空白”内存,用于存放推理的输出结果。
单元格 7:执行推理(核心)
# --- 6. 执行推理 ---
print("开始执行推理...")ret = acl.mdl.execute(model_id, input_dataset, output_dataset)
# [修正] 使用 acl.mdl.ACL_SUCCESS
if ret != acl.mdl.ACL_SUCCESS:print(f"execute model failed, ret={ret}")print("推理执行成功!")

这是最激动人心的一步!万事俱备,只欠“执行”。
单元格 8:获取并解析结果
# --- 7. 获取并处理结果 ---
print("开始从 NPU 回传并解析结果...")# 1. 申请 Host 内存
output_host, ret = acl.rt.malloc_host(output_size)# 2. 拷贝数据回 Host 内存 (Device -> Host)
ret = acl.rt.memcpy(output_host, output_size, output_device, output_size, acl.rt.ACL_MEMCPY_DEVICE_TO_HOST)
# [修正] 使用 acl.rt.ACL_SUCCESS
if ret != acl.rt.ACL_SUCCESS:print(f"memcpy D2H failed, ret={ret}")# 3. 将 Host 内存 (void*) 转换为 numpy 数组
# (1000 个 float, 每个 float 4 字节)
data = acl.util.ptr_to_numpy(output_host, (output_size // 4,), np.float32)# 4. 找到 Top-5 概率的索引
top_k = 5
top_k_indices = data.argsort()[-top_k:][::-1] # 排序并反转print("\n======== 推理结果 Top-5 ========")
# (官方示例的 data 目录里有 imagenet_1000.json 标签文件,
# 我们这里为了简单,只打印索引号。在实际应用中,
# 我们会用索引号去查表,得到 "Pekinese" 这样的标签)
for i, index in enumerate(top_k_indices):print(f"Top {i+1}: 类别索引 [ {index} ] 概率: {data[index]:.5f}")
print("===============================\n")

推理已完成,但结果(1000 个概率值)还静静地躺在 Device 侧的内存中。我们需要把它“拿回” Host 侧,才能在 Python 中读取它。
执行这个单元格,看到和 C++ 示例完全一致的 Top-5 结果!dog1.jpg 被高概率识别为索引 162(京巴犬)。
单元格 9:清理所有资源
# --- 8. 清理资源 ---
print("开始清理所有 ACL 资源...")# 释放内存 (Host 和 Device)
acl.rt.free(input_device)
acl.rt.free(output_device)
acl.rt.free_host(output_host)# 销毁 dataset 和 databuffer
acl.mdl.destroy_dataset(input_dataset)
acl.mdl.destroy_dataset(output_dataset)
# [修正] 使用 acl.mdl.destroy_data_buffer
acl.mdl.destroy_data_buffer(input_databuffer)
acl.mdl.destroy_data_buffer(output_databuffer)# 卸载模型
acl.mdl.unload(model_id)
acl.mdl.destroy_desc(model_desc)# 销毁 Context
acl.rt.destroy_context(context)# 重置 Device
acl.rt.reset_device(DEVICE_ID)# ACL 去初始化
acl.finalize()print("清理完毕。")

“有借有还,再借不难”。在 CANN 编程中,资源清理至关重要。
四、 总结与展望
通过以上 9 个单元格的操作,我们在 Jupyter Notebook 环境中,使用 acl-python 库完成了一次完整且规范的端到端 AI 推理。
回顾整个过程,我们深刻体验了“昇腾应用既玩”的精髓:
- “玩”得明白:我们不再是简单调用框架 API,而是深入到了 AscendCL 层。我们清晰地看到了一个 AI 推理任务的完整生命周期:初始化 -> 加载模型 -> 申请内存 (H/D) -> 拷贝数据 (H2D) -> 执行推理 -> 拷贝结果 (D2H) -> 解析数据 -> 释放资源。
- “玩”得高效:我们体验了 CANN 的 AIPP 核心特性,它极大地简化了数据预处理流程,让开发者能更专注于推理逻辑本身。
- “玩”得便捷:
acl-python库与 Notebook 的结合,为 AI 开发者提供了最熟悉的“配方”。我们可以逐个单元格验证想法,快速调试,这大大降低了学习底层 API 的门槛。
CANN 架构的潜力远不止于此。从这里出发,你还可以继续探索更多高级“玩法”,例如:模型的多线程推理、动态 Batch/Shape 处理、使用 acl.media 接口进行视频解码等。希望这份教程能真正带你入门,激发你更多“玩转”昇腾的灵感!


