Ascend C 算子的 4 种调用方式:Kernel/CL/PyTorch 全覆盖
开发好的 Ascend C 算子,最终需要被上层业务代码或框架调用 —— 有的场景需要在 C++ 中直接调用,有的场景需要在 Python 中通过 PyTorch 调用,还有的场景需要通过 Ascend CL(计算库)调用。不同的调用方式对应不同的业务需求,掌握这些方式能让你的算子适配更多场景。Kernel 直调Ascend CL 调用PyTorch 调用MindSpore 调用,并结合实际案例说
前言
开发好的 Ascend C 算子,最终需要被上层业务代码或框架调用 —— 有的场景需要在 C++ 中直接调用,有的场景需要在 Python 中通过 PyTorch 调用,还有的场景需要通过 Ascend CL(计算库)调用。不同的调用方式对应不同的业务需求,掌握这些方式能让你的算子适配更多场景。
本文将详细解析 Ascend C 算子的 4 种核心调用方式:Kernel 直调、Ascend CL 调用、PyTorch 调用、MindSpore 调用,并结合实际案例说明每种方式的实现步骤与适用场景,帮助大家打通算子开发与应用的 “最后一公里”。
一、算子调用的核心逻辑
不管采用哪种调用方式,算子调用的核心逻辑都是一致的:
- 准备输入数据:将业务数据转换为 Tensor 格式,拷贝到 Device 侧内存;
- 调用算子接口:通过对应方式的 API 调用算子,触发 Device 侧的计算;
- 获取输出结果:将 Device 侧的计算结果拷贝回 Host 侧,转换为业务数据格式。
不同调用方式的差异,主要在于 “接口形式” 和 “数据流转方式”—— 例如,Kernel 直调需要手动管理 Device 侧内存,而 PyTorch 调用则由框架自动管理内存。
二、方式 1:Kernel 直调(Host 侧 C++ 直接调用)
Kernel 直调是最底层的调用方式,直接在 Host 侧 C++ 代码中调用 Device 侧的 Kernel 函数,无需经过框架封装。
2.1 适用场景
- Kernel 逻辑的快速验证;
- 小型 C++ 项目中的算子调用;
- 需要精细控制内存与执行流程的场景。
2.2 实现步骤(以 Add 算子为例)
步骤 1:编写 Kernel 函数
c++
__global__ void AddKernel(
const half* d_x1,
const half* d_x2,
half* d_y,
int64_t size
) {
int block_id = blockIdx.x;
int block_size = 256;
int start = block_id * block_size;
int end = min(start + block_size, size);
__local half local_x1[256];
__local half local_x2[256];
__local half local_y[256];
memcpy(local_x1, d_x1 + start, (end - start) * sizeof(half));
memcpy(local_x2, d_x2 + start, (end - start) * sizeof(half));
vadd(local_y, local_x1, local_x2, end - start);
memcpy(d_y + start, local_y, (end - start) * sizeof(half));
}
步骤 2:Host 侧调用 Kernel
c++
#include <iostream>
#include <vector>
#include "ascend_c_runtime.h"
int main() {
// 1. 初始化Ascend C运行时
AscendCRuntime rt;
rt.Init();
// 2. 准备Host侧数据
int64_t size = 1024;
std::vector<half> h_x1(size, 1.0f);
std::vector<half> h_x2(size, 2.0f);
std::vector<half> h_y(size, 0.0f);
// 3. 分配Device侧内存
half* d_x1 = rt.Malloc<half>(size * sizeof(half));
half* d_x2 = rt.Malloc<half>(size * sizeof(half));
half* d_y = rt.Malloc<half>(size * sizeof(half));
// 4. Host -> Device数据拷贝
rt.Memcpy(d_x1, h_x1.data(), size * sizeof(half), HOST_TO_DEVICE);
rt.Memcpy(d_x2, h_x2.data(), size * sizeof(half), HOST_TO_DEVICE);
// 5. 配置线程块与线程格
dim3 grid_dim((size + 256 - 1) / 256, 1, 1);
dim3 block_dim(1, 1, 1);
// 6. 直接调用Kernel
AddKernel<<<grid_dim, block_dim>>>(d_x1, d_x2, d_y, size);
// 7. Device -> Host结果拷贝
rt.Memcpy(h_y.data(), d_y, size * sizeof(half), DEVICE_TO_HOST);
// 8. 验证结果
bool success = true;
for (int i = 0; i < size; ++i) {
if (h_y[i] != 3.0f) {
success = false;
break;
}
}
std::cout << "Kernel直调结果:" << (success ? "成功" : "失败") << std::endl;
// 9. 释放资源
rt.Free(d_x1);
rt.Free(d_x2);
rt.Free(d_y);
rt.Destroy();
return 0;
}
2.3 优缺点
优点:
- 控制粒度细:可手动管理内存、线程配置,灵活性高;
- 调试方便:直接在 C++ 代码中打印日志,定位问题快。
缺点:
- 开发成本高:需手动处理内存分配、数据拷贝;
- 复用性差:无法被 Python 代码或上层框架调用。
三、方式 2:Ascend CL 调用(C++ 计算库调用)
Ascend CL(Ascend Computing Library)是昇腾提供的 C 语言计算库,支持调用注册好的 Ascend C 算子,是 C++ 项目中集成算子的推荐方式。
3.1 适用场景
- 中大型 C++ 项目中的算子集成;
- 需要与昇腾其他计算库(如 DVPP、MediaProcess)配合使用的场景;
- 追求性能与易用性平衡的场景。
3.2 实现步骤(以 Add 算子为例)
步骤 1:注册算子并编译为动态库
c++
// 注册Add算子
REGISTER_OP(Add)
.INPUT(x1, TensorType::FLOAT16)
.INPUT(x2, TensorType::FLOAT16)
.OUTPUT(y, TensorType::FLOAT16)
.REQUIRE(x1.shape() == x2.shape(), "Shape mismatch")
.SET_OP_CONSTRUCT_FUNC(AddOp::Construct)
.SET_TILING_FUNC(AddTiling::ComputeTiling)
.SET_KERNEL_FUNC(AddKernel);
编译命令:
bash
运行
msopgen build --op-name Add --source add_op.cpp --output ./output --target ascend310b
步骤 2:通过 Ascend CL 调用算子
c++
#include <iostream>
#include <vector>
#include "acl/acl.h"
int main() {
// 1. 初始化Ascend CL
aclInit(nullptr);
aclSetDevice(0);
// 2. 加载算子动态库
aclopLoadFromFile("./output/add_op.so");
// 3. 准备Host侧数据
int64_t size = 1024;
std::vector<half> h_x1(size, 1.0f);
std::vector<half> h_x2(size, 2.0f);
std::vector<half> h_y(size, 0.0f);
// 4. 创建Device侧Tensor
aclTensorDesc* desc_x1 = aclCreateTensorDesc(ACL_FLOAT16, 1, &size, ACL_FORMAT_ND);
aclDataBuffer* buffer_x1 = aclCreateDataBuffer(h_x1.data(), size * sizeof(half));
aclTensor* tensor_x1 = aclCreateTensor(desc_x1, buffer_x1);
aclTensorDesc* desc_x2 = aclCreateTensorDesc(ACL_FLOAT16, 1, &size, ACL_FORMAT_ND);
aclDataBuffer* buffer_x2 = aclCreateDataBuffer(h_x2.data(), size * sizeof(half));
aclTensor* tensor_x2 = aclCreateTensor(desc_x2, buffer_x2);
aclTensorDesc* desc_y = aclCreateTensorDesc(ACL_FLOAT16, 1, &size, ACL_FORMAT_ND);
aclDataBuffer* buffer_y = aclCreateDataBuffer(h_y.data(), size * sizeof(half));
aclTensor* tensor_y = aclCreateTensor(desc_y, buffer_y);
// 5. 调用算子
aclopExecute("Add", 2, &tensor_x1, 1, &tensor_y, nullptr);
// 6. 验证结果
bool success = true;
for (int i = 0; i < size; ++i) {
if (h_y[i] != 3.0f) {
success = false;
break;
}
}
std::cout << "Ascend CL调用结果:" << (success ? "成功" : "失败") << std::endl;
// 7. 释放资源
aclDestroyTensor(tensor_x1);
aclDestroyTensor(tensor_x2);
aclDestroyTensor(tensor_y);
aclUnloadOp("Add");
aclResetDevice(0);
aclFinalize();
return 0;
}
3.3 优缺点
优点:
- 易用性好:无需手动管理 Device 侧内存,由 Ascend CL 自动处理;
- 兼容性强:支持与昇腾其他库配合使用,适合复杂项目;
- 性能稳定:经过昇腾官方优化,调用效率高。
缺点:
- 学习成本:需要熟悉 Ascend CL 的 API 体系;
- 灵活性略低:内存与执行流程由库管理,自定义空间较小。
四、方式 3:PyTorch 调用(Python 框架集成)
Ascend C 算子可以通过 PyTorch 的自定义算子接口集成到 Python 项目中,是 AI 模型开发中最常用的调用方式。
4.1 适用场景
- 基于 PyTorch 的 AI 模型开发;
- Python 业务代码中的算子调用;
- 需要与其他 PyTorch 算子配合使用的场景。
4.2 实现步骤(以 Add 算子为例)
步骤 1:编译算子为 PyTorch 扩展
bash
运行
# 编译为PyTorch扩展
python setup.py build_ext --inplace
setup.py 代码:
python
运行
from setuptools import setup, Extension
from torch.utils.cpp_extension import BuildExtension, AscendExtension
setup(
name='add_op',
ext_modules=[
AscendExtension(
'add_op',
sources=['add_op.cpp'],
extra_compile_args=['-std=c++17']
)
],
cmdclass={'build_ext': BuildExtension}
)
步骤 2:PyTorch 中调用算子
python
运行
import torch
import add_op # 导入编译后的扩展
# 1. 准备输入Tensor(NPU设备)
device = torch.device('npu:0')
x1 = torch.tensor([1.0, 2.0, 3.0], dtype=torch.float16, device=device)
x2 = torch.tensor([4.0, 5.0, 6.0], dtype=torch.float16, device=device)
# 2. 调用Ascend C算子
y = add_op.Add(x1, x2)
# 3. 输出结果
print("PyTorch调用结果:", y) # 输出:tensor([5., 7., 9.], device='npu:0', dtype=torch.float16)
4.3 进阶:通过 torch.ops 调用
对于注册到昇腾框架的算子,还可以通过torch.ops直接调用:
python
运行
import torch
# 加载算子
torch.ops.load_library("./output/add_op.so")
# 调用算子
x1 = torch.tensor([1.0, 2.0], dtype=torch.float16, device='npu')
x2 = torch.tensor([3.0, 4.0], dtype=torch.float16, device='npu')
y = torch.ops.ascend.add(x1, x2)
print(y) # 输出:tensor([4., 6.], device='npu:0', dtype=torch.float16)
4.4 优缺点
优点:
- 易用性极高:符合 PyTorch 开发者的使用习惯;
- 集成性好:可直接嵌入 PyTorch 模型中,与其他算子无缝配合;
- 生态丰富:支持 PyTorch 的自动微分、分布式训练等功能。
缺点:
- 性能损耗:Python 与 C++ 的交互会带来少量性能开销;
- 调试复杂度:涉及 Python、C++、Device 侧三层,调试需要兼顾多个环节。
五、方式 4:MindSpore 调用(全场景框架集成)
MindSpore 是昇腾原生支持的全场景 AI 框架,Ascend C 算子可以通过 MindSpore 的 Custom 算子接口实现无缝调用。
5.1 适用场景
- 基于 MindSpore 的 AI 模型开发;
- 昇腾全栈解决方案中的算子集成;
- 需要端边云协同的场景。
5.2 实现步骤(以 Add 算子为例)
步骤 1:注册算子并编译
bash
运行
msopgen build --op-name Add --source add_op.cpp --output ./output --target ascend310b
步骤 2:MindSpore 中调用算子
python
运行
import mindspore as ms
from mindspore import ops, Tensor
# 1. 加载自定义算子
ops.load_custom_op("./output/add_op.so")
# 2. 准备输入Tensor
x1 = Tensor([1.0, 2.0, 3.0], dtype=ms.float16)
x2 = Tensor([4.0, 5.0, 6.0], dtype=ms.float16)
# 3. 调用算子(方式1:直接通过算子名称调用)
add_op = ops.Custom("Add", out_shape=lambda x, y: x.shape, out_dtype=lambda x, y: x.dtype)
y = add_op(x1, x2)
# 4. 输出结果
print("MindSpore调用结果:", y) # 输出:[5. 7. 9.]
# 进阶:方式2:结合MindSpore模型使用
class AddModel(ms.nn.Cell):
def __init__(self):
super(AddModel, self).__init__()
self.add_op = ops.Custom("Add", out_shape=lambda x, y: x.shape, out_dtype=lambda x, y: x.dtype)
def construct(self, x1, x2):
return self.add_op(x1, x2)
# 实例化模型并运行
model = AddModel()
y_model = model(x1, x2)
print("MindSpore模型调用结果:", y_model) # 输出:[5. 7. 9.]
5.3 优缺点
优点:
- 原生适配昇腾:与昇腾硬件、CANN 工具链深度协同,性能最优;
- 全场景支持:支持端、边、云多种部署场景,算子可跨设备复用;
- 动态图 / 静态图兼容:同时支持动态图调试与静态图优化。
缺点:
- 框架依赖性强:仅适用于 MindSpore 生态,跨框架兼容性弱;
- 自定义配置复杂:部分高级功能(如动态 Shape 适配)需要额外配置。
六、4 种调用方式的对比与选择策略
6.1 核心特性对比
| 调用方式 | 开发成本 | 性能表现 | 框架兼容性 | 适用场景 |
|---|---|---|---|---|
| Kernel 直调 | 高 | 最优 | 无 | 原型验证、C++ 小型项目 |
| Ascend CL 调用 | 中 | 优秀 | C++ 生态 | 中大型 C++ 项目、多库协同 |
| PyTorch 调用 | 低 | 良好 | PyTorch 生态 | AI 模型开发、Python 业务 |
| MindSpore 调用 | 低 | 优秀 | MindSpore 生态 | 昇腾全栈方案、端边云协同 |
6.2 选择策略
- 若需快速验证 Kernel 逻辑,无需框架集成:选择Kernel 直调;
- 若开发 C++ 项目,需与昇腾其他库配合:选择Ascend CL 调用;
- 若基于 PyTorch 开发 AI 模型:选择PyTorch 调用;
- 若使用昇腾全栈解决方案,需跨设备部署:选择MindSpore 调用。
常见问题
-
PyTorch 调用 Ascend C 算子时,提示 “找不到 NPU 设备”,该如何解决?答:首先确认已安装昇腾 NPU 驱动与 CANN 工具链,且环境变量(如
ASCEND_HOME、LD_LIBRARY_PATH)配置正确;其次检查 PyTorch 是否支持昇腾 NPU(需安装适配昇腾的 PyTorch 版本);最后在代码中显式指定设备(device = torch.device('npu:0')),避免设备识别失败。 -
MindSpore 调用算子时,动态 Shape 场景下计算结果错误,是什么原因?答:可能是算子未适配动态 Shape,或
out_shape函数配置错误。解决方案:一是在算子注册时开启动态 Shape 支持(通过.DYNAMIC_SHAPE()声明);二是确保out_shape函数能正确处理所有可能的输入 Shape(如使用lambda x, y: x.shape而非固定 Shape);三是在 Model 的construct函数中添加 Shape 校验逻辑。 -
Ascend CL 调用算子时,出现 “算子未找到” 错误,如何排查?答:排查步骤:① 确认算子动态库路径正确(
aclopLoadFromFile的路径需准确);② 检查算子注册名称与调用名称一致(如注册时为 “Add”,调用时不能写 “add”);③ 验证算子编译成功(查看输出目录是否生成.so文件与op_info.json);④ 检查 CANN 版本与算子编译时的目标版本匹配。 -
Kernel 直调时,如何处理多 Device 场景(如多卡并行)?答:需在代码中手动管理多 Device 资源:① 通过
rt.GetDeviceCount()获取设备数量;② 循环切换设备(rt.SetDevice(device_id));③ 为每个设备单独分配内存、拷贝数据、调用 Kernel;④ 最后汇总各设备的计算结果。若需更复杂的并行逻辑,建议使用 Ascend CL 或框架的分布式接口。 -
4 种调用方式中,如何统一实现算子的性能监控?答:可通过昇腾提供的
aclprof工具进行统一性能监控,无需修改算子代码:① 启动aclprof采集性能数据(指定设备 ID、采集时长);② 生成性能报告,查看算子的执行时间、内存带宽、计算效率等指标;③ 针对性能瓶颈(如内存拷贝耗时过长),优化对应的调用逻辑(如调整分块大小、使用异步拷贝)。
结语
Ascend C 算子的 4 种调用方式,覆盖了从底层验证到上层应用的全场景需求:Kernel 直调保证了极致的灵活性与性能,Ascend CL 调用平衡了 C++ 项目的开发效率与兼容性,PyTorch 调用适配了主流 AI 模型生态,MindSpore 调用则最大化发挥了昇腾硬件的原生能力。
对于开发者而言,无需拘泥于单一调用方式 —— 可以根据项目的不同阶段灵活切换:先用 Kernel 直调验证算子逻辑,再通过 Ascend CL 或框架调用集成到业务中。掌握这些调用方式,能让你开发的 Ascend C 算子真正实现 “一次开发,多场景复用”。
在后续的实践中,建议结合具体业务场景,重点深耕 1-2 种核心调用方式,并关注算子的兼容性与性能优化,让算子在实际应用中发挥最大价值。
2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖。
报名链接:https://www.hiascend.com/developer/activities/cann20252
编辑分享
在文章中添加一些常见问题的解答
常见问题的添加位置有哪些?
常见问题的数量有要求吗?
昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链
更多推荐

所有评论(0)