当前位置: 首页 > news >正文

Ascend C 算子开发实战:从零写一个矩阵乘法

前言CANN 自带的算子库已经覆盖了大部分常用算子但总会遇到不支持的。这时候要用 Ascend C 自己写一个算子。Ascend C 是昇腾的算子编程语言语法类似 C但专门针对达芬奇架构做了优化。这篇文章讲怎么从零写一个矩阵乘法算子。开发环境准备安装算子开发包# CANN 工具包自带算子开发工具whichcanndev# 输出/usr/local/Ascend/ascend-toolkit/latest/bin/canndev# 如果没有安装 toolkitaptinstallascend-toolkit创建算子工程# 用 canndev 创建算子工程canndev create--projectmatmul_custom--output./workspace# 工程结构tree ./workspace/matmul_custom/# output:# matmul_custom/# ├── CMakeLists.txt# ├── framework/# │ └── tensor_add_impl.cpp # 算子实现# ├── op_proto/# │ └── tensor_add.h # 算子原型# ├── unittest/# │ └── test_tensor_add.py # 单元测试# └── CMakeLists.txt算子原型定义第一步是定义算子的原型输入、输出、属性。头文件定义// matmul_custom.h#ifndefMATMUL_CUSTOM_H#defineMATMUL_CUSTOM_H#includetiking_pub_api.hnamespaceacl{namespaceops{classMatMulCustom{public:MatMulCustom()default;~MatMulCustom()default;// 初始化算子graphStatusInit(constTensora,constTensorb,Tensorc,booltrans_afalse,booltrans_bfalse);// 推理接口graphStatusInferShape();// 数据类型推断graphStatusInferDataType();};}// namespace ops}// namespace acl#endif// MATMUL_CUSTOM_H实现文件// matmul_custom.cpp#includematmul_custom.hnamespaceacl{namespaceops{graphStatusMatMulCustom::Init(constTensora,constTensorb,Tensorc,booltrans_a,booltrans_b){// 设置输入(void)ge::Node::GetNodeFromTensor(a);(void)ge::Node::GetNodeFromTensor(b);// 设置属性SetAttr(trans_a,trans_a);SetAttr(trans_b,trans_b);returnGRAPH_SUCCESS;}graphStatusMatMulCustom::InferShape(){// 获取输入 shapestd::vectorint64_tshape_a;std::vectorint64_tshape_b;GetInputDesc(0).GetShape(shape_a);GetInputDesc(1).GetShape(shape_b);// 计算输出 shapebooltrans_aGetAttr(trans_a)-GetBool();booltrans_bGetAttr(trans_b)-GetBool();int64_tmtrans_a?shape_a[1]:shape_a[0];int64_tntrans_b?shape_b[0]:shape_b[1];std::vectorint64_tshape_c{m,n};GetOutputDesc(0).SetShape(shape_c);returnGRAPH_SUCCESS;}graphStatusMatMulCustom::InferDataType(){// 输出数据类型和输入 A 一致DataType dtypeGetInputDesc(0).GetDataType();GetOutputDesc(0).SetDataType(dtype);returnGRAPH_SUCCESS;}}// namespace ops}// namespace acl算子实现核心这是最关键的部分用 Ascend C 实现矩阵乘法。基本框架#includekernel_operator.hclassMatMulCustomKernel{public:__aicore__inlineMatMulCustomKernel(){}__aicore__voidInit(GM_ADDR a,GM_ADDR b,GM_ADDR c,int32_tM,int32_tN,int32_tK);__aicore__voidProcess();private:GlobalTensorfloataGm;// 输入 AGM 内存GlobalTensorfloatbGm;// 输入 BGM 内存GlobalTensorfloatcGm;// 输出 CGM 内存LocalTensorfloataLocal;// 输入 AUB 内存LocalTensorfloatbLocal;// 输入 BUB 内存LocalTensorfloatcLocal;// 输出 CUB 内存int32_tM,N,K;};externC__global__voidmatmul_custom(GM_ADDR a,GM_ADDR b,GM_ADDR c,int32_tM,int32_tN,int32_tK){MatMulCustomKernel op;op.Init(a,b,c,M,N,K);op.Process();}Init 函数__aicore__voidMatMulCustomKernel::Init(GM_ADDR a,GM_ADDR b,GM_ADDR c,int32_tM,int32_tN,int32_tK){// 设置 GM 地址aGm.SetGlobalBuffer((__gm__float*)a);bGm.SetGlobalBuffer((__gm__float*)b);cGm.SetGlobalBuffer((__gm__float*)c);// 保存参数this-MM;this-NN;this-KK;// 分配 UB 空间constexprint32_tBLOCK_SIZE32;// 一个 block 的大小aLocalGetUbBlockfloat(M*K/BLOCK_SIZE);bLocalGetUbBlockfloat(K*N/BLOCK_SIZE);cLocalGetUbBlockfloat(M*N/BLOCK_SIZE);}Process 函数核心计算__aicore__voidMatMulCustomKernel::Process(){// 1. 把数据从 GM 搬到 UBCopyIn(aGm,aLocal,M*K);CopyIn(bGm,bLocal,K*N);// 2. 矩阵乘法计算MatMul(cLocal,aLocal,bLocal,M,N,K);// 3. 把结果从 UB 写回 GMCopyOut(cLocal,cGm,M*N);}// 矩阵乘法实现voidMatMul(LocalTensorfloatc,LocalTensorfloata,LocalTensorfloatb,int32_tM,int32_tN,int32_tK){// 简化版本逐行逐列计算for(int32_ti0;iM;i){for(int32_tj0;jN;j){floatsum0.0f;for(int32_tk0;kK;k){suma[i*Kk]*b[k*Nj];}c[i*Nj]sum;}}}优化版本使用 Cube Unit#includelib_api.h// 使用 Cube Unit 做矩阵乘法voidMatMulCube(LocalTensorfloatc,LocalTensorfloata,LocalTensorfloatb,int32_tM,int32_tN,int32_tK){// 调用 Cube Unit 的 MatMul 接口matmul_t matmul_para;matmul_para.MM;matmul_para.NN;matmul_para.KK;matmul_para.is_trans_afalse;matmul_para.is_trans_bfalse;// 用 Cube Unit 计算MatMul(c,a,b,matmul_para);}编译算子CMake 配置# CMakeLists.txt cmake_minimum_required(VERSION 3.10) project(matmul_custom) # 设置 Ascend C 编译选项 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -marchdavinci) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -O2) # 添加算子实现文件 add_library(matmul_custom SHARED matmul_custom.cpp matmul_custom_kernel.cpp ) # 链接 Ascend C 库 target_link_libraries(matmul_custom ascendcl tbe_operator )编译命令# 创建编译目录mkdirbuildcdbuild# 配置cmake..# 编译make-j8# 输出libmatmul_custom.so在 PyTorch 里调用注册自定义算子importtorchimporttorch.npuimportctypes# 加载自定义算子库libctypes.CDLL(./libmatmul_custom.so)# 定义 Python 接口defmatmul_custom(a:torch.Tensor,b:torch.Tensor)-torch.Tensor:自定义矩阵乘法# 检查输入asserta.dim()2andb.dim()2asserta.shape[1]b.shape[0]M,Ka.shape K2,Nb.shape# 创建输出 tensorctorch.empty(M,N,devicenpu:0,dtypetorch.float32)# 调用自定义算子lib.matmul_custom(a.data_ptr(),b.data_ptr(),c.data_ptr(),M,N,K)returnc测试自定义算子# 创建测试数据atorch.randn(128,256,devicenpu:0)btorch.randn(256,64,devicenpu:0)# 用自定义算子计算c_custommatmul_custom(a,b)# 用 PyTorch 内置算子计算参考c_reftorch.matmul(a,b)# 对比精度cosine_simtorch.nn.functional.cosine_similarity(c_custom.flatten(),c_ref.flatten(),dim0)print(f余弦相似度:{cosine_sim.item():.6f})# 对比性能importtime# 预热for_inrange(10):matmul_custom(a,b)torch.npu.synchronize()starttime.time()for_inrange(100):matmul_custom(a,b)torch.npu.synchronize()endtime.time()print(f自定义算子延迟:{(end-start)/100*1000:.2f}ms)性能优化使用 Cube Unit达芬奇架构的 Cube Unit 是专门为矩阵乘法设计的硬件单元比用 Vector Unit 算快得多。// 使用 Cube Unit#includelib_api.hvoidMatMulOptimized(LocalTensorfloatc,LocalTensorfloata,LocalTensorfloatb,int32_tM,int32_tN,int32_tK){// 检查是否可以用 Cubeif(M16N16K16){// 用 Cube Unitmatmul_t params;params.MM;params.NN;params.KK;MatMul(c,a,b,params);}else{// 用小矩阵算法MatMulSmall(c,a,b,M,N,K);}}分块计算当矩阵太大UB 放不下时要分块计算。constexprint32_tTILE_SIZE32;// 块大小voidMatMulTiled(LocalTensorfloatc,GlobalTensorfloataGm,GlobalTensorfloatbGm,int32_tM,int32_tN,int32_tK){// 分块计算for(int32_ti0;iM;iTILE_SIZE){for(int32_tj0;jN;jTILE_SIZE){// 搬运当前块CopyInA(aGm,aLocal,i,min(iTILE_SIZE,M),K);CopyInB(bGm,bLocal,j,min(jTILE_SIZE,N),K);// 计算当前块MatMulTile(cLocal,aLocal,bLocal,min(TILE_SIZE,M-i),min(TILE_SIZE,N-j),K);// 写回当前块CopyOutC(cLocal,cGm,i,j,min(TILE_SIZE,M-i),min(TILE_SIZE,N-j));}}}调试技巧打印调试// 在算子实现里加打印#includecstdio__aicore__voidMatMulCustomKernel::Process(){// 打印输入参数printf(M%d, N%d, K%d\n,M,N,K);// 打印输入数据前 10 个for(inti0;imin(10,M*K);i){printf(a[%d]%f\n,i,aLocal.GetValue(i));}// 计算结果MatMul(cLocal,aLocal,bLocal,M,N,K);// 打印输出数据前 10 个for(inti0;imin(10,M*N);i){printf(c[%d]%f\n,i,cLocal.GetValue(i));}}用 Ascend CL 调试importacl# 初始化acl.init()acl.rt.set_device(0)# 运行算子调试模式acl.rt.set_op_execute_mode(debug)# 运行# ...运行算子# 查看日志# 日志在 /var/log/Ascend/ascend_toolkit/matmul_custom.log常见问题问题一编译报错error: matmul_t was not declared in this scope解决包含正确的头文件#includelib_api.h// 包含 matmul_t 的定义问题二运行时报错[ERROR] Kernel execute failed: out of memory解决减小块大小或者检查 UB 大小是否足够// 检查 UB 大小uint32_tub_sizeGetUbSize();printf(UB size: %u bytes\n,ub_size);// 减小块大小constexprint32_tTILE_SIZE16;// 从 32 改成 16问题三精度不达标余弦相似度: 0.97 (应该 0.99)解决检查计算精度可能需要用 FP32// 用 FP32 计算GlobalTensorfloataGm;// float FP32GlobalTensorfloatbGm;GlobalTensorfloatcGm;// 不要用 FP16// GlobalTensorhalf aGm; // half FP16精度可能不够参考资源Ascend C 编程指南: https://www.hiascend.com/document/detail/zh/CANN/算子开发最佳实践: https://www.hiascend.com/document/detail/zh/CANN/算子样例代码: https://atomgit.com/cann/samples达芬奇架构白皮书: https://www.hiascend.com/document/detail/zh/CANN/总结用 Ascend C 开发自定义算子流程是定义算子原型 → 实现算子逻辑 → 编译成.so→ 在 PyTorch 里调用。核心是要理解达芬奇架构的内存层次GM → UB → 计算单元以及 Cube Unit 和 Vector Unit 的适用场景。性能优化的关键是尽量用 Cube Unit 做矩阵乘法太大的矩阵要分块计算。调试可以用printf打印中间结果或者用 Ascend CL 的调试模式。精度问题通常是因为用了 FP16改成 FP32 一般能解决。
http://www.rkmt.cn/news/1376180.html

相关文章:

  • 英特尔 Hammer Lake 处理器将引入统一核心架构并重拾超线程技术
  • 实战避坑:在Linux服务器上配置PTP(ptp4l)实现微秒级时间同步的完整流程
  • CANN 算子拆解:FlashAttention 在 ops-transformer 里的实现逻辑
  • UE5 DefaultLayout.ini 源码级解析:UI布局的ASCII拓扑图
  • 环境配置助手 For Mac:macOS环境变量可视化管理工具
  • WebFlux + R2DBC 场景下的分库分表预研:从架构选型到落地风险
  • Wireshark实战还原中国菜刀Webshell通信与解码
  • AI 系统分层治理:从用户无感知降级到多能力协同的架构演进
  • Java + Spring Boot 操作 Kafka 完整学习指南
  • 深入 QEMU 热迁移
  • BetterJoy终极配置指南:让Switch手柄在电脑上完美运行
  • 机器学习在期权定价中的应用:超越Black-Scholes与Heston模型的实践
  • 12.【.NET10 实战--孢子记账--产品智能化】--技术选型
  • 医疗物联网异常检测:八种机器学习算法实战对比与选型指南
  • 手把手教你无损转换:把老电脑的Legacy启动盘改成UEFI+GPT(附DiskGenius操作截图)
  • 大麦网抢票神器终极指南:告别黄牛票的Python自动化解决方案
  • 终极指南:3种简单方法快速重置JetBrains IDE试用期
  • 碧蓝航线Alas自动化脚本:解放双手的终极游戏助手完整指南
  • 终极指南:如何用SketchUp STL插件轻松实现3D打印文件转换
  • 华硕笔记本性能释放终极方案:G-Helper轻量控制工具完全指南
  • [408] [数据结构] 链表-代码基础
  • 以书香润心,借坚韧前行
  • 信创运维实战:在ARM版银河麒麟V10上离线搞定telnet的完整流程(附软件包查找技巧)
  • 百度网盘解析工具终极指南:3分钟突破限速实现高速下载
  • 从 Session 到 JWT:Web 认证系统的发展与 JWT 原理详解
  • 匿名内部类的使用场景 java反射机制
  • 小小屠龙原始火龙手游官网下载:小小屠龙原始火龙最新官方下载渠道
  • 普通人如何在 GPT‑5.5 时代保持竞争力:不被替代、学会协作、放大优势
  • IwaraDownloadTool:浏览器扩展视频嗅探引擎深度解析与架构设计
  • 阿里云服务器CPU 100%排查指南:识别伪装挖矿病毒的三步法