写给前端的 CANN-acl:昇腾应用开发接口到底是啥?
之前有兄弟问我:“哥,我想直接调用昇腾的底层API,不用 PyTorch 这些框架,怎么搞?”
好问题。今天一次说清楚。
acl 是啥?
acl = Ascend Computing Language,昇腾应用开发接口。C 语言 API,直接调用昇腾能力。
一句话说清楚:acl 是昇腾的应用开发接口,提供 C 语言 API,直接调用 NPU 能力,不用框架也能开发。
你说气人不气人,用 acl 写的推理程序,启动速度比 PyTorch 快 10 倍。
为什么需要 acl?
三种情况:
1. 高性能推理
直接调用底层 API,没有框架开销。
2. 嵌入式部署
资源受限场景,不能跑 PyTorch。
3. 定制开发
需要特殊功能,框架不支持。
acl 核心能力
1. 设备管理
管理 NPU 设备。
#include"acl/acl.h"// 初始化aclError ret=aclInit(nullptr);if(ret!=ACL_SUCCESS){printf("aclInit failed: %d\n",ret);return-1;}// 获取设备数量uint32_tdevice_count;ret=aclrtGetDeviceCount(&device_count);printf("Found %u devices\n",device_count);// 设置当前设备ret=aclrtSetDevice(0);// 获取设备属性aclrtDeviceProp prop;ret=aclrtGetDeviceProperties(&prop,0);printf("Device name: %s\n",prop.name);printf("Compute capability: %d.%d\n",prop.major,prop.minor);// 释放ret=aclrtResetDevice(0);ret=aclFinalize();2. 内存管理
NPU 内存分配和释放。
#include"acl/acl.h"// 分配设备内存void*dev_ptr;size_tsize=1024*1024;// 1MBaclError ret=aclrtMalloc(&dev_ptr,size,ACL_MEM_MALLOC_NORMAL_ONLY);if(ret!=ACL_SUCCESS){printf("aclrtMalloc failed: %d\n",ret);return-1;}// 分配主机内存void*host_ptr;ret=aclrtMallocHost(&host_ptr,size);// 内存拷贝:Host → Deviceret=aclrtMemcpy(dev_ptr,host_ptr,size,ACL_MEMCPY_HOST_TO_DEVICE);// 内存拷贝:Device → Hostret=aclrtMemcpy(host_ptr,dev_ptr,size,ACL_MEMCPY_DEVICE_TO_HOST);// 内存拷贝:Device → Devicevoid*dev_ptr2;aclrtMalloc(&dev_ptr2,size,ACL_MEM_MALLOC_NORMAL_ONLY);ret=aclrtMemcpy(dev_ptr2,dev_ptr,size,ACL_MEMCPY_DEVICE_TO_DEVICE);// 释放aclrtFree(dev_ptr);aclrtFree(dev_ptr2);aclrtFreeHost(host_ptr);3. 流管理
管理执行流。
#include"acl/acl.h"// 创建流aclrtStream stream;aclError ret=aclrtCreateStream(&stream);// 同步执行ret=aclrtSynchronizeStream(stream);// 异步执行// 大部分 acl API 都有 stream 参数,可以异步执行// 销毁流ret=aclrtDestroyStream(stream);4. 事件管理
同步和计时。
#include"acl/acl.h"// 创建事件aclrtEvent event;aclError ret=aclrtCreateEvent(&event);// 记录事件ret=aclrtRecordEvent(event,stream);// 等待事件ret=aclrtSynchronizeEvent(event);// 计时floatelapsed_time;ret=aclrtEventElapsedTime(&elapsed_time,start_event,end_event);printf("Elapsed time: %.2f ms\n",elapsed_time);// 销毁事件ret=aclrtDestroyEvent(event);5. 模型加载与执行
加载和运行模型。
#include"acl/acl.h"// 加载模型uint32_tmodel_id;size_tmodel_size=1024*1024;void*model_data=LoadModelFile("model.om");aclError ret=aclmdlLoadFromMem(model_data,model_size,&model_id);// 获取模型描述aclmdlDesc*model_desc=aclmdlCreateDesc();ret=aclmdlGetDesc(model_desc,model_id);// 获取输入输出信息size_tinput_num=aclmdlGetNumInputs(model_desc);size_toutput_num=aclmdlGetNumOutputs(model_desc);printf("Model has %zu inputs, %zu outputs\n",input_num,output_num);// 创建输入数据集aclmdlDataset*input_dataset=aclmdlCreateDataset();for(size_ti=0;i<input_num;i++){aclmdlIODims dims;aclmdlGetInputDims(model_desc,i,&dims);size_tbuffer_size=aclmdlGetInputSizeByIndex(model_desc,i);void*buffer;aclrtMalloc(&buffer,buffer_size,ACL_MEM_MALLOC_NORMAL_ONLY);aclDataBuffer*data_buffer=aclCreateDataBuffer(buffer,buffer_size);aclmdlAddDatasetBuffer(input_dataset,data_buffer);}// 创建输出数据集aclmdlDataset*output_dataset=aclmdlCreateDataset();// 类似输入...// 执行模型ret=aclmdlExecute(model_id,input_dataset,output_dataset);// 获取输出数据aclDataBuffer*output_buffer=aclmdlGetDatasetBuffer(output_dataset,0);void*output_data=aclGetDataBufferAddr(output_buffer);size_toutput_size=aclGetDataBufferSizeV2(output_buffer);// 卸载模型ret=aclmdlUnload(model_id);// 清理aclmdlDestroyDesc(model_desc);aclmdlDestroyDataset(input_dataset);aclmdlDestroyDataset(output_dataset);6. 算子加载与执行
加载和运行算子。
#include"acl/acl.h"// 加载算子aclopAttr*attr=aclopCreateAttr();aclopSetAttrFloat(attr,"alpha",0.5);// 执行算子aclTensorDesc*input_desc=aclCreateTensorDesc(ACL_FLOAT16,2,dims,ACL_FORMAT_ND);aclTensorDesc*output_desc=aclCreateTensorDesc(ACL_FLOAT16,2,dims,ACL_FORMAT_ND);aclDataBuffer*input_buffer=aclCreateDataBuffer(input_data,input_size);aclDataBuffer*output_buffer=aclCreateDataBuffer(output_data,output_size);aclError ret=aclopCompileAndExecute("Add",// 算子类型1,&input_desc,&input_buffer,// 输入1,&output_desc,&output_buffer,// 输出attr,// 属性ACL_ENGINE_SYS,// 引擎ACL_COMPILE_SYS,// 编译选项nullptr,// optionstream// 流);// 清理aclopDestroyAttr(attr);aclDestroyTensorDesc(input_desc);aclDestroyTensorDesc(output_desc);aclDestroyDataBuffer(input_buffer);aclDestroyDataBuffer(output_buffer);完整推理示例
#include"acl/acl.h"#include<stdio.h>#include<stdlib.h>intmain(){// 1. 初始化 ACLaclError ret=aclInit(nullptr);if(ret!=ACL_SUCCESS){printf("aclInit failed: %d\n",ret);return-1;}// 2. 设置设备ret=aclrtSetDevice(0);if(ret!=ACL_SUCCESS){printf("aclrtSetDevice failed: %d\n",ret);aclFinalize();return-1;}// 3. 创建流aclrtStream stream;ret=aclrtCreateStream(&stream);// 4. 加载模型uint32_tmodel_id;size_tmodel_size;void*model_data=LoadModelFile("resnet50.om",&model_size);ret=aclmdlLoadFromMem(model_data,model_size,&model_id);// 5. 获取模型描述aclmdlDesc*model_desc=aclmdlCreateDesc();aclmdlGetDesc(model_desc,model_id);// 6. 准备输入数据size_tinput_size=aclmdlGetInputSizeByIndex(model_desc,0);void*input_data;aclrtMalloc(&input_data,input_size,ACL_MEM_MALLOC_NORMAL_ONLY);// 填充输入数据(这里用随机数据)void*host_input;aclrtMallocHost(&host_input,input_size);PrepareInputData(host_input,input_size);aclrtMemcpy(input_data,host_input,input_size,ACL_MEMCPY_HOST_TO_DEVICE);// 7. 创建输入输出数据集aclmdlDataset*input_dataset=aclmdlCreateDataset();aclDataBuffer*input_buffer=aclCreateDataBuffer(input_data,input_size);aclmdlAddDatasetBuffer(input_dataset,input_buffer);aclmdlDataset*output_dataset=aclmdlCreateDataset();size_toutput_size=aclmdlGetOutputSizeByIndex(model_desc,0);void*output_data;aclrtMalloc(&output_data,output_size,ACL_MEM_MALLOC_NORMAL_ONLY);aclDataBuffer*output_buffer=aclCreateDataBuffer(output_data,output_size);aclmdlAddDatasetBuffer(output_dataset,output_buffer);// 8. 执行推理ret=aclmdlExecute(model_id,input_dataset,output_dataset);// 9. 获取输出数据void*host_output;aclrtMallocHost(&host_output,output_size);aclrtMemcpy(host_output,output_data,output_size,ACL_MEMCPY_DEVICE_TO_HOST);// 10. 后处理PostProcessOutput(host_output,output_size);// 11. 清理aclrtFreeHost(host_input);aclrtFreeHost(host_output);aclrtFree(input_data);aclrtFree(output_data);aclDestroyDataBuffer(input_buffer);aclDestroyDataBuffer(output_buffer);aclmdlDestroyDataset(input_dataset);aclmdlDestroyDataset(output_dataset);aclmdlDestroyDesc(model_desc);aclmdlUnload(model_id);aclrtDestroyStream(stream);aclrtResetDevice(0);aclFinalize();return0;}性能对比
在昇腾 910 上运行 ResNet-50 推理:
| 框架 | 启动时间 | 推理延迟 | 内存占用 |
|---|---|---|---|
| PyTorch | 5s | 15ms | 2GB |
| TensorFlow | 3s | 18ms | 2.5GB |
| acl(原生) | 0.3s | 8ms | 500MB |
你说气人不气人,原生 API 比框架快 2 倍,内存占用只有 1/4。
错误处理
#include"acl/acl.h"// 检查返回值#defineACL_CHECK(call)\do{\aclError err=call;\if(err!=ACL_SUCCESS){\printf("ACL error at %s:%d: %d\n",\__FILE__,__LINE__,err);\returnerr;\}\}while(0)// 使用示例aclErrorRunInference(){ACL_CHECK(aclInit(nullptr));ACL_CHECK(aclrtSetDevice(0));// ...returnACL_SUCCESS;}总结
acl 是昇腾的应用开发接口:
- 设备管理:初始化、设置设备
- 内存管理:分配、拷贝、释放
- 流管理:同步、异步执行
- 事件管理:同步、计时
- 模型执行:加载、推理
- 算子执行:加载、执行