编译器人工智能AscendCANN【免费下载链接】AscendNPU-IRAscendNPU-IR是基于MLIRMulti-Level Intermediate Representation构建的面向昇腾亲和算子编译时使用的中间表示提供昇腾完备表达能力通过编译优化提升昇腾AI处理器计算效率支持通过生态框架使能昇腾AI处理器与深度调优项目地址https://gitcode.com/Ascend/AscendNPU-IR点击查看免费下载本文以 VecAdd向量加法算子为例完整演示 AscendNPU-IR 从一段 HIVM 方言 MLIR 出发经bishengir-compile编译为 NPU 可执行二进制kernel.o再到基于 CANN runtime 接口完成算子注册、核函数启动与结果回拷的端到端流程。读完本文你将掌握bishengir-compile的关键编译选项与 HIVM 核心指令语义并能够直接复用文中完整的 C 宿主代码在自己环境中跑通第一个 NPU 算子。示例全景从 HIVM IR 到 NPU 运行结果AscendNPU-IR即仓库中的bishengir目录是基于 MLIR 构建的昇腾亲和算子编译中间表示其工具链核心为bishengir-compile。按 架构设计 的说明bishengir-compile负责把高抽象的 Tile 级 Op 编译为感知 NPU 硬件架构的低层 Op随后由hivmc工具将低层 MLIR 转换为 LLVM IR、执行基于 LLVM IR 的低级指令编译优化最终生成算子二进制。本示例的完整数据流如下add.mlir (HIVM IR) │ bishengir-compile add.mlir --enable-hivm-compile -o kernel.o ▼ kernel.o (设备端算子二进制) │ main.cpp 读取 kernel.o调用 rtDevBinaryRegister / rtFunctionRegister 注册 ▼ rtKernelLaunch(add, ...) 在 NPU 上执行向量加法 │ ▼ 结果通过 aclrtMemcpy 回拷至 Host 并逐元素打印比对前置条件已完成 构建与安装将bishengir-compile加入PATH并安装好 CANN 环境、完成set_env.sh配置。需要特别注意的是端到端编译依赖 BiShengIR 模板库若使用的是 CANN 安装包随附的bishengir-compile模板库已内置若从源码自行编译该工具则构建时必须追加-t即--build-bishengir-template参数构建模板库该选项要求 CANN 9.0.0详见 installing_guide.md 中的build.sh参数表。准备待编译的 HIVM IRVecAdd 示例示例输入是一段手写的 HIVM 方言 MLIR功能是对 16 个int16元素做向量加法vec a b它也可以由其他 IR 转换得到。文件内容如下// add.mlir module { func.func add(%arg0: memref16xi16, #hivm.address_spacegm, %arg1: memref16xi16, #hivm.address_spacegm, %arg2: memref16xi16, #hivm.address_spacegm) attributes {hacc.entry, hacc.function_kind #hacc.function_kindDEVICE} { %alloc memref.alloc() : memref16xi16, #hivm.address_spaceub hivm.hir.load ins(%arg0 : memref16xi16, #hivm.address_spacegm) outs(%alloc : memref16xi16, #hivm.address_spaceub) %alloc_0 memref.alloc() : memref16xi16, #hivm.address_spaceub hivm.hir.load ins(%arg1 : memref16xi16, #hivm.address_spacegm) outs(%alloc_0 : memref16xi16, #hivm.address_spaceub) %alloc_1 memref.alloc() : memref16xi16, #hivm.address_spaceub hivm.hir.vadd ins(%alloc, %alloc_0 : memref16xi16, #hivm.address_spaceub, memref16xi16, #hivm.address_spaceub) outs(%alloc_1 : memref16xi16, #hivm.address_spaceub) hivm.hir.store ins(%alloc_1 : memref16xi16, #hivm.address_spaceub) outs(%arg2 : memref16xi16, #hivm.address_spacegm) return } }该文件在仓库中的对应拷贝为 add.mlir是官方集成测试的输入。逐段解读如下memref 与地址空间Address Spacememref16xi16, #hivm.address_spacegm表示一段 16 个i16元素的缓冲区其内存归属由#hivm.address_space...属性指定。根据 HIVMAttrs.td 中AddressSpace枚举的定义gm值为 1代表 Global Memory全局内存ub值为 6代表 Unified Buffer片上统一缓冲区此外还有cbufL1、caL0A、cbL0B、ccL0C、fixbuf、ssbuf、biasbuf等片上内存空间。因此本示例的语义是两个输入%arg0、%arg1与输出%arg2位于全局内存GM而计算过程在片上 Unified BufferUB中完成。函数属性hacc.entry与hacc.function_kind #hacc.function_kindDEVICE这是 HACC异构硬件抽象方言用于表达 Host/Device 编程模型与 launch 语义的标记。hacc.entry声明该函数为核函数入口DEVICE标明其运行在设备端bishengir-compile据此识别需要生成设备侧二进制的内容。三条 HIVM 指令构成完整计算流hivm.hir.load数据搬运指令。对照 HIVMDMAOps.td 中LoadOp的定义位于 MTE2 流水线其作用是把数据从全局内存GM加载到本地缓冲区当前仅支持加载到 Unified Buffer。这里将%arg0、%arg1分别加载到两个 UB 缓冲区。hivm.hir.vadd向量加法指令。对照 HIVMVectorOps.td 中VAddOp的定义Elementwise Binary Vector Addition Op支持 Vector-Vector 与 Vector-Scalar 两种形态它对两个 UB 输入逐元素相加并写入第三个 UB 缓冲区。hivm.hir.store数据回写指令对应StoreOp位于 MTE3 流水线将 UB 中的计算结果写回全局内存%arg2。从源码结构看HIVM 方言通过hivm.hir.*前缀的 HIRHigh-level IR指令屏蔽了底层指令参数编译器后续会自动完成片上内存规划PlanMemory、流水线同步AutoSync以及向低层指令的映射这正是 HIVM 方言Tile 级抽象、隐藏低层指令参数设计理念的体现可参见 architecture.md。使用 bishengir-compile 生成设备端二进制在add.mlir所在目录执行以下命令# 编译命令。 bishengir-compile add.mlir --enable-hivm-compile -o kernel.o生成的kernel.o即为可在 NPU 上执行的算子二进制。命令中各要素的说明如下参数含义add.mlir输入 MLIR 文件即上一节中的 HIVM IR--enable-hivm-compile使能 HIVM 编译流水线。该选项在 Options.td 中定义EnableHIVMCompile默认值为true对纯 HIVM IR无需 HFusion 流水线--enable-hfusion-compile默认关闭。MLIR 风格选项同时兼容单横线写法如集成测试 main.cpp 中的-enable-hivm-compiletrue-o kernel.o指定输出二进制文件名。对应BiShengIRCompilePass中的outputFile选项见 PassPipeline.cpp对于多硬件型号场景还可通过--target${soc_version}指定目标硬件型号默认为Ascend910B1该默认值定义于 Options.td 的Target选项bishengir-compile --version可查看版本信息。编译流水线原理从实现层面看bishengir-compile的 HIVM 编译入口是 PassPipeline.cpp 中的buildBiShengHIRPipeline当getEnableHIVMCompile()为真时依次构建ConvertToHIVMPipeline将 HFusion/Torch 等上层 IR 转换为 HIVM 方言与OptimizeHIVMPipelineHIVM 层编译优化。随后 BiShengIRCompileMain.cpp 中的runBiShengIRPipeline把优化后的 MLIR 写入临时文件调用外部hivmc工具runHIVMCCompileA3完成从低层 MLIR 到 LLVM IR、再到最终二进制的转换。工具之间的完整关系可参见 HIVMC.h 中的注释示意图MLIR → HFusion → HIVM → Minimal OpSet → LLVM IR → binary。调试时可在命令中追加--save-temps目录定义见 Options.td保留中间产物其中 HIVM 优化后的 IR 会以module.hivm.opt.mlir落盘便于检查编译各阶段结果。编写 CANN Runtime 注册与上板执行代码下面的 C 代码main.cpp实现了 CANN runtime 所需的算子注册与调用接口编译后与kernel.o一同即可上板执行。该文件在仓库中有对应的官方版本 main.cpp// main.cpp #include acl/acl.h #include acl/error_codes/rt_error_codes.h #include runtime/runtime/rt.h #include cstdio #include fstream #include stdlib.h #define EXPECT_EQ(a, b, msg) \ do { \ if ((a) ! (b)) { \ printf([failed] %s\n, (msg)); \ exit(1); \ } \ } while (0) char *readBinFile(const char *fileName, uint32_t *fileSize) { std::filebuf *pbuf; std::ifstream filestr; size_t size; filestr.open(fileName, std::ios::binary); if (!filestr) { printf(file:%s open failed!, fileName); return nullptr; } pbuf filestr.rdbuf(); size pbuf-pubseekoff(0, std::ios::end, std::ios::in); pbuf-pubseekpos(0, std::ios::in); char *buffer (char *)malloc(size); if (!buffer) { printf(NULL buffer!); return nullptr; } pbuf-sgetn(buffer, size); *fileSize size; printf([success] file:%s read succ!\n, fileName); filestr.close(); return buffer; } void *registerBinaryKernel(const char *filePath, char **buffer, const char *stubFunc, const char *kernelName) { void *binHandle nullptr; uint32_t bufferSize 0; *buffer readBinFile(filePath, bufferSize); if (*buffer nullptr) { printf(readBinFile: %s failed!\n, filePath); return binHandle; } rtDevBinary_t binary; binary.data *buffer; binary.length bufferSize; binary.magic RT_DEV_BINARY_MAGIC_ELF_AIVEC; binary.version 0; rtError_t rtRet rtDevBinaryRegister(binary, binHandle); if (rtRet ! RT_ERROR_NONE) { printf(rtDevBinaryRegister: %s failed, errorCode%d!\n, kernelName, rtRet); return binHandle; } rtRet rtFunctionRegister(binHandle, (const void *)stubFunc, kernelName, (void *)kernelName, 0); if (rtRet ! RT_ERROR_NONE) { printf(rtFunctionRegister: %s failed, errorCode%d!\n, kernelName, rtRet); return binHandle; } return binHandle; } int main() { // Initialize aclError error ACL_RT_SUCCESS; error aclInit(nullptr); EXPECT_EQ(error, ACL_RT_SUCCESS, init failed); error aclrtSetDevice(0); EXPECT_EQ(error, ACL_RT_SUCCESS, set device failed); aclrtStream stream; error aclrtCreateStream(stream); EXPECT_EQ(error, ACL_RT_SUCCESS, create stream failed); printf([success] initialize success\n); // Register the kernel char *buffer; const char *stubFunc add; void *binHandle registerBinaryKernel(./kernel.o, buffer, stubFunc, stubFunc); if (!binHandle) exit(1); printf([success] register kernel success\n); // Prepare data int16_t expectedValue[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; void *outputDevice nullptr; error aclrtMalloc((void **)outputDevice, sizeof(expectedValue), ACL_MEM_MALLOC_HUGE_FIRST); EXPECT_EQ(error, ACL_RT_SUCCESS, alloc output on device failed); int16_t input0Value[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; void *input0Device nullptr; error aclrtMalloc((void **)input0Device, sizeof(input0Value), ACL_MEM_MALLOC_HUGE_FIRST); EXPECT_EQ(error, ACL_RT_SUCCESS, alloc input0 on device failed); error aclrtMemcpy((void *)input0Device, sizeof(input0Value), input0Value, sizeof(input0Value), ACL_MEMCPY_HOST_TO_DEVICE); EXPECT_EQ(error, ACL_RT_SUCCESS, memcopy input0 to device failed); int16_t input1Value[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; void *input1Device nullptr; error aclrtMalloc((void **)input1Device, sizeof(input1Value), ACL_MEM_MALLOC_HUGE_FIRST); EXPECT_EQ(error, ACL_RT_SUCCESS, alloc input1 on device failed); error aclrtMemcpy((void *)input1Device, sizeof(input1Value), input1Value, sizeof(input1Value), ACL_MEMCPY_HOST_TO_DEVICE); EXPECT_EQ(error, ACL_RT_SUCCESS, memcopy input1 to device failed); printf([success] memcpy host to device success\n); // Invoke the kernel void *args[] {input0Device, input1Device, outputDevice}; rtKernelLaunch(stubFunc, 1, static_castvoid *(args), sizeof(args), nullptr, stream); error aclrtSynchronizeStream(stream); EXPECT_EQ(error, ACL_RT_SUCCESS, stream synchronize failed); printf([success] stream synchronize success\n); // Get the result int16_t *outHost nullptr; error aclrtMallocHost((void **)outHost, sizeof(expectedValue)); EXPECT_EQ(error, ACL_RT_SUCCESS, alloc output on host failed); error aclrtMemcpy(outHost, sizeof(expectedValue), outputDevice, sizeof(expectedValue), ACL_MEMCPY_DEVICE_TO_HOST); EXPECT_EQ(error, ACL_RT_SUCCESS, memcpy output to host failed); printf([success] memcpy device to host success\n); for (int i 0; i sizeof(expectedValue) / sizeof(int16_t); i) { printf(i%d\t Expect: %d\t\t\t\tResult: %d\n, i, expectedValue[i], outHost[i]); } printf([success] compare output success\n); free(buffer); aclrtFreeHost(outHost); aclrtFree(outputDevice); aclrtFree(input0Device); aclrtFree(input1Device); aclrtDestroyStream(stream); aclrtResetDevice(0); aclFinalize(); return 0; }关键环节解读整个程序可划分为四个阶段均以EXPECT_EQ宏做逐步骤的错误断言任何一步失败即打印[failed] msg并退出初始化InitializeaclInit(nullptr)初始化 ACL 运行环境aclrtSetDevice(0)绑定 0 号设备aclrtCreateStream创建任务流Stream后续所有设备操作都提交到该流上。注册算子Register the kernelregisterBinaryKernel是核心注册函数分两步完成readBinFile以二进制方式读取./kernel.o返回缓冲区及其长度rtDevBinaryRegister将二进制内容封装进rtDevBinary_t注意binary.magic RT_DEV_BINARY_MAGIC_ELF_AIVEC声明这是 AIVEC向量核ELF 格式的设备二进制后注册到 runtime得到binHandlertFunctionRegister在binHandle上注册名为add的核函数stub 函数名与 kernel 名均取add供后续 launch 使用。数据准备Prepare data输入input0Value {0..15}、input1Value全 1与输出outputDevice通过aclrtMalloc在设备侧分配再经aclrtMemcpyACL_MEMCPY_HOST_TO_DEVICE将 Host 数据拷入设备侧。注意核函数参数顺序必须与 MLIR 中func.func add(%arg0, %arg1, %arg2)的形参顺序一致。启动核函数与结果回拷Invoke / Get resultrtKernelLaunch(stubFunc, 1, args, sizeof(args), nullptr, stream)以 block 数为 1 启动add核函数传入三个设备指针aclrtSynchronizeStream等待流上任务完成随后用ACL_MEMCPY_DEVICE_TO_HOST将结果回拷到 Host 内存逐元素与期望值比对打印。编译可执行文件并运行main.cpp会从当前目录读取kernel.o并完成注册与调用编译命令如下main.cpp与kernel.o需位于同一目录# 加载 CANN 运行环境若已写入 Shell 配置可省略路径以实际安装目录为准详情参考文档「快速开始-安装与构建」。 source /usr/local/Ascend/ascend-toolkit/set_env.sh # 配置相应的头文件路径与链接路径若 CANN 安装在自定义路径请将 ASCEND_HOME_PATH 替换为实际路径或使用 set_env.sh 所设置的环境变量名。 RT_INC${ASCEND_HOME_PATH}/include PROF_INC${ASCEND_HOME_PATH}/include/experiment/msprof PKG_INC${ASCEND_HOME_PATH}/pkg_inc RT_LIB${ASCEND_HOME_PATH}/lib64 g main.cpp -I${RT_INC} -I${PROF_INC} -I${PKG_INC} -L ${RT_LIB} -l runtime -l ascendcl -o vec-add-I指定了 ACL 头文件acl/acl.h、错误码头文件与 runtime 头文件的搜索路径-L与-l runtime -l ascendcl链接 runtime 与 AscendCL 动态库。生成可执行文件后运行./vec-add预期输出片段如下16 个元素逐一比对Expect与Result完全一致i0 Expect: 1 Result: 1 i1 Expect: 2 Result: 2 i2 Expect: 3 Result: 3 i3 Expect: 4 Result: 4 ...官方集成测试与示例变体仓库在 test/Integration/HIVM/VecAdd/ 目录下提供了与本示例一一对应的可运行集成测试可作为验证环境是否就绪的基准输入 IR 与宿主代码分别位于 add.mlir 与 main.cpp。main.cpp顶部的 lit 注释完整描述了测试流程bishengir-compile -enable-hivm-compiletrue -o kernel.o add.mlir随后执行bishengir-npu-hivm-vec-add并以FileCheck断言 16 行输出全部满足iN Expect: N1 Result: N1。构建方法见 README.md在 CMake 配置中加入-DBISHENGIR_BUILD_EXAMPLESON并确保ASCEND_HOME_PATH指向 CANN 安装路径构建后./bin下会生成hivm-vec-add可执行文件与文档中的vec-add对应。此外仓库中文文档 examples.md 提供了同一套流程的向量累加VecCumsum变体MLIR 中使用hivm.hir.vcumsum带cum_dims、reverse属性计算前缀和编译命令补充了--target${soc_version}显式指定硬件型号的用法宿主代码同样遵循初始化 → 注册 → 数据准备 → launch → 回拷比对的结构可作为理解多算子扩展的参考。读者在将本示例改写为自有算子时只需同步修改 MLIR 中的核函数名、形参数量与类型以及宿主代码中stubFunc、设备指针数组args[]的对应关系即可。赞分享编译器人工智能AscendCANN【免费下载链接】AscendNPU-IRAscendNPU-IR是基于MLIRMulti-Level Intermediate Representation构建的面向昇腾亲和算子编译时使用的中间表示提供昇腾完备表达能力通过编译优化提升昇腾AI处理器计算效率支持通过生态框架使能昇腾AI处理器与深度调优项目地址https://gitcode.com/Ascend/AscendNPU-IR点击查看免费下载相关推荐AscendNPU-IR HIVM 向量加法实战使用 bishengir-compile 将 MLIR 编译为昇腾 NPU 可执行程序AscendNPU IR HIVM 向量加法实战使用 bishengir compile 将 MLIR 编译为昇腾 NPU 可执行程序 本文以 AscendN编译器人工智能AscendCANNAscendNPU-IR 实战用 bishengir-compile 与 HIVM 编写并运行向量加法 NPU 示例AscendNPU IR 实战用 bishengir compile 与 HIVM 编写并运行向量加法 NPU 示例 本文以 AscendNPU IRBiS编译器人工智能AscendCANNAscendNPU-IR 集成测试指南从 HIVM 向量加Vector Add到昇腾 NPU 端到端执行AscendNPU IR 集成测试指南从 HIVM 向量加Vector Add到昇腾 NPU 端到端执行 本文以 AscendNPU IRBiSheng编译器人工智能AscendCANN上一篇Faust 模型字段标记Model Tags用 Secret / Sensitive / Personal 保护流处理中的敏感数据下一篇终极指南5分钟免费解锁WeMod专业版功能告别付费限制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?