{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 2.2 算子执行流程\n",
"\n",
"本节详细介绍自定义算子的完整执行流程,包括 aclnn 单算子 API 调用和 GE 图模式两种主要调用方式的执行流程。\n",
"\n",
"---\n",
"\n",
"## 学习目标\n",
"\n",
"完成本节后,你将能够:\n",
"- 理解算子的执行流程架构\n",
"- 掌握 aclnn 单算子 API 调用流程\n",
"- 掌握 GE 图模式执行流程\n",
"- 了解 内核调用符<<<>>> kernel直调的执行方式\n",
"- 掌握各阶段的调试方法\n",
"\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. 环境准备\n",
"正式开始学习之前,先要对jupyter环境进行初始化。以下代码完成了初始化并将环境中的变量导入jupyter环境,同时完成了代码目录的创建。保证能正常导入代码以及使用bisheng编译器,完成算子的开发及编译。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!mkdir -p Sources/02.02\n",
"!mkdir -p Sources/02.02/add\n",
"\n",
"import os, subprocess\n",
"env = subprocess.check_output(\"bash -l -c 'source $ASCEND_TOOLKIT_HOME/set_env.sh && env'\", shell=True, text=True)\n",
"for line in env.splitlines():\n",
" if \"=\" in line: os.environ.__setitem__(*line.split(\"=\", 1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## 2. 算子执行通路介绍\n",
"\n",
"算子开发完成后,需要验证完整的通路,需对算子的执行过程有所了解,下图介绍了框架到算子的执行流程,共5个通路。\n",
"\n",
"<img src=\"./images/算子执行通路.png\" alt=\"算子执行通路\" width=\"700px\" >\n",
"\n",
"从上层框架到算子执行,一共有5条路径,开发过程中,重点关注的是路径3和路径5,也是本节课程重点介绍的路径。\n",
"\n",
"\n",
"路径3:即所谓的图模式。图模式是相对于单算子模式的,图模式下,通过有向无环图,描述网络的计算逻辑。图模式的Host调度,可以避免总是返回Python调用栈,避免冗余流程与数据结构转换,并且可以直接使用图编译阶段完成的Infer Shape与Tiling计算结果。\n",
"\n",
"路径5:即所谓的单算子模式或者eager模式(即时执行),由Host CPU逐个下发算子,一个算子的下发流程包含Python处理、Python到C++数据结构转换、Tiling计算、申请算子的Workspace内存和输出内存、Launch等Host操作。为了加速单算子Host调度,在Pytorch中,昇腾适配层采用了生产者-消费者双线程模式加速,生产者线程主要负责Launch之前的处理动作,消费者线程主要负责Launch算子,对标路径2。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ascend C算子常见的调用方式有kernel直调、单算子调用、第三方框架中调用。工程化的算子通常以单算子调用和第三方框架中调用为主。具体调用方式和调用条件如下:\n",
"\n",
"<table >\n",
" <tr>\n",
" <th style=\"border: 1px solid #ddd; padding: 12px; text-align: left; font-weight: bold; min-width: 180px;\">调用方式</th>\n",
" <th style=\"border: 1px solid #ddd; padding: 12px; text-align: left; font-weight: bold; min-width: 180px;\">使用条件</th>\n",
" </tr>\n",
" <tr>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">单算子API</td>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">算子工程编译部署</td>\n",
" </tr>\n",
" <tr>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">单算子模型执行</td>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">算子入图开发,算子工程编译部署</td>\n",
" </tr>\n",
" <tr>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">IR构图</td>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">算子入图开发,算子工程编译部署</td>\n",
" </tr>\n",
" <tr>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">Pytorch框架调用</td>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">插件适配开发,算子工程编译部署</td>\n",
" </tr>\n",
" <tr>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">TensorFlow框架调用</td>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">插件适配开发,算子工程编译部署</td>\n",
" </tr>\n",
" <tr>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">Pybind调用</td>\n",
" <td style=\"border: 1px solid #ddd; padding: 10px; vertical-align: middle; text-align: left;\">算子工程编译部署</td>\n",
" </tr>\n",
"</table> "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## 3. 单算子调用执行流程\n",
"\n",
"下面我们以Add算子为例,基于torch框架,介绍单算子API的执行流程。\n",
"\n",
"Add算子的介绍参见CANN社区:https://gitcode.com/cann/ops-math/blob/master/math/add/README.md\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"step1: 用户使用torch,调用Add算子。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"import torch_npu\n",
"\n",
"x = torch.randint(-5, 5, (2, 2), dtype=torch.int32).npu()\n",
"y = torch.randint(-5, 5, (2, 2), dtype=torch.int32).npu()\n",
"\n",
"z = torch.add(x, y)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"step2:torch层调用Add算子的aclnn接口,aclnn分为两段式接口,会先调用aclnnAddGetWorkspace分配内存,然后调用aclnnAdd接口。\n",
"\n",
"step3: aclnn根据注册的算子原型,找到算子执行的二进制,即kernel.o文件。\n",
"\n",
"step4: 进入Add算子的Tiling计算阶段,完成相关tilingData计算。\n",
"\n",
"step5: rts 进行kernelLaunch调度执行\n",
"\n",
"step6: Add算子kernel,从GM上CopyIn数据,并根据TilingData,在AICore上执行Compute计算,最后从UB上CopyOut到GM上。\n",
"\n",
"详细流程如图:\n",
"\n",
"<img src=\"./images/Add算子执行流程.png\" alt=\"Add算子执行接口流程\" width=\"700px\" >\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## 4. 图模式调用执行流程\n",
"\n",
"\n",
"### 4.1 静态图执行流程\n",
"\n",
"静态图,即图上算子Shape在编译时是已知且明确的(shape为[10, 2])。\n",
"\n",
"以Model粒度下发执行:\n",
"\n",
"- 模型加载时GE向rts申请模型流,按顺序下发所有算子的Kernel,此时模型不会立即执行\n",
"\n",
"- 模型执行开始时,由GE调用rts的rtModelExecute接口下发一个ModelExecute任务,触发流上的tasks启动,执行过程中没有host调用\n",
"\n",
"静态图执行示意:\n",
"\n",
"<img src=\"./images/静态图执行流程.png\" alt=\"静态图执行示意\" width=\"700px\" >\n",
"\n",
"\n",
"### 4.2 动态图执行流程\n",
"\n",
"动态图:图上算子Shape在编译时未知(shape中包含-1或-2),编译结果需要支持(range内)所有shape的执行。\n",
"\n",
"动态图分类:\n",
"\n",
"- 纯动态图 —— 网络输入时动态\n",
"\n",
"- 动静混合 —— 网络中部分输入动态,部分输入静态;或者网络输入为静态,但存在二三类算子导致inferShape推导出动态。\n",
"\n",
"动态图执行:\n",
"\n",
"走GE的动态图执行器(常说的runtime2.0)按照拓扑序调用每个算子的inferShape、Tiling,为每个算子分配输出内存和WorkSpace内存,组装args后调用rtKenelLaunch完成算子下发。\n",
"\n",
"\n",
"动态图执行示意:\n",
"\n",
"<img src=\"./images/动态图执行流程.png\" alt=\"动态图执行示意\" width=\"700px\" >\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## 5、使用内核调用符<<<>>> kernel直调执行流程\n",
"\n",
"以Add算子为例子,演示内核调用符<<<>>> kernel直调执行流程。\n",
"\n",
"- 样例功能: \n",
" Add样例实现了两个数据相加,返回相加结果的功能。对应的数学表达式为: \n",
" ```\n",
" z = x + y\n",
" ```\n",
"\n",
"- 样例规格:\n",
" <table>\n",
" <tr><td rowspan=\"1\" align=\"center\">样例类型(OpType)</td><td colspan=\"4\" align=\"center\">Add</td></tr>\n",
" <tr><td rowspan=\"3\" align=\"center\">样例输入</td><td align=\"center\">name</td><td align=\"center\">shape</td><td align=\"center\">data type</td><td align=\"center\">format</td></tr>\n",
" <tr><td align=\"center\">x</td><td align=\"center\">[8, 2048]</td><td align=\"center\">float</td><td align=\"center\">ND</td></tr>\n",
" <tr><td align=\"center\">y</td><td align=\"center\">[8, 2048]</td><td align=\"center\">float</td><td align=\"center\">ND</td></tr>\n",
" <tr><td rowspan=\"1\" align=\"center\">样例输出</td><td align=\"center\">z</td><td align=\"center\">[8, 2048]</td><td align=\"center\">float</td><td align=\"center\">ND</td></tr>\n",
" <tr><td rowspan=\"1\" align=\"center\">核函数名</td><td colspan=\"4\" align=\"center\">add_custom</td></tr>\n",
" </table>\n",
"\n",
"- 样例实现:\n",
" - Kernel实现 \n",
" 本样例使用8个核完成计算,每个核处理2048个元素。计算偏移量为:\n",
" ```\n",
" block_idx * blockLength\n",
" ```\n",
"\n",
" Add样例的实现流程分为3个步骤:\n",
"\n",
" **第一步:搬运数据到UB(Unified Buffer)**\n",
" \n",
" 将GM(Global Memory)上的输入x和y搬运到UB(Unified Buffer)上的xLocal、yLocal中。\n",
" \n",
" **第二步:执行向量加法**\n",
" \n",
" 对xLocal、yLocal执行加法操作,计算结果存储在UB(Unified Buffer)上的zLocal中。\n",
" \n",
" **第三步:搬运结果到GM(Global Memory)**\n",
" \n",
" 将输出数据从zLocal搬运至GM(Global Memory)上的输出z中。\n",
"\n",
"- 调用实现 \n",
" 使用内核调用符<<<>>>调用核函数。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"样例代码如下:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%writefile Sources/02.02/add/Add_kernel_call_test.cpp\n",
"\n",
"#include <cstdint>\n",
"#include <vector>\n",
"#include <algorithm>\n",
"#include <iterator>\n",
"\n",
"#include <iostream>\n",
"\n",
"#include \"acl/acl.h\"\n",
"\n",
"#include \"kernel_operator.h\"\n",
"\n",
"template <uint32_t blockLength>\n",
"__vector__ __global__ void add_custom(__gm__ float* x, __gm__ float* y, __gm__ float* z)\n",
"{\n",
" AscendC::InitSocState();\n",
"\n",
" AscendC::GlobalTensor<float> xGm, yGm, zGm;\n",
" xGm.SetGlobalBuffer(x + block_idx * blockLength, blockLength);\n",
" yGm.SetGlobalBuffer(y + block_idx * blockLength, blockLength);\n",
" zGm.SetGlobalBuffer(z + block_idx * blockLength, blockLength);\n",
"\n",
" AscendC::LocalMemAllocator<AscendC::Hardware::UB> ubAllocator;\n",
" AscendC::LocalTensor<float> xLocal = ubAllocator.Alloc<float, blockLength>();\n",
" AscendC::LocalTensor<float> yLocal = ubAllocator.Alloc<float, blockLength>();\n",
" AscendC::LocalTensor<float> zLocal = ubAllocator.Alloc<float, blockLength>();\n",
"\n",
" AscendC::DataCopy(xLocal, xGm, blockLength);\n",
" AscendC::DataCopy(yLocal, yGm, blockLength);\n",
" AscendC::PipeBarrier<PIPE_ALL>();\n",
"\n",
" AscendC::Add(zLocal, xLocal, yLocal, blockLength);\n",
" AscendC::PipeBarrier<PIPE_ALL>();\n",
"\n",
"#if 0\n",
" // Debug I/O. Set #if 1 to enable print.\n",
" AscendC::printf(\"%s\\n\", \"[ DumpTensor in xLocal]\");\n",
" AscendC::DumpTensor(xLocal, 1, 32);\n",
"\n",
" AscendC::printf(\"%s\\n\", \"[ DumpTensor in yLocal]\");\n",
" AscendC::DumpTensor(yLocal, 2, 32);\n",
"\n",
" AscendC::printf(\"%s\\n\", \"[ DumpTensor in zLocal]\");\n",
" AscendC::DumpTensor(zLocal, 3, 32);\n",
"#endif\n",
"\n",
" AscendC::DataCopy(zGm, zLocal, blockLength);\n",
" AscendC::PipeBarrier<PIPE_ALL>();\n",
"}\n",
"\n",
"std::vector<float> kernel_add(std::vector<float>& x, std::vector<float>& y)\n",
"{\n",
" constexpr uint32_t numBlocks = 8;\n",
" constexpr uint32_t blockLength = 2048;\n",
" uint32_t totalLength = x.size();\n",
" size_t totalByteSize = totalLength * sizeof(float);\n",
" int32_t deviceId = 0;\n",
" float* xDevice = nullptr;\n",
" float* yDevice = nullptr;\n",
" float* zDevice = nullptr;\n",
" uint8_t* zHost = nullptr;\n",
"\n",
" aclInit(nullptr);\n",
" aclrtSetDevice(deviceId);\n",
"\n",
" aclrtMalloc((void**)&xDevice, totalByteSize, ACL_MEM_MALLOC_HUGE_FIRST);\n",
" aclrtMalloc((void**)&yDevice, totalByteSize, ACL_MEM_MALLOC_HUGE_FIRST);\n",
" aclrtMalloc((void**)&zDevice, totalByteSize, ACL_MEM_MALLOC_HUGE_FIRST);\n",
" aclrtMallocHost((void**)&zHost, totalByteSize);\n",
"\n",
" aclrtMemcpy(xDevice, totalByteSize, x.data(), totalByteSize, ACL_MEMCPY_HOST_TO_DEVICE);\n",
" aclrtMemcpy(yDevice, totalByteSize, y.data(), totalByteSize, ACL_MEMCPY_HOST_TO_DEVICE);\n",
"\n",
" add_custom<blockLength><<<numBlocks, 0>>>(xDevice, yDevice, zDevice);\n",
" aclrtSynchronizeDevice();\n",
"\n",
" aclrtMemcpy(zHost, totalByteSize, zDevice, totalByteSize, ACL_MEMCPY_DEVICE_TO_HOST);\n",
" std::vector<float> z((float*)zHost, (float*)(zHost + totalByteSize));\n",
"\n",
" aclrtFree(xDevice);\n",
" aclrtFree(yDevice);\n",
" aclrtFree(zDevice);\n",
" aclrtFreeHost(zHost);\n",
"\n",
" aclrtResetDevice(deviceId);\n",
" aclFinalize();\n",
"\n",
" return z;\n",
"}\n",
"\n",
"uint32_t VerifyResult(std::vector<float>& output, std::vector<float>& golden)\n",
"{\n",
" auto printTensor = [](std::vector<float>& tensor, const char* name) {\n",
" constexpr size_t maxPrintSize = 20;\n",
" std::cout << name << \": \";\n",
" std::copy(\n",
" tensor.begin(), tensor.begin() + std::min(tensor.size(), maxPrintSize),\n",
" std::ostream_iterator<float>(std::cout, \" \"));\n",
" if (tensor.size() > maxPrintSize) {\n",
" std::cout << \"...\";\n",
" }\n",
" std::cout << std::endl;\n",
" };\n",
" printTensor(output, \"Output\");\n",
" printTensor(golden, \"Golden\");\n",
" if (std::equal(golden.begin(), golden.end(), output.begin())) {\n",
" std::cout << \"test pass!\" << std::endl;\n",
" return 0;\n",
" } else {\n",
" std::cout << \"test failed!\" << std::endl;\n",
" return 1;\n",
" }\n",
" return 0;\n",
"}\n",
"\n",
"int32_t main(int32_t argc, char* argv[])\n",
"{\n",
" constexpr uint32_t totalLength = 8 * 2048;\n",
" std::vector<float> x(totalLength);\n",
" std::vector<float> y(totalLength);\n",
"\n",
" for (uint32_t i = 0; i < totalLength; ++i) {\n",
" x[i] = i * 0.1f;\n",
" y[i] = i * 0.2f;\n",
" }\n",
"\n",
" std::vector<float> output = kernel_add(x, y);\n",
"\n",
" std::vector<float> golden(totalLength);\n",
" for (uint32_t i = 0; i < totalLength; ++i) {\n",
" golden[i] = x[i] + y[i];\n",
" }\n",
"\n",
" return VerifyResult(output, golden);\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"编写CMakeLists.txt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%writefile Sources/02.02/add/CMakeLists.txt\n",
"\n",
"cmake_minimum_required(VERSION 3.16)\n",
"\n",
"set(CMAKE_ASC_ARCHITECTURES \"dav-2201\" CACHE STRING \"NPU architecture: dav-2201, dav-3510\")\n",
"\n",
"find_package(ASC REQUIRED)\n",
"\n",
"project(kernel_samples LANGUAGES ASC CXX)\n",
"\n",
"add_executable(demo\n",
" Add_kernel_call_test.cpp\n",
")\n",
"\n",
"target_compile_options(demo PRIVATE\n",
" $<$<COMPILE_LANGUAGE:ASC>:--npu-arch=${CMAKE_ASC_ARCHITECTURES}>\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"样例执行:"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {},
"outputs": [],
"source": [
"# 配置环境变量\n",
"!source $ASCEND_TOOLKIT_HOME/set_env.sh"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!mkdir -p Sources/02.02/add/build # 创建并进入build目录\n",
"!cd Sources/02.02/add/build && cmake -DCMAKE_ASC_ARCHITECTURES=dav-2201 ..; make -j # 编译工程,默认npu模式\n",
"!./demo # 执行样例"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## 课后练习\n",
"\n",
"完成以下题目,检验你对算子执行流程的理解:\n",
"\n",
"1. (判断题)aclnn 单算子 API 调用需要先完成算子工程的编译部署。 \n",
"\n",
"2. (判断题)静态图模式下,算子的 Shape 在编译时是已知且明确的,动态图模式下 Shape 在编译时未知。 \n",
"\n",
"3. (单选题)aclnn 接口采用两段式调用,先调用哪个接口分配内存? \n",
" A. aclnnAdd \n",
" B. aclnnAddGetWorkspace \n",
" C. aclrtMalloc \n",
" D. aclrtMemcpy \n",
"\n",
"4. (单选题)内核调用符 <<<>>> 的语法中,threadsPerBlock 参数表示什么? \n",
" A. 线程块的个数 \n",
" B. 每个线程块内的线程数量 \n",
" C. 动态申请内存大小 \n",
" D. 流同步标识 \n",
"\n",
"5. (单选题)动态图执行流程中,按照拓扑序调用每个算子的哪些操作? \n",
" A. inferShape 和 Tiling \n",
" B. 编译和执行 \n",
" C. 数据生成和验证 \n",
" D. 内存分配和释放 \n",
"\n",
"**执行以下代码获取答案。**\n",
"\n",
"---\n",
"\n",
"上一节:[2.1 章节介绍](./02.01_chapter_intro.ipynb) | [下一节:2.3 算子目录结构](./02.03_operator_directory_structure.ipynb)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!cat ./answer/02.02_answer.txt"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}