{

    "cells": [

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "# Conv2D V2 核函数开发\n",

       "\n",

       "本章以 **Conv2D V2** 算子为例,完整演示如何在 AscendC 中直接调用卷积核函数。内容涵盖:\n",

       "\n",

       "- 算子分析:卷积数学表达式、输入/输出规格、NPU 片上存储层次。\n",

       "- 核函数开发:Tiling 结构体、片上缓冲区管理、im2col、Mmad、Fixpipe 等关键步骤。\n",

       "- 主机侧调用:SetTilingData、CheckTilingSpace、ACL 内存管理与核函数启动。\n",

       "\n",

       "本章学习大纲如下:\n",

       "\n",

       "- 算子分析:明确 Conv2D 的数学表达式、输入输出规格及 NPU 存储层次。\n",

       "- 核函数开发:逐步实现设备侧卷积核函数的各个模块。\n",

       "- 主机侧调用代码:编写 Host 侧驱动程序,完成 Tiling 填充、内存管理与核函数启动。\n",

       "- 编译与运行:使用 CMake + 毕昇编译器完成编译并执行。\n",

       "\n",

       "---\n",

       "## 1. 环境准备\n",

       "\n",

       "正式开始学习之前,先要对 Jupyter 环境进行初始化。以下代码完成了初始化并将环境变量导入 Jupyter 环境,同时创建代码目录,保证能正常使用毕昇编译器完成算子的开发及编译。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "import os, subprocess\n",

       "os.makedirs(\"Sources/01.03\", exist_ok=True)\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))\n",

       "print(\"\\n Environment initialization process completed successfully!\")"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "outputs": [],

      "source": [

        "---\n\n",

        "## 2. 算子分析\n\n",

        "### 2.1 数学表达式\n\n",

        "二维卷积(Conv2D)的数学表达式为:\n\n",

        "$$Y[n, c_o, h_o, w_o] = \\sum_{c_i} \\sum_{k_h} \\sum_{k_w} X[n,\\, c_i,\\, h_o \\cdot s_H + k_h \\cdot d_H,\\, w_o \\cdot s_W + k_w \\cdot d_W] \\times W[c_o, c_i, k_h, k_w]$$\n\n",

        "其中 $s_H, s_W$ 为步长,$d_H, d_W$ 为膨胀系数。\n\n",

        "### 2.2 输入与输出\n\n",

        

        "<table style=\"text-align: left; margin-left: 0;\">\n",

        "<tr style=\"background-color:#f0f0f0\">\n",

        "  <td align=\"center\" style=\"width: 150px\"><strong>张量</strong></td>\n",

        "  <td align=\"center\"><strong>形状(NCHW)</strong></td>\n",

        "  <td align=\"center\" style=\"width: 180px\"><strong>取值范围</strong></td>\n",

        "</tr>\n",

        "<tr>\n",

        "  <td align=\"left\">输入特征图 X </td>\n",

        "  <td align=\"left\">(BATCH, CI, HI, WI) </td>\n",

        "  <td align=\"left\">fp16</td>\n",

        "</tr>\n",

        "<tr>\n",

        "  <td align=\"left\">卷积核 W </td>\n",

        "  <td align=\"left\"> (CO, CI, KH, KW) </td>\n",

        "  <td align=\"left\">fp16</td>\n",

        "</tr>\n",

        "<tr>\n",

        "  <td align=\"left\">输出特征图 Y </td>\n",

        "  <td align=\"left\"> (BATCH, CO, HO, WO) </td>\n",

        "  <td align=\"left\">fp16</td>\n",

        "</tr>\n",

        "</table>\n",

        "输出尺寸公式:\n\n$$HO = \\lfloor (HI + pad\\_top + pad\\_bottom - d_H(KH-1) - 1) / s_H \\rfloor + 1$$\n\n",

        "### 2.3 NPU 上存储层次\n\nConv2D 的数据流经过以下存储层次(速度从快到慢):\n\n",

        "```\nGM  (Global Memory)  — 全局存储空间 (Ascend 950PR/Ascend 950DT)\n",

        "L1  (L1 缓冲区)             — 每核 512 MB\nL0A (矩阵 A 缓冲区)         — 64 KB,送入 Mmad 左操作数\nL0B (矩阵 B 缓冲区)         — 64 KB,送入 Mmad 右操作数\nL0C (累加器缓冲区)           — 256 KB,存储 Mmad 结果(fp32)\n",

        "```\n\n数据流示意:\n\n",

        "```\nGM ──DataCopy──► L1  (LoadAL1 / LoadBL1)\n",

        "L1 ──Load3D──►  L0A  (LoadAL0: im2col 在线展开)\n",

        "L1 ──Load2D──►  L0B  (LoadBL0: 权重 NZ 布局)\n",

        "L0A × L0B ──Mmad──► L0C\nL0C ──Fixpipe──► GM  (fp32→fp16 类型转换 + NCHW 布局)\n",

        "```\n\n### 2.4 本 Demo 的局限性与约束\n\n本 Demo 是一个**最小可运行实现**,**只支持Ascend 950PR/Ascend 950DT芯片型号**,目的是清晰展示 AscendC 卷积核函数的完整调用链。为了降低复杂度,做了以下简化,在实际生产代码中均需扩展:\n\n",

        "#### 全载搬运(Full-Load)\n\n这是最核心的限制。`SetTilingData` 中令 `kL0 == kAL1 == kBL1 == KH × KW × CI`,即整个 K 维度在一次 L1→L0 搬运中全部完成,`ddr2l0LoopK` 始终为 1,K 循环只跑一次。\n\n",

        "当输入通道 CI 或卷积核尺寸 KH/KW 增大时,全载所需的 L0A/L0B 空间会线性增长,很快超出 64 KB 的硬件上限。生产实现需要将 K 维度分块,分多次 Mmad 累加。\n\n",

        "#### 单核,无多核分工\n\n`numBlocks = 1`,整个输出特征图由单核完成。没有按 batch、输出通道或空间维度分核的逻辑(无 `block_idx` 分片)。\n\n",

        "#### 无流水 / 无双缓冲\n\nL1 队列深度为 1(`TQue<..., 1>`),L0 双缓冲关闭(`L0_SYBC_DB_CLOSE`)。数据搬运(MTE1)和矩阵计算(M)串行执行,没有流水重叠,硬件利用率低。\n\n",

        "#### 数据类型固定为 fp16\n\n输入特征图、权重、输出均为 `half`(fp16),L0C 累加器为 `float`(fp32)。不支持 int8、bf16 等其他数据类型。\n\n",

        "#### 数据布局固定为 NCHW\n\n`ConvFormat` 枚举只定义了 `NCHW = 1`,不支持 NHWC 等其他布局。\n\n",

        "#### CI 和 CO 均固定为 16\n\n- **CI = 16**:`aL1SpaceSize = 8192` 硬编码,只够容纳 CI=16 的一个输入 patch;`cinAInCore / cinBInCore` 也直接由 `kAL1 / kernelHW` 推算,没有多 CI 块的循环。\n",

        "- **CO = 16**:`nBL1 = 16`、`nL0 = 16` 硬编码,每次只处理 16 个输出通道,没有多 CO 块的循环。\n\n两者的根本原因相同:Demo 没有实现通道方向的分块循环,CI 和 CO 必须恰好等于 16(即一个 C0 块),而不是\"16 的倍数\"。\n\n",

        "#### 只支持对称 Padding\n\n`Conv2DTilingData` 只存储 `padTop` 和 `padLeft`,没有独立的 `padBottom` 和 `padRight` 字段。`LoadAL1` 在运行时从输入/输出尺寸的几何关系推算 bottom/right padding,隐含 `padBottom = padTop`、`padRight = padLeft`。非对称 padding 无法通过当前结构体表达。\n\n",

        "#### 空间尺寸上限(由全载搬运决定)\n\n`CheckTilingSpace` 会在启动前验证以下约束,超出则拒绝运行:\n\n",

        "<table style=\"text-align: left; margin-left: 0;\">\n",

        "<tr style=\"background-color:#f0f0f0\">\n",

        "  <td align=\"center\" style=\"width: 150px\"><strong>缓冲区</strong></td>\n",

        "  <td align=\"center\"><strong>硬件上限</strong></td>\n",

        "  <td align=\"center\" style=\"width: 300px\"><strong>全载用量公式</strong></td>\n",

        "</tr>\n",

        "<tr>\n",

        "  <td align=\"left\">L1 </td>\n",

        "  <td align=\"left\">512 MB / 核</td>\n",

        "  <td align=\"left\">`(hiLoad × wiLoad × CI + nBL1 × kBL1) × 2` 字节</td>\n",

        "</tr>\n",

        "<tr>\n",

        "  <td align=\"left\">L0A </td>\n",

        "  <td align=\"left\"> 64 KB / 核 </td>\n",

        "  <td align=\"left\"> `AlignUp(HO×WO, 16) × (KH×KW×CI) × 2` 字节 </td>\n",

        "</tr>\n",

        "<tr>\n",

        "  <td align=\"left\">L0B </td>\n",

        "  <td align=\"left\">  64 KB / 核  </td>\n",

        "  <td align=\"left\"> `AlignUp(CO, 16) × (KH×KW×CI) × 2` 字节</td>\n",

        "</tr>\n",

        "<tr>\n",

        "  <td align=\"left\">L0C </td>\n",

        "  <td align=\"left\">  256 KB / 核  </td>\n",

        "  <td align=\"left\"> `AlignUp(HO×WO, 16) × AlignUp(CO, 16) × 4` 字节 </td>\n",

        "</tr>\n",

        "</table>\n",

        "其中 `hiLoad = (HO-1)×strideH + dilatedKH`,`wiLoad = (WO-1)×strideW + dilatedKW`。\n\n> **示例验证**:本 Demo 默认参数(CI=16, HI=WI=8, KH=KW=3, CO=16, 无 padding)下,输入全 1、权重全 1,每个输出点的计算值为:\n>\n> $$Y[n, c_o, h_o, w_o] = \\sum_{c_i=0}^{15} \\sum_{k_h=0}^{2} \\sum_{k_w=0}^{2} 1 \\times 1 = CI \\times KH \\times KW = 16 \\times 3 \\times 3 = 144$$\n>\n> 输出通道 $c_o$ 不影响每个点的数值,因为所有输出通道的权重均为 1,累加的元素数量(CI × KH × KW)相同。\n\n---\n\n## 3. 核函数开发\n\n基于对 Conv2D V2 算子的分析,我们开始逐步实现核函数代码。所有代码均写入 `Sources/01.03/minimal_demo.asc`,采用增量追加方式,便于逐节理解每个模块的作用。\n\n### 3.1 头文件与 Tiling 结构体\n\n首先引入必要的头文件,并定义 `Conv2DTilingData` 结构体。\n\n**Conv2DTilingData** 保存了核函数所需的全部参数:存储层次的分块尺寸以及 padding 偏移量。它由主机侧通过 `aclrtMemcpy` 传递给设备侧,再由 `CopyConv2dTiling` 从 GM 复制到片上内存后供核函数读取。\n\n**CopyConv2dTiling / GET_TILING_DATA**:核函数不能直接解引用 GM 指针读取结构体字段,必须先将数据复制到片上(L1 可访问)内存。`CopyConv2dTiling` 逐字完成这一复制,`GET_TILING_DATA` 宏将其封装为一行调用。"

    ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile Sources/01.03/minimal_demo.asc\n",

       "/**\n",

       " * Copyright (c) 2025 Huawei Technologies Co., Ltd.\n",

       " */\n",

       "\n",

       "/*!\n",

       " * \\file conv2d_v2_demo.asc\n",

       " * \\brief Standalone demo for direct-calling Conv2D V2 kernel.\n",

       " *\n",

       " * ============================================================\n",

       " * Tutorial overview\n",

       " * ============================================================\n",

       " * This file is the host-side driver for the Conv2D V2 kernel demo.\n",

       " * It covers the full lifecycle of an AscendC kernel call:\n",

       " *\n",

       " *   1. Define conv shape constants (BATCH, CI, HI, WI, CO, KH, KW, …)\n",

       " *   2. Fill in the tiling struct (SetTilingData) — tells the kernel how\n",

       " *      to partition work across the NPU memory hierarchy.\n",

       " *   3. Validate that the tiling fits in on-chip SRAM (CheckTilingSpace).\n",

       " *   4. Allocate device memory, copy inputs, launch the kernel.\n",

       " *   5. Copy results back and dump to text files for inspection.\n",

       " *\n",

       " * NPU memory hierarchy (smallest / fastest → largest / slowest):\n",

       " *\n",

       " *   GM  (Global Memory)  — off-chip (Ascend 950PR/Ascend 950DT)\n",

       " *   L1  (on-chip SRAM)         — 512 MB per core\n",

       " *   L0A (Matrix A buffer)      — 64 KB, feeds left operand of Mmad\n",

       " *   L0B (Matrix B buffer)      — 64 KB, feeds right operand of Mmad\n",

       " *   L0C (Accumulator buffer)   — 256 KB, stores Mmad result (fp32)\n",

       " *\n",

       " * Data flow for a single Conv2D tile:\n",

       " *\n",

       " *   GM ──DataCopy──► L1  (LoadAL1 / LoadBL1)\n",

       " *   L1 ──Load3D──►  L0A  (LoadAL0: im2col on-the-fly)\n",

       " *   L1 ──Load2D──►  L0B  (LoadBL0: weight NZ layout)\n",

       " *   L0A × L0B ──Mmad──► L0C\n",

       " *   L0C ──Fixpipe──► GM  (fp32→fp16 cast + NCHW layout)\n",

       " * ============================================================\n",

       " */\n",

       "\n",

       "#include <cstdint>\n",

       "#include <cstring>\n",

       "#include <iostream>\n",

       "#include <fstream>\n",

       "#include <cmath>\n",

       "#include \"acl/acl.h\"\n",

       "#include \"kernel_operator.h\"\n",

       "\n",

       "// ============================================================================\n",

       "// Tiling data structure\n",

       "// ============================================================================\n",

       "//\n",

       "// Conv2DTilingData holds all parameters the kernel needs: memory-hierarchy\n",

       "//   tiling values plus the two padding offsets.\n",

       "//   Passed from host to device via aclrtMemcpy, then copied from GM to\n",

       "//   on-chip memory by CopyConv2dTiling before the kernel reads any field.\n",

       "//\n",

       "// CopyConv2dTiling / GET_TILING_DATA: the kernel cannot dereference a GM\n",

       "//   pointer directly to read struct fields — the data must first be copied\n",

       "//   to on-chip (L1-accessible) memory.  CopyConv2dTiling does that word-by-word.\n",

       "\n",

       "#pragma pack(1)\n",

       "\n",

       "struct Conv2DTilingData {\n",

       "    uint64_t orgHi = 0;\n",

       "    uint64_t orgWi = 0;\n",

       "    uint64_t orgHo = 0;\n",

       "    uint64_t orgWo = 0;\n",

       "    uint64_t singleCoreBatch = 0;\n",

       "    uint64_t singleCoreHo = 0;\n",

       "    uint64_t singleCoreWo = 0;\n",

       "    uint32_t orgCi = 0;\n",

       "    uint32_t orgCo = 0;\n",

       "    uint32_t singleCoreCi = 0;\n",

       "    uint32_t singleCoreCo = 0;\n",

       "    uint32_t kAL1 = 0;\n",

       "    uint32_t kBL1 = 0;\n",

       "    uint32_t nBL1 = 0;\n",

       "    uint32_t hoL0 = 0;\n",

       "    uint32_t woL0 = 0;\n",

       "    uint32_t kL0 = 0;\n",

       "    uint32_t nL0 = 0;\n",

       "    uint32_t orgHixWi = 0;\n",

       "    uint32_t kernelHxkernelW = 0;\n",

       "    uint32_t aL1SpaceSize = 0;\n",

       "    uint32_t cinAInCore = 0;\n",

       "    uint32_t cinBInCore = 0;\n",

       "    uint32_t mStep = 0;\n",

       "    uint32_t kStep = 0;\n",

       "    uint32_t fmapKStride = 0;\n",

       "    uint32_t coutOffsetBlock = 0;\n",

       "    uint32_t nL1DivBlockSize = 0;\n",

       "    uint32_t kernelH = 0;\n",

       "    uint32_t kernelW = 0;\n",

       "    uint32_t strideH = 0;\n",

       "    uint32_t strideW = 0;\n",

       "    uint32_t dilationH = 0;\n",

       "    uint32_t dilationW = 0;\n",

       "    int8_t offsetx = 0;\n",

       "    uint32_t padTop = 0;\n",

       "    uint32_t padLeft = 0;\n",

       "};\n",

       "\n",

       "#pragma pack()\n",

       "\n",

       "__aicore__ inline void CopyConv2dTiling(Conv2DTilingData* dst, GM_ADDR tilingGM)\n",

       "{\n",

       "    uint32_t* ptr = reinterpret_cast<uint32_t*>(dst);\n",

       "    auto src = reinterpret_cast<__gm__ uint32_t*>(tilingGM);\n",

       "    for (uint32_t i = 0; i < sizeof(Conv2DTilingData) / sizeof(uint32_t); i++, ptr++) {\n",

       "        *ptr = *(src + i);\n",

       "    }\n",

       "}\n",

       "\n",

       "#define GET_TILING_DATA(tilingData, tilingArg) \\\n",

       "    Conv2DTilingData tilingData;               \\\n",

       "    CopyConv2dTiling(&tilingData, tilingArg)\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 3.2 NPU存储层次与常量\n",

       "\n",

       "`#ifdef __CCE_AICORE__` 块内的代码只在设备侧编译。`namespace conv` 中定义了各个缓冲区的大小常量(L0A/L0B/L0C 均以字节为单位)、C0 块大小(32 字节)、padding 索引以及 Load3D/Load2D 所需的位域偏移量。\n",

       "\n",

       "这些常量直接对应 NPU 硬件规格,修改它们会导致越界访问,因此在教学中保持固定值。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "#ifdef __CCE_AICORE__\n",

       "#include \"kernel_basic_intf.h\"\n",

       "#include \"kernel_tiling/kernel_tiling.h\"\n",

       "\n",

       "using namespace AscendC;\n",

       "\n",

       "namespace conv {\n",

       "\n",

       "const static uint64_t L0A_SIZE = 65536;\n",

       "const static uint64_t L0B_SIZE = 65536;\n",

       "const static uint64_t L0C_SIZE = 262144;\n",

       "const static uint64_t C0_SIZE = 32;\n",

       "const static uint64_t PAD_IDX_T = 2;\n",

       "const static uint64_t PAD_IDX_L = 0;\n",

       "const static uint64_t PAD_IDX_R = 1;\n",

       "const static uint64_t MAX_PAD_R = 255;\n",

       "const static uint64_t MIN_HI_WI = 1;\n",

       "const static uint32_t BLOCK_L0_N = 16;\n",

       "const static uint32_t BLOCK_L0_M = 16;\n",

       "const static uint32_t L0_SYBC_DB_CLOSE = 0x0;\n",

       "\n",

       "const static uint8_t MSTEP_OFFSET = 16;\n",

       "const static uint8_t POSM_OFFSET = 48;\n",

       "const static uint8_t POSK_OFFSET = 32;\n",

       "const static uint8_t STRIDEH_OFFSET = 6;\n",

       "const static uint8_t KERNELW_OFFSET = 12;\n",

       "const static uint8_t KERNELH_OFFSET = 20;\n",

       "const static uint8_t KERNELW_HIGHEST_BIT_OFFSET = 36;\n",

       "const static uint8_t KERNELH_HIGHEST_BIT_OFFSET = 37;\n",

       "const static uint8_t DILATIONW_OFFSET = 28;\n",

       "const static uint8_t DILATIONH_OFFSET = 36;\n",

       "const static uint8_t CIN_OFFSET = 48;\n",

       "\n",

       "const static uint64_t MASK_16 = 0xffff;\n",

       "const static uint64_t MASK_8 = 0xff;\n",

       "const static uint64_t MASK_6 = 0x3f;\n",

       "const static uint64_t NINTH_BIT_MASK = 0x100;\n",

       "\n",

       "const static uint64_t DEQ_SCALAR_ONE = 1065353216;\n",

       "static constexpr IsResetLoad3dConfig CONV_LOAD3DV2_DEFAULT_CONFIG = {false, false};\n",

       "\n",

       "#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 5102)\n",

       "#define ASCEND_IS_AIC_CONV constexpr(true)\n",

       "#define ASCEND_IS_AIV_CONV constexpr(true)\n",

       "#else\n",

       "#define ASCEND_IS_AIC_CONV ASCEND_IS_AIC\n",

       "#define ASCEND_IS_AIV_CONV ASCEND_IS_AIV\n",

       "#endif\n",

       "\n",

       "constexpr uint8_t UNIT_FLAG_ENABLE_ONLY = 2;\n",

       "constexpr uint8_t UNIT_FLAG_ENABLE_WITH_FLIP = 3;\n",

       "\n",

       "static __aicore__ inline uint64_t AlignB(uint64_t a, uint64_t b)\n",

       "{\n",

       "    return ((a + b - 1) / b) * b;\n",

       "}\n",

       "\n",

       "static __aicore__ inline uint64_t CeilDiv(uint64_t a, uint64_t b)\n",

       "{\n",

       "    return (a + b - 1) / b;\n",

       "}\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 3.3 数据类型定义\n",

       "\n",

       "本节定义三个核心类型:\n",

       "\n",

       "- **ConvFormat**:枚举卷积数据布局,当前仅支持 NCHW。\n",

       "- **ConvType**:将存储位置(TPosition)、数据布局(ConvFormat)和元素类型(TYPE)打包为一个模板类型标签,供 `Conv2dContext` 和 `Conv2dBase` 使用。\n",

       "- **Conv2dContext**:保存单次卷积迭代所需的全部状态,包括片上缓冲区句柄(al0Buf/bl0Buf/l0cBuf)、队列(queueAL1/queueBL1)、GlobalTensor 指针以及从 Tiling 派生的循环控制变量。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "enum class ConvFormat : std::uint8_t {\n",

       "    NCHW = 1,\n",

       "};\n",

       "\n",

       "template <TPosition POSITION, ConvFormat FORMAT, typename TYPE>\n",

       "struct ConvType {\n",

       "    constexpr static TPosition pos = POSITION;\n",

       "    constexpr static ConvFormat format = FORMAT;\n",

       "    using T = TYPE;\n",

       "};\n",

       "\n",

       "template <class FMAP_TYPE, class WEIGHT_TYPE, class OUTPUT_TYPE>\n",

       "struct Conv2dContext {\n",

       "    using FmapT = typename FMAP_TYPE::T;\n",

       "    using WeightT = typename WEIGHT_TYPE::T;\n",

       "    using OutputT = typename OUTPUT_TYPE::T;\n",

       "    using L0cT = float;\n",

       "\n",

       "    constexpr static uint64_t k0 = C0_SIZE / sizeof(WeightT);\n",

       "\n",

       "    TPipe pipe;\n",

       "    GlobalTensor<FmapT> agm;\n",

       "    GlobalTensor<WeightT> bgm;\n",

       "    TBuf<TPosition::A2> al0Buf;\n",

       "    TBuf<TPosition::B2> bl0Buf;\n",

       "    TBuf<TPosition::CO1> l0cBuf;\n",

       "\n",

       "    LocalTensor<FmapT> al1;\n",

       "    LocalTensor<WeightT> bl1;\n",

       "    LocalTensor<FmapT> wholeAl0Tensor;\n",

       "    LocalTensor<FmapT> al0;\n",

       "    LocalTensor<WeightT> wholeBl0Tensor;\n",

       "    LocalTensor<WeightT> bl0;\n",

       "    LocalTensor<L0cT> wholeCl0Tensor = LocalTensor<L0cT>(TPosition::CO1, 0, 0);\n",

       "    LocalTensor<L0cT> cl0 = LocalTensor<L0cT>(TPosition::CO1, 0, 0);\n",

       "\n",

       "    TQue<QuePosition::A1, 1> queueAL1;\n",

       "    TQue<QuePosition::B1, 1> queueBL1;\n",

       "\n",

       "    const Conv2DTilingData* __restrict convTiling = nullptr;\n",

       "\n",

       "    uint64_t fmapOneBatchSize = 0;\n",

       "    uint64_t outputOneBatchSize = 0;\n",

       "    uint64_t dilatedKernelH = 0;\n",

       "    uint64_t dilatedKernelW = 0;\n",

       "    uint64_t orgCi = 0;\n",

       "    uint64_t orgCo = 0;\n",

       "    uint64_t orgHi = 0;\n",

       "    uint64_t orgWi = 0;\n",

       "    uint64_t orgHo = 0;\n",

       "    uint64_t orgWo = 0;\n",

       "    uint64_t kernelH = 0;\n",

       "    uint64_t kernelW = 0;\n",

       "    int64_t hiStartPos = -1;\n",

       "    int64_t wiStartPos = 0;\n",

       "    uint64_t singleCoreBatch = 0;\n",

       "    uint64_t singleCoreHo = 0;\n",

       "    uint64_t singleCoreWo = 0;\n",

       "    uint64_t singleCoreCi = 0;\n",

       "    uint64_t singleCoreCo = 0;\n",

       "\n",

       "    uint64_t currentHoL0 = 0;\n",

       "    uint64_t currentWoL0 = 0;\n",

       "    uint64_t currentNBL1 = 0;\n",

       "    uint64_t currentML0Align = 0;\n",

       "    uint64_t currentNL0Align = 0;\n",

       "\n",

       "    uint64_t ddr2l0LoopK = 0;\n",

       "    uint64_t maxKL0Iter = 0;\n",

       "    uint64_t kL0Tail = 0;\n",

       "    uint64_t kIter = 0;\n",

       "    uint64_t kAL0Iter = 0;\n",

       "    uint64_t kBL0Iter = 0;\n",

       "\n",

       "    uint32_t bL1SpaceSize = 0;\n",

       "};\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 3.4 上下文初始化 InitContext\n",

       "\n",

       "`InitContext` 从 Tiling 结构体中读取所有形状参数,初始化片上缓冲区(L0A/L0B/L0C)和 L1 队列,并预计算 K 维度的循环次数(`ddr2l0LoopK`)及尾块大小(`kL0Tail`)。\n",

       "\n",

       "关键逻辑:\n",

       "- `dilatedKernelH/W`:考虑膨胀后的有效卷积核尺寸。\n",

       "- `ddr2l0LoopK = CeilDiv(totalK, kL0)`:K 维度需要多少次 L0 迭代。\n",

       "- `kL0Tail`:最后一次迭代的 K 块大小(若整除则等于 kL0)。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "template <class Ctx>\n",

       "__aicore__ inline void InitContext(Ctx &ctx, const Conv2DTilingData *tiling)\n",

       "{\n",

       "    ctx.convTiling = tiling;\n",

       "    ctx.singleCoreBatch = tiling->singleCoreBatch;\n",

       "\n",

       "    if ASCEND_IS_AIC_CONV {\n",

       "        ctx.orgCi = tiling->orgCi;\n",

       "        ctx.orgHi = tiling->orgHi;\n",

       "        ctx.orgWi = tiling->orgWi;\n",

       "        ctx.orgCo = tiling->orgCo;\n",

       "        ctx.orgHo = tiling->orgHo;\n",

       "        ctx.orgWo = tiling->orgWo;\n",

       "        ctx.kernelH = tiling->kernelH;\n",

       "        ctx.kernelW = tiling->kernelW;\n",

       "        ctx.dilatedKernelH = 1 + (ctx.kernelH - 1) * tiling->dilationH;\n",

       "        ctx.dilatedKernelW = 1 + (ctx.kernelW - 1) * tiling->dilationW;\n",

       "        ctx.singleCoreCi = tiling->singleCoreCi;\n",

       "        ctx.singleCoreCo = tiling->singleCoreCo;\n",

       "        ctx.fmapOneBatchSize = tiling->orgCi * tiling->orgHi * tiling->orgWi;\n",

       "        ctx.outputOneBatchSize = tiling->orgCo * tiling->orgHo * tiling->orgWo;\n",

       "\n",

       "        ctx.singleCoreHo = tiling->singleCoreHo;\n",

       "        ctx.singleCoreWo = tiling->singleCoreWo;\n",

       "        ctx.currentHoL0 = ctx.singleCoreHo;\n",

       "        ctx.currentWoL0 = ctx.singleCoreWo;\n",

       "        ctx.currentNBL1 = tiling->nBL1;\n",

       "\n",

       "        ctx.pipe.InitBuffer(ctx.al0Buf, L0A_SIZE);\n",

       "        ctx.pipe.InitBuffer(ctx.bl0Buf, L0B_SIZE);\n",

       "        ctx.pipe.InitBuffer(ctx.l0cBuf, L0C_SIZE);\n",

       "        ctx.wholeAl0Tensor = ctx.al0Buf.template Get<typename Ctx::FmapT>();\n",

       "        ctx.wholeBl0Tensor = ctx.bl0Buf.template Get<typename Ctx::WeightT>();\n",

       "        ctx.wholeCl0Tensor = ctx.l0cBuf.template Get<typename Ctx::L0cT>();\n",

       "        ctx.bL1SpaceSize = tiling->nBL1 * tiling->kBL1;\n",

       "        ctx.pipe.InitBuffer(ctx.queueAL1, 1, tiling->aL1SpaceSize);\n",

       "        ctx.pipe.InitBuffer(ctx.queueBL1, 1, ctx.bL1SpaceSize * sizeof(typename Ctx::WeightT));\n",

       "\n",

       "        uint64_t totalK = AlignB(ctx.singleCoreCi, Ctx::k0) * tiling->kernelHxkernelW;\n",

       "        ctx.ddr2l0LoopK = CeilDiv(totalK, tiling->kL0);\n",

       "        ctx.maxKL0Iter = ctx.ddr2l0LoopK - 1;\n",

       "        ctx.kL0Tail = totalK % tiling->kL0;\n",

       "        ctx.kL0Tail = ctx.kL0Tail == 0 ? tiling->kL0 : ctx.kL0Tail;\n",

       "\n",

       "        ctx.currentML0Align = tiling->mStep;\n",

       "        ctx.currentNL0Align = tiling->nL0;\n",

       "    }\n",

       "}\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 3.5 数据加载:LoadAL1 / LoadBL1\n",

       "\n",

       "**LoadAL1**:将特征图的一个输入 patch 从 GM 搬运到 L1(`queueAL1`)。\n",

       "\n",

       "核心步骤:\n",

       "1. 根据当前输出 tile 的位置(`hiStartPos`/`wiStartPos`)计算需要加载的输入行列范围,并推导出四个方向的 padding 量。\n",

       "2. 若整个 patch 都落在 padding 区域(`allPad`),则用 `InitConstValue` 填零,避免越界访问。\n",

       "3. 否则,使用 `Dn2NzParams` 描述 NZ 布局的搬运参数,调用 `DataCopy` 完成实际数据搬运。\n",

       "\n",

       "**LoadBL1**:将权重从 GM 搬运到 L1(`queueBL1`)。\n",

       "\n",

       "- 1×1 卷积(`kernelHxkernelW == 1`)使用 `Nd2NzParams`(行优先到 NZ 的转换)。\n",

       "- 非 1×1 卷积使用 `Dn2NzParams`,按 `coutOffsetBlock` 步长遍历输出通道块。\n",

       "\n",

       "#### DataCopy详解\n",

       "\n",

       "**功能定位**:DataCopy是通用数据搬运接口,支持多种数据搬运场景,并可在搬运过程中实现随路格式转换和量化激活等操作。该接口支持Local Memory与Global Memory之间的数据搬运,以及Local Memory内部的数据搬运,不涉及img2col变换。\n",

       "\n",

       "**核心参数**:\n",

       "\n",

       "```cpp\n",

       "\n",

       "DataCopyParams params;\n",

       "params.blockCount = blockNum;       // 搬运块数\n",

       "params.blockLen = dataLength;       // 每块数据长度\n",

       "params.srcStride = srcStride;       // 源步长\n",

       "params.dstStride = dstStride;       // 目的步长\n",

       "// 带Padding的搬运\n",

       "DataCopyPadParams padParams;\n",

       "padParams.isPad = true;\n",

       "padParams.leftPadding = leftPad;\n",

       "padParams.rightPadding = rightPad;\n",

       "\n",

       "```\n",

       "\n",

       "**代码示例**:\n",

       "\n",

       "```cpp\n",

       "\n",

       "DataCopyParams dataCopyParams;\n",

       "dataCopyParams.blockCount = cin1LoadL1;\n",

       "dataCopyParams.blockLen = hiLoadL1 * orgWi;\n",

       "dataCopyParams.srcStride = orgHixWi - blockLen;\n",

       "DataCopy<FmapT>(al1[offset], agm[gmOffset], dataCopyParams);\n",

       "// 带Padding搬运\n",

       "DataCopyPadParams padParams(true, 0, rightPadding, 0);\n",

       "DataCopyPad<ChannelWiseT>(tensorL1, tensorGm[offset], dataCopyParams, padParams);\n",

       "\n",

       "```\n",

       "\n",

       "**格式转换的必要性**:\n",

       "\n",

       "- **硬件适配**:Cube单元要求输入为分形矩阵格式(如NZ格式),而非NCHW/NHWC的连续存储格式\n",

       "- **性能优化**:分形格式能够更好地利用矩阵乘法单元的计算能力,提高内存访问效率\n",

       "- **计算流水**:格式转换与数据搬运融合,减少额外的转换开销\n",

       "\n",

       "**常用格式转换参数结构体**:\n",

       "在卷积算子实现中,DataCopy不仅完成数据搬运,还承担着格式转换的关键角色。昇腾AI处理器的Cube单元要求输入数据为特定的分形格式(Fractal Format),而主流框架(如PyTorch、TensorFlow)通常采用NCHW或NHWC格式。因此,需要在数据从GM搬运至L1的过程中完成格式转换,以适配硬件计算单元的输入要求。为支持不同格式的数据转换,Ascend C提供了专门的参数结构体。以下介绍两种核心格式转换参数:Dn2NzParams和Nd2NzParams。\n",

       "\n",

       "#### Dn2NzParams详解\n",

       "\n",

       "**功能**:将NCHW格式转换为NZ格式。\n",

       "\n",

       "**Dn2NzParams结构体参数定义**:\n",

       "\n",

       "<table style=\"text-align: left; margin-left: 0;\">\n",

       "<tr style=\"background-color:#f0f0f0\">\n",

       "  <td align=\"center\" style=\"width: 150px\"><strong>参数名称</strong></td>\n",

       "  <td align=\"center\"><strong>含义</strong></td>\n",

       "  <td align=\"center\" style=\"width: 180px\"><strong>取值范围</strong></td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dnNum</td>\n",

       "  <td align=\"left\">传输DN矩阵的数目。</td>\n",

       "  <td align=\"left\">[0, 4095]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">nValue</td>\n",

       "  <td align=\"left\">DN矩阵的行数(矩阵高度)。</td>\n",

       "  <td align=\"left\">[0, 16384]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dValue</td>\n",

       "  <td align=\"left\">DN矩阵的列数(矩阵宽度)。当dValue不满足32B对齐时,目的操作数中不足部分会被补齐为0。</td>\n",

       "  <td align=\"left\">[0, 2^32-1]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">srcDnMatrixStride</td>\n",

       "  <td align=\"left\">源操作数相邻DN矩阵起始地址间的偏移,单位:元素。</td>\n",

       "  <td align=\"left\">[0, 2^64-1]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">srcDValue</td>\n",

       "  <td align=\"left\">源操作数同一DN矩阵的相邻行起始地址间的偏移,单位:元素。</td>\n",

       "  <td align=\"left\">[1, 2^64-1]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dstNzC0Stride</td>\n",

       "  <td align=\"left\">DN转换到NZ格式后,源操作数中的一列会转换为目的操作数的多行。dstNzC0Stride表示目的NZ矩阵中,来自源操作数同一列的多行数据相邻行起始地址间的偏移,单位:C0_SIZE(32B)。</td>\n",

       "  <td align=\"left\">[1, 65535]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dstNzNStride</td>\n",

       "  <td align=\"left\">目的NZ矩阵中,Z型矩阵相邻行起始地址之间的偏移,单位:C0_SIZE(32B)。</td>\n",

       "  <td align=\"left\">[1, 65535]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dstNzMatrixStride</td>\n",

       "  <td align=\"left\">目的NZ矩阵中,相邻NZ矩阵起始地址间的偏移,单位:元素。</td>\n",

       "  <td align=\"left\">[1, 2^32-1]</td>\n",

       "</tr>\n",

       "</table>\n",

       "\n",

       "#### Nd2NzParams详解\n",

       "\n",

       "**功能**:将NHWC格式转换为NZ格式。\n",

       "\n",

       "**Nd2NzParams结构体参数定义**:\n",

       "\n",

       "<table style=\"text-align: left; margin-left: 0;\">\n",

       "<tr style=\"background-color:#f0f0f0\">\n",

       "  <td align=\"center\" style=\"width: 150px\"><strong>参数名称</strong></td>\n",

       "  <td align=\"center\"><strong>含义</strong></td>\n",

       "  <td align=\"center\" style=\"width: 180px\"><strong>取值范围</strong></td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">ndNum</td>\n",

       "  <td align=\"left\">传输ND矩阵的数目。</td>\n",

       "  <td align=\"left\">[0, 4095]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">nValue</td>\n",

       "  <td align=\"left\">ND矩阵的行数(矩阵高度)。</td>\n",

       "  <td align=\"left\">[0, 16384]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dValue</td>\n",

       "  <td align=\"left\">ND矩阵的列数(矩阵宽度)。当dValue不满足32B对齐时,目的操作数中不足部分会被补齐为0。</td>\n",

       "  <td align=\"left\">[0, 65535]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">srcNdMatrixStride</td>\n",

       "  <td align=\"left\">源操作数相邻ND矩阵起始地址间的偏移,单位:元素。</td>\n",

       "  <td align=\"left\">[0, 65535]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">srcDValue</td>\n",

       "  <td align=\"left\">源操作数同一ND矩阵的相邻行起始地址间的偏移,单位:元素。</td>\n",

       "  <td align=\"left\">[1, 65535]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dstNzC0Stride</td>\n",

       "  <td align=\"left\">ND转换到NZ格式后,源操作数中的一行会转换为目的操作数的多行。dstNzC0Stride表示目的NZ矩阵中,来自源操作数同一行的多行数据相邻行起始地址间的偏移,单位:C0_SIZE(32B)。</td>\n",

       "  <td align=\"left\">[1, 16384]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dstNzNStride</td>\n",

       "  <td align=\"left\">目的NZ矩阵中,Z型矩阵相邻行起始地址之间的偏移,单位:C0_SIZE(32B)。</td>\n",

       "  <td align=\"left\">[1, 16384]</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dstNzMatrixStride</td>\n",

       "  <td align=\"left\">目的NZ矩阵中,相邻NZ矩阵起始地址间的偏移,单位:元素。</td>\n",

       "  <td align=\"left\">[1, 65535]</td>\n",

       "</tr>\n",

       "</table>\n",

       "\n",

       "详细的ND2NZ转换示意图请参考[昇腾社区官方文档](https://www.hiascend.com/document/detail/zh/canncommercial/850/API/ascendcopapi/atlasascendc_api_07_00127.html)中的\"图1 ND2NZ转换示意图(half数据类型)\"部分。该图清晰地展示了从ND格式到NZ格式的转换过程,包括源操作数和目的操作数的数据布局、各参数对应的偏移关系以及DataBlock的对齐方式。\n",

       "\n",

       "**约束说明**:\n",

       "\n",

       "- 针对Atlas 推理系列产品AI Core,使用Global Memory → Local Memory通路的ND2NZ搬运接口时,需要预留8K的UB空间,作为接口的临时数据存放区。\n",

       "\n",

       "#### 格式转换对比\n",

       "\n",

       "<table style=\"text-align: left; margin-left: 0;\">\n",

       "<tr style=\"background-color:#f0f0f0\">\n",

       "  <td align=\"center\"><strong>参数类型</strong></td>\n",

       "  <td align=\"center\"><strong>源格式</strong></td>\n",

       "  <td align=\"center\"><strong>目标格式</strong></td>\n",

       "  <td align=\"center\"><strong>适用框架</strong></td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">Dn2NzParams</td>\n",

       "  <td align=\"left\">NCHW</td>\n",

       "  <td align=\"left\">NZ</td>\n",

       "  <td align=\"left\">PyTorch、ONNX、Caffe</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">Nd2NzParams</td>\n",

       "  <td align=\"left\">NHWC</td>\n",

       "  <td align=\"left\">NZ</td>\n",

       "  <td align=\"left\">TensorFlow、推理引擎</td>\n",

       "</tr>\n",

       "</table>\n",

       "\n"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "template <class Ctx>\n",

       "__aicore__ inline void LoadAL1(Ctx &ctx)\n",

       "{\n",

       "    ctx.al1 = ctx.queueAL1.template AllocTensor<typename Ctx::FmapT>();\n",

       "\n",

       "    const auto *t = ctx.convTiling;\n",

       "    int64_t paddingHi = (ctx.singleCoreHo - 1) * t->strideH + ctx.dilatedKernelH;\n",

       "    int64_t paddingWi = (ctx.singleCoreWo - 1) * t->strideW + ctx.dilatedKernelW;\n",

       "\n",

       "    int64_t hiTop = ctx.hiStartPos > 0 ? 0 : ctx.hiStartPos;\n",

       "    int64_t wiLeft = ctx.wiStartPos > 0 ? 0 : ctx.wiStartPos;\n",

       "    int64_t hiBottom = hiTop + paddingHi;\n",

       "    int64_t wiRight = wiLeft + paddingWi;\n",

       "\n",

       "    uint64_t padTopL1 = hiTop < 0 ? uint64_t(-hiTop) : 0;\n",

       "    hiBottom = ctx.hiStartPos > 0 ? hiBottom + ctx.hiStartPos : hiBottom;\n",

       "    uint64_t padBottomL1 = hiBottom > (int64_t)ctx.orgHi ? uint64_t(hiBottom - ctx.orgHi) : 0;\n",

       "    uint64_t padLeftL1 = wiLeft < 0 ? uint64_t(-wiLeft) : 0;\n",

       "    wiRight = ctx.wiStartPos > 0 ? wiRight + ctx.wiStartPos : wiRight;\n",

       "    uint64_t padRightL1 = wiRight > (int64_t)ctx.orgWi ? uint64_t(wiRight - ctx.orgWi) : 0;\n",

       "\n",

       "    bool allPad = (padTopL1 >= (uint64_t)paddingHi || padBottomL1 >= (uint64_t)paddingHi ||\n",

       "                   padLeftL1 >= (uint64_t)paddingWi || padRightL1 >= (uint64_t)paddingWi);\n",

       "    uint64_t hiLoad = 0, wiLoad = 0;\n",

       "    if (!allPad) {\n",

       "        hiLoad = paddingHi - padTopL1 - padBottomL1;\n",

       "        hiLoad = hiLoad > ctx.orgHi ? ctx.orgHi : hiLoad;\n",

       "        wiLoad = paddingWi - padLeftL1 - padRightL1;\n",

       "    }\n",

       "\n",

       "    uint8_t padList[PAD_SIZE] = {MAX_PAD_R, MAX_PAD_R, MAX_PAD_R, MAX_PAD_R};\n",

       "    if (unlikely(allPad)) {\n",

       "        Load3DSetFMatrixCal(MIN_HI_WI, MIN_HI_WI, padList);\n",

       "    } else {\n",

       "        padList[PAD_IDX_L] = padLeftL1;\n",

       "        padList[PAD_IDX_R] = padRightL1;\n",

       "        padList[PAD_IDX_T] = padTopL1;\n",

       "        Load3DSetFMatrixCal(hiLoad, wiLoad, padList);\n",

       "    }\n",

       "    Load3DSetPaddingCal(t->offsetx);\n",

       "\n",

       "    if (allPad) {\n",

       "        InitConstValueParams<typename Ctx::FmapT> params(\n",

       "            1, static_cast<uint16_t>(t->aL1SpaceSize / C0_SIZE), 0, 0);\n",

       "        InitConstValue<typename Ctx::FmapT>(ctx.al1, params);\n",

       "        ctx.queueAL1.EnQue(ctx.al1);\n",

       "        return;\n",

       "    }\n",

       "\n",

       "    int64_t realHiTopGm = hiTop < 0 ? 0 : hiTop;\n",

       "    int64_t realWiTopGm = wiLeft < 0 ? 0 : wiLeft;\n",

       "    int64_t aL1GmOffset = realHiTopGm * ctx.orgWi + realWiTopGm;\n",

       "\n",

       "    Dn2NzParams params;\n",

       "    if (likely(wiLoad == ctx.orgWi)) {\n",

       "        params.dnNum = 1;\n",

       "        params.nValue = hiLoad * wiLoad;\n",

       "        params.srcDnMatrixStride = 0;\n",

       "        params.dstNzMatrixStride = 0;\n",

       "    } else {\n",

       "        params.dnNum = hiLoad;\n",

       "        params.nValue = wiLoad;\n",

       "        params.srcDnMatrixStride = ctx.orgWi;\n",

       "        params.dstNzMatrixStride = wiLoad * Ctx::k0;\n",

       "    }\n",

       "    params.dValue = t->cinAInCore;\n",

       "    params.srcDValue = t->orgHixWi;\n",

       "    params.dstNzC0Stride = hiLoad * wiLoad;\n",

       "    params.dstNzNStride = 1;\n",

       "    DataCopy<typename Ctx::FmapT>(ctx.al1, ctx.agm[aL1GmOffset], params);\n",

       "    ctx.queueAL1.EnQue(ctx.al1);\n",

       "}\n",

       "\n",

       "template <class Ctx>\n",

       "__aicore__ inline void LoadBL1(Ctx &ctx)\n",

       "{\n",

       "    ctx.bl1 = ctx.queueBL1.template AllocTensor<typename Ctx::WeightT>();\n",

       "\n",

       "    const auto *t = ctx.convTiling;\n",

       "    bool oneByOne = (t->kernelHxkernelW == 1);\n",

       "    if (oneByOne) {\n",

       "        Nd2NzParams p;\n",

       "        p.ndNum = 1;\n",

       "        p.nValue = ctx.currentNBL1;\n",

       "        p.dValue = t->kBL1;\n",

       "        p.srcNdMatrixStride = 0;\n",

       "        p.srcDValue = ctx.orgCi;\n",

       "        p.dstNzC0Stride = t->nBL1;\n",

       "        p.dstNzNStride = 1;\n",

       "        p.dstNzMatrixStride = 0;\n",

       "        DataCopy<typename Ctx::WeightT>(ctx.bl1, ctx.bgm[0], p);\n",

       "    } else {\n",

       "        Dn2NzParams p;\n",

       "        p.dnNum = ctx.currentNBL1;\n",

       "        p.nValue = t->kernelHxkernelW;\n",

       "        p.dValue = t->cinBInCore;\n",

       "        p.srcDnMatrixStride = t->coutOffsetBlock;\n",

       "        p.srcDValue = t->kernelHxkernelW;\n",

       "        p.dstNzC0Stride = t->kernelHxkernelW * t->nBL1;\n",

       "        p.dstNzNStride = t->nBL1;\n",

       "        p.dstNzMatrixStride = Ctx::k0;\n",

       "        DataCopy<typename Ctx::WeightT>(ctx.bl1, ctx.bgm[0], p);\n",

       "    }\n",

       "    ctx.queueBL1.EnQue(ctx.bl1);\n",

       "}\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 3.6 im2col:LoadAL0 / LoadBL0\n",

       "\n",

       "**LoadAL0**:将 L1 中的特征图 patch 通过 `Load3D` 指令在线展开为 im2col 矩阵,写入 L0A。\n",

       "\n",

       "`Load3D` 是 NPU 的专用硬件指令,能在数据搬运的同时完成 im2col 展开,无需额外的软件循环。第一次调用(`isFirst == true`)需要设置完整的卷积参数(步长、卷积核尺寸、膨胀系数、通道数),后续迭代只需更新 K 偏移量。\n",

       "\n",

       "#### Load3D详解\n",

       "\n",

       "**功能说明**:Load3D用于完成image to column操作,将多维feature map转为二维矩阵。支持如下数据通路:A1→A2; B1→B2。\n",

       "\n",

       "**Load3Dv2接口**:\n",

       "\n",

       "```cpp\n",

       "\n",

       "template <typename T, \n",

       "          const IsResetLoad3dConfig &defaultConfig = IS_RESER_LOAD3D_DEFAULT_CONFIG,\n",

       "          typename U = PrimT<T>,\n",

       "          typename Std::enable_if<Std::is_same<PrimT<T>, U>::value, bool>::type = true>\n",

       "__aicore__ inline void LoadData(const LocalTensor<T>& dst, \n",

       "                                const LocalTensor<T>& src, \n",

       "                                const LoadData3DParamsV2<U>& loadDataParams)\n",

       "\n",

       "```\n",

       "**参数说明**:\n",

       "\n",

       "<table style=\"text-align: left; margin-left: 0;\">\n",

       "<tr style=\"background-color:#f0f0f0\">\n",

       "  <td align=\"center\"><strong>参数名称</strong></td>\n",

       "  <td align=\"center\"><strong>输入/输出</strong></td>\n",

       "  <td align=\"center\"><strong>含义</strong></td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dst</td>\n",

       "  <td align=\"left\">输出</td>\n",

       "  <td align=\"left\">目的操作数(LocalTensor)</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">src</td>\n",

       "  <td align=\"left\">输入</td>\n",

       "  <td align=\"left\">源操作数(LocalTensor/GlobalTensor),类型需与dst保持一致</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">loadDataParams</td>\n",

       "  <td align=\"left\">输入</td>\n",

       "  <td align=\"left\">LoadData参数结构体(LoadData3DParamsV2)</td>\n",

       "</tr>\n",

       "</table>\n",

       "\n",

       "**LoadData3DParamsV2 结构体参数**:\n",

       "\n",

       "<table style=\"text-align: left; margin-left: 0;\">\n",

       "<tr style=\"background-color:#f0f0f0\">\n",

       "  <td align=\"center\" style=\"width: 150px\"><strong>参数名称</strong></td>\n",

       "  <td align=\"center\"><strong>含义</strong></td>\n",

       "  <td align=\"center\" style=\"width: 180px\"><strong>取值范围</strong></td>\n",

       "  <td align=\"center\" style=\"width: 80px\"><strong>默认值</strong></td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">padList</td>\n",

       "  <td align=\"left\">padding列表</td>\n",

       "  <td align=\"left\">[0,255]</td>\n",

       "  <td align=\"left\">{0,0,0,0}</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">l1H</td>\n",

       "  <td align=\"left\">源操作数height</td>\n",

       "  <td align=\"left\">[1, 32767]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">l1W</td>\n",

       "  <td align=\"left\">源操作数width</td>\n",

       "  <td align=\"left\">[1, 32767]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">channelSize</td>\n",

       "  <td align=\"left\">源操作数的通道数</td>\n",

       "  <td align=\"left\">[1, 63]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">kExtension</td>\n",

       "  <td align=\"left\">目的操作数width维度传输长度</td>\n",

       "  <td align=\"left\">[1, 65535]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">mExtension</td>\n",

       "  <td align=\"left\">目的操作数height维度传输长度</td>\n",

       "  <td align=\"left\">[1, 65535]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">kStartPt</td>\n",

       "  <td align=\"left\">目的操作数width维度起点</td>\n",

       "  <td align=\"left\">[0, 65535]</td>\n",

       "  <td align=\"left\">0</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">mStartPt</td>\n",

       "  <td align=\"left\">目的操作数height维度起点</td>\n",

       "  <td align=\"left\">[0, 65535]</td>\n",

       "  <td align=\"left\">0</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">strideW</td>\n",

       "  <td align=\"left\">卷积核w维度滑动步长</td>\n",

       "  <td align=\"left\">[1, 63]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">strideH</td>\n",

       "  <td align=\"left\">卷积核h维度滑动步长</td>\n",

       "  <td align=\"left\">[1, 63]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">filterW</td>\n",

       "  <td align=\"left\">卷积核width</td>\n",

       "  <td align=\"left\">[1, 255]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">filterH</td>\n",

       "  <td align=\"left\">卷积核height</td>\n",

       "  <td align=\"left\">[1, 255]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dilationFilterW</td>\n",

       "  <td align=\"left\">卷积核width膨胀系数</td>\n",

       "  <td align=\"left\">[1, 255]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dilationFilterH</td>\n",

       "  <td align=\"left\">卷积核height膨胀系数</td>\n",

       "  <td align=\"left\">[1, 255]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">enTranspose</td>\n",

       "  <td align=\"left\">是否启用转置功能(仅A2位置,half类型有效)</td>\n",

       "  <td align=\"left\">true/false</td>\n",

       "  <td align=\"left\">false</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">enSmallK</td>\n",

       "  <td align=\"left\">是否使能small k特性(已不再支持)</td>\n",

       "  <td align=\"left\">true/false</td>\n",

       "  <td align=\"left\">false</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">padValue</td>\n",

       "  <td align=\"left\">Pad填充值数值</td>\n",

       "  <td align=\"left\">与src数据类型一致</td>\n",

       "  <td align=\"left\">0</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">filterSizeW</td>\n",

       "  <td align=\"left\">是否在filterW基础上增加256个元素</td>\n",

       "  <td align=\"left\">true/false</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">filterSizeH</td>\n",

       "  <td align=\"left\">是否在filterH基础上增加256个元素</td>\n",

       "  <td align=\"left\">true/false</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">fMatrixCtrl</td>\n",

       "  <td align=\"left\">从左矩阵还是右矩阵获取FeatureMap属性描述(当前只支持false)</td>\n",

       "  <td align=\"left\">true/false</td>\n",

       "  <td align=\"left\">false</td>\n",

       "</tr>\n",

       "</table>\n",

       "\n",

       "**LoadBL0**:将 L1 中的权重通过 `Load2D` 指令搬运到 L0B,转换为 NZ 布局。\n",

       "\n",

       "第一次调用需要设置 N/K 步长和源矩阵步长,后续迭代只需更新 K 起始位置。\n",

       "\n",

       "#### Load2D详解\n",

       "\n",

       "**功能定位**:Load2D用于将权重数据从L1或GM搬运至L0B,支持矩阵转置和分块搬运,适配Cube单元的输入格式要求。\n",

       "\n",

       "**Load2D接口**:\n",

       "\n",

       "```cpp\n",

       "\n",

       "template <typename T>\n",

       "__aicore__ inline void LoadData(const LocalTensor<T>& dst, const LocalTensor<T>& src, const LoadData2DParams& loadDataParams)\n",

       "\n",

       "```\n",

       "\n",

       "**通用参数说明**:\n",

       "\n",

       "<table style=\"text-align: left; margin-left: 0;\">\n",

       "<tr style=\"background-color:#f0f0f0\">\n",

       "  <td align=\"center\" style=\"width: 150px\"><strong>参数名称</strong></td>\n",

       "  <td align=\"center\" style=\"width: 100px\"><strong>输入/输出</strong></td>\n",

       "  <td align=\"center\"><strong>含义</strong></td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dst</td>\n",

       "  <td align=\"left\">输出</td>\n",

       "  <td align=\"left\">目的操作数,类型为LocalTensor。数据连续排列顺序由目的操作数所在TPosition决定:<br>- A2:ZZ格式,分形大小为16 * (32B / sizeof(T))<br>- B2:ZN格式,分形大小为(32B / sizeof(T)) * 16<br>- A1/B1:无格式要求,一般为NZ格式,分形大小为16 * (32B / sizeof(T))</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">src</td>\n",

       "  <td align=\"left\">输入</td>\n",

       "  <td align=\"left\">源操作数,类型为LocalTensor或GlobalTensor。数据类型需与dst保持一致。</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">loadDataParams</td>\n",

       "  <td align=\"left\">输入</td>\n",

       "  <td align=\"left\">LoadData参数结构体,类型为LoadData2DParams。</td>\n",

       "</tr>\n",

       "</table>\n",

       "\n",

       "**LoadData2DParams结构体内参数说明**:\n",

       "\n",

       "<table style=\"text-align: left; margin-left: 0;\">\n",

       "<tr style=\"background-color:#f0f0f0\">\n",

       "  <td align=\"center\" style=\"width: 150px\"><strong>参数名称</strong></td>\n",

       "  <td align=\"center\"><strong>含义</strong></td>\n",

       "  <td align=\"center\" style=\"width: 180px\"><strong>取值范围</strong></td>\n",

       "  <td align=\"center\" style=\"width: 80px\"><strong>默认值</strong></td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">startIndex</td>\n",

       "  <td align=\"left\">分形矩阵ID,说明搬运起始位置为源操作数中第几个分形(0为第1个分形矩阵)。单位:512B。</td>\n",

       "  <td align=\"left\">[0, 65535]</td>\n",

       "  <td align=\"left\">0</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">repeatTimes</td>\n",

       "  <td align=\"left\">迭代次数,每个迭代可以处理512B数据。</td>\n",

       "  <td align=\"left\">[1, 255]</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">srcStride</td>\n",

       "  <td align=\"left\">相邻迭代间,源操作数前一个分形与后一个分形起始地址的间隔。单位:512B。</td>\n",

       "  <td align=\"left\">[0, 65535]</td>\n",

       "  <td align=\"left\">0</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">sid</td>\n",

       "  <td align=\"left\">预留参数,配置为0即可。</td>\n",

       "  <td align=\"left\">-</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">dstGap</td>\n",

       "  <td align=\"left\">相邻迭代间,目的操作数前一个分形结束地址与后一个分形起始地址的间隔。单位:512B。<br><strong>注</strong>:Atlas 训练系列产品此参数不使能。</td>\n",

       "  <td align=\"left\">[0, 65535]</td>\n",

       "  <td align=\"left\">0</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">ifTranspose</td>\n",

       "  <td align=\"left\">是否启用转置功能,对每个分形矩阵进行转置。<br><strong>注意</strong>:只有A1->A2和B1->B2通路才能使能转置,使能转置时仅支持uint16_t/int16_t/half数据类型。</td>\n",

       "  <td align=\"left\">true/false</td>\n",

       "  <td align=\"left\">false</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">addrMode</td>\n",

       "  <td align=\"left\">预留参数,配置为0即可。</td>\n",

       "  <td align=\"left\">-</td>\n",

       "  <td align=\"left\">-</td>\n",

       "</tr>\n",

       "</table>\n",

       "\n",

       "**约束说明**:\n",

       "\n",

       "- 操作数地址对齐要求请参见通用地址对齐约束。\n",

       "- repeatTimes最大值为255,当搬运数据量超过255块时需分批搬运。\n",

       "\n",

       "**repeatTimes限制处理方案**:\n",

       "\n",

       "当搬运数据量超过255块时,需要分批搬运:\n",

       "\n",

       "```cpp\n",

       "\n",

       "if (repeatTimes > LOAD2D_MAX_REPEAT_TIMES) {\n",

       "    // 方案一:使用DataCopy替代\n",

       "    DataCopyParams dataCopyParams(1, blockLen, 0, 0);\n",

       "    DataCopy<WeightT>(bl1, bgm[offset], dataCopyParams);\n",

       "    \n",

       "    // 方案二:分批LoadData2D\n",

       "    for (i = 0; i < repeatTimes / 255; i++) {\n",

       "        LoadData<WeightT>(bl1[offset1], bgm[offset2], params255);\n",

       "    }\n",

       "    // 处理尾部\n",

       "    if (tail > 0) {\n",

       "        LoadData<WeightT>(bl1[offset1], bgm[offset2], paramsTail);\n",

       "    }\n",

       "}\n",

       "\n",

       "```\n"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "template <class Ctx>\n",

       "__aicore__ inline void LoadAL0(Ctx &ctx, bool isFirst)\n",

       "{\n",

       "    if ASCEND_IS_AIV {\n",

       "        return;\n",

       "    }\n",

       "    const auto *t = ctx.convTiling;\n",

       "    uint64_t currentKL0 = (ctx.kIter == ctx.maxKL0Iter) ? ctx.kL0Tail : t->kL0;\n",

       "    uint64_t posK = ctx.kAL0Iter * t->kL0;\n",

       "    uint64_t channelSize = t->cinAInCore;\n",

       "    uint64_t currentML0 = ctx.currentHoL0 * ctx.currentWoL0;\n",

       "    uint16_t mExtension = static_cast<uint16_t>(currentML0);\n",

       "    uint16_t mStartPt = 0;\n",

       "\n",

       "    Load3DBitModeParam param;\n",

       "    if (isFirst) {\n",

       "        uint64_t xmtmp = ((mExtension & MASK_16) << MSTEP_OFFSET) |\n",

       "                         ((uint64_t)(mStartPt & MASK_16) << POSM_OFFSET);\n",

       "        uint64_t xt = ((uint64_t(t->strideW) & MASK_6) << 0) |\n",

       "                      ((uint64_t(t->strideH) & MASK_6) << STRIDEH_OFFSET) |\n",

       "                      ((uint64_t(ctx.kernelW) & MASK_8) << KERNELW_OFFSET) |\n",

       "                      ((uint64_t(ctx.kernelW) & NINTH_BIT_MASK) << KERNELW_HIGHEST_BIT_OFFSET) |\n",

       "                      ((uint64_t(ctx.kernelH) & MASK_8) << KERNELH_OFFSET) |\n",

       "                      ((uint64_t(ctx.kernelH) & NINTH_BIT_MASK) << KERNELH_HIGHEST_BIT_OFFSET) |\n",

       "                      ((uint64_t(t->dilationW) & MASK_8) << DILATIONW_OFFSET) |\n",

       "                      ((uint64_t(t->dilationH) & MASK_8) << DILATIONH_OFFSET) |\n",

       "                      ((uint64_t(channelSize) & MASK_16) << CIN_OFFSET);\n",

       "        param.SetConfig1(xt);\n",

       "        uint64_t xm = ((currentKL0 & MASK_16) << 0) | ((posK & MASK_16) << POSK_OFFSET) | xmtmp;\n",

       "        param.SetConfig0(xm);\n",

       "\n",

       "        uint16_t dstStride =\n",

       "            (ctx.currentWoL0 == t->woL0 && ctx.currentHoL0 == t->hoL0)\n",

       "                ? static_cast<uint16_t>(t->fmapKStride)\n",

       "                : static_cast<uint16_t>(ctx.currentML0Align / BLOCK_L0_M);\n",

       "        LoadDataRepeatParam repeatParams = {0, 1, 0, dstStride};\n",

       "        SetLoadDataRepeat(repeatParams);\n",

       "    } else {\n",

       "        uint64_t xmtmp = ((mExtension & MASK_16) << MSTEP_OFFSET) |\n",

       "                         ((uint64_t)(mStartPt & MASK_16) << POSM_OFFSET);\n",

       "        uint64_t xm = ((currentKL0 & MASK_16) << 0) | ((posK & MASK_16) << POSK_OFFSET) | xmtmp;\n",

       "        param.SetConfig0(xm);\n",

       "    }\n",

       "    LoadData<TPosition::A2, TPosition::A1, typename Ctx::FmapT>(ctx.al0, ctx.al1, param);\n",

       "}\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 3.7 矩阵乘累加:Mad 与 CopyOut\n",

       "\n",

       "**Mad**:调用 `Mmad` 指令执行矩阵乘累加,将 L0A(特征图 im2col)× L0B(权重 NZ)的结果累加到 L0C(fp32 累加器)。\n",

       "\n",

       "关键参数:\n",

       "- `cmatrixInitVal = (kIter == 0)`:第一次 K 迭代时清零 L0C,后续迭代累加。\n",

       "- `unitFlag`:最后一次 K 迭代时设置 `UNIT_FLAG_ENABLE_WITH_FLIP`,触发 L0C 翻转(通知 Fixpipe 可以读取结果)。\n",

       "\n",

       "**CopyOut**:调用 `Fixpipe` 指令将 L0C 中的 fp32 结果转换为 fp16,并以 NCHW 布局写回 GM。\n",

       "\n",

       "`FixpipeParamsC310` 中的 `quantPre = F322F16` 指定了量化模式,`deqScalar = DEQ_SCALAR_ONE`(即浮点 1.0)表示不缩放。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "template <class Ctx>\n",

       "__aicore__ inline void LoadBL0(Ctx &ctx, bool isFirst)\n",

       "{\n",

       "    const auto *t = ctx.convTiling;\n",

       "    uint64_t kStep = (ctx.kIter != ctx.maxKL0Iter) ? t->kStep : (ctx.kL0Tail / Ctx::k0);\n",

       "    uint64_t ratioOfNToN0 = ctx.currentNL0Align / BLOCK_L0_N;\n",

       "\n",

       "    Load2DBitModeParam param;\n",

       "    if (unlikely(isFirst)) {\n",

       "        param.SetMStartPosition(0);\n",

       "        param.SetKStartPosition(static_cast<uint32_t>(ctx.kBL0Iter * t->kStep));\n",

       "        param.SetMStep(static_cast<uint16_t>(ratioOfNToN0));\n",

       "        param.SetKStep(static_cast<uint16_t>(kStep));\n",

       "        param.SetSrcStride(static_cast<int32_t>(t->nL1DivBlockSize));\n",

       "        param.SetDstStride(static_cast<uint16_t>(ratioOfNToN0));\n",

       "        param.SetIfTranspose(false);\n",

       "    } else {\n",

       "        param.SetKStartPosition(static_cast<uint32_t>(ctx.kBL0Iter * t->kStep));\n",

       "        param.SetKStep(static_cast<uint16_t>(kStep));\n",

       "    }\n",

       "    LoadData<TPosition::B2, TPosition::B1, typename Ctx::WeightT>(ctx.bl0, ctx.bl1, param);\n",

       "}\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 3.8 迭代控制:IterateOne 与 EndConv\n",

       "\n",

       "**IterateOne**:执行一次完整的卷积 tile 计算,包含以下流水步骤:\n",

       "\n",

       "1. 调用 `LoadAL1` / `LoadBL1` 将数据从 GM 搬运到 L1。\n",

       "2. 进入 K 维度循环:每次迭代调用 `LoadAL0`(im2col)、`LoadBL0`(权重 NZ)、`Mad`(矩阵乘累加)。\n",

       "3. 使用 `WaitFlag` / `SetFlag` 在 MTE1(数据搬运)和 M(矩阵计算)单元之间进行同步。\n",

       "4. 调用 `CopyOut` 将 L0C 结果写回 GM。\n",

       "\n",

       "**EndConv**:释放 L1 队列中的所有事件资源,完成一次卷积的收尾工作。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "template <class Ctx>\n",

       "__aicore__ inline void Mad(Ctx &ctx)\n",

       "{\n",

       "    MmadParams p;\n",

       "    p.m = ctx.currentML0Align;\n",

       "    p.n = ctx.currentNL0Align;\n",

       "    p.k = (ctx.kIter == ctx.maxKL0Iter) ? ctx.kL0Tail : ctx.convTiling->kL0;\n",

       "    p.cmatrixInitVal = (ctx.kIter == 0);\n",

       "    p.cmatrixSource = false;\n",

       "    p.unitFlag = (ctx.kIter == ctx.maxKL0Iter) ? UNIT_FLAG_ENABLE_WITH_FLIP : UNIT_FLAG_ENABLE_ONLY;\n",

       "    Mmad<typename Ctx::L0cT, typename Ctx::FmapT, typename Ctx::WeightT>(\n",

       "        ctx.cl0, ctx.al0, ctx.bl0, p);\n",

       "}\n",

       "\n",

       "template <class Ctx>\n",

       "__aicore__ inline void CopyOut(Ctx &ctx, const GlobalTensor<typename Ctx::OutputT> &output)\n",

       "{\n",

       "    uint64_t valueHoWo = ctx.orgHo * ctx.orgWo;\n",

       "    uint64_t offset = 0;\n",

       "\n",

       "    FixpipeParamsC310<CO2Layout::COLUMN_MAJOR> p;\n",

       "    p.mSize = ctx.currentHoL0 * ctx.currentWoL0;\n",

       "    p.params.srcNzMatrixStride = 0;\n",

       "    p.params.dnNum = 1;\n",

       "    p.params.dstDnMatrixStride = 0;\n",

       "    p.nSize = ctx.currentNL0Align;\n",

       "    p.srcStride = AlignB(p.mSize, BLOCK_L0_M);\n",

       "    p.dstStride = valueHoWo;\n",

       "    p.params.srcNzC0Stride = 1;\n",

       "    p.quantPre = QuantMode_t::F322F16;\n",

       "    p.deqScalar = DEQ_SCALAR_ONE;\n",

       "    p.unitFlag = UNIT_FLAG_ENABLE_WITH_FLIP;\n",

       "\n",

       "    Fixpipe<typename Ctx::OutputT, typename Ctx::L0cT, CFG_COLUMN_MAJOR>(\n",

       "        output[offset], ctx.cl0, p);\n",

       "}\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 3.9 Conv2dBase 类与核函数入口\n",

       "\n",

       "**关闭 namespace conv**,然后定义三个 `constexpr` 变量指定特征图、权重和输出的数据布局(均为 NCHW)。\n",

       "\n",

       "**Conv2dBase**:模板类,封装了 `RunConv2dKernel` 方法。该方法:\n",

       "1. 设置 GM 指针(特征图、权重、输出)。\n",

       "2. 调用 `InitContext` 初始化上下文。\n",

       "3. 调用 `IterateOne` 执行卷积计算。\n",

       "4. 调用 `EndConv` 释放资源。\n",

       "\n",

       "**conv2d_v2_kernel**:全局核函数入口,使用 `GET_TILING_DATA` 宏从 GM 读取 Tiling 参数,实例化类型标签,创建 `Conv2dBase` 对象并调用 `RunConv2dKernel`。\n",

       "\n",

       "`#endif` 结束 `__CCE_AICORE__` 设备侧编译块。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "template <class Ctx>\n",

       "__aicore__ inline void IterateOne(Ctx &ctx, const GlobalTensor<typename Ctx::OutputT> &output)\n",

       "{\n",

       "    ctx.cl0 = ctx.wholeCl0Tensor;\n",

       "    ctx.kIter = 0;\n",

       "\n",

       "    LoadAL1(ctx);\n",

       "    LoadBL1(ctx);\n",

       "    ctx.al1 = ctx.queueAL1.template DeQue<typename Ctx::FmapT>();\n",

       "    ctx.bl1 = ctx.queueBL1.template DeQue<typename Ctx::WeightT>();\n",

       "\n",

       "    event_t ev = static_cast<event_t>(L0_SYBC_DB_CLOSE);\n",

       "    bool isFirst = true;\n",

       "    while (ctx.kIter < ctx.ddr2l0LoopK) {\n",

       "        AscendC::WaitFlag<AscendC::HardEvent::M_MTE1>(ev);\n",

       "        ctx.al0 = ctx.wholeAl0Tensor;\n",

       "        ctx.kAL0Iter = ctx.kIter;\n",

       "        LoadAL0(ctx, isFirst);\n",

       "        ctx.bl0 = ctx.wholeBl0Tensor;\n",

       "        ctx.kBL0Iter = ctx.kIter;\n",

       "        LoadBL0(ctx, isFirst);\n",

       "        AscendC::SetFlag<AscendC::HardEvent::MTE1_M>(ev);\n",

       "        AscendC::WaitFlag<AscendC::HardEvent::MTE1_M>(ev);\n",

       "        Mad(ctx);\n",

       "        AscendC::SetFlag<AscendC::HardEvent::M_MTE1>(ev);\n",

       "        ctx.kIter++;\n",

       "        isFirst = false;\n",

       "    }\n",

       "\n",

       "    ctx.queueAL1.FreeTensor(ctx.al1);\n",

       "    ctx.queueBL1.FreeTensor(ctx.bl1);\n",

       "\n",

       "    if ASCEND_IS_AIC_CONV {\n",

       "        CopyOut(ctx, output);\n",

       "    }\n",

       "}\n",

       "\n",

       "template <class Ctx>\n",

       "__aicore__ inline void EndConv(Ctx &ctx)\n",

       "{\n",

       "    if ASCEND_IS_AIC_CONV {\n",

       "        ctx.queueAL1.FreeAllEvent();\n",

       "        ctx.queueBL1.FreeAllEvent();\n",

       "    }\n",

       "}\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "---\n",

       "\n",

       "## 4. Host侧调用代码\n",

       "\n",

       "Host侧代码负责:填充 Tiling 结构体、验证片上空间、分配设备内存、启动核函数、回收结果。\n",

       "\n",

       "### 4.1 卷积形状常量\n",

       "\n",

       "定义本 Demo 使用的卷积形状参数。所有后续的 Tiling 计算和内存分配都基于这些常量。\n",

       "\n",

       "`Fp16ToFloat` 是一个纯软件的 fp16 解码函数,用于在主机侧将输出结果转换为 float 以便打印。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "}  // namespace conv\n",

       "\n",

       "using namespace conv;\n",

       "\n",

       "constexpr ConvFormat fmapFormat = ConvFormat::NCHW;\n",

       "constexpr ConvFormat filterFormat = ConvFormat::NCHW;\n",

       "constexpr ConvFormat outputFormat = ConvFormat::NCHW;\n",

       "\n",

       "template <class FMAP_TYPE, class WEIGHT_TYPE, class OUTPUT_TYPE>\n",

       "class Conv2dBase {\n",

       "public:\n",

       "    using FMAP_T = typename FMAP_TYPE::T;\n",

       "    using WEIGHT_T = typename WEIGHT_TYPE::T;\n",

       "    using OUTPUT_T = typename OUTPUT_TYPE::T;\n",

       "\n",

       "    __aicore__ inline void RunConv2dKernel(GM_ADDR x, GM_ADDR filter, GM_ADDR y,\n",

       "                                           const Conv2DTilingData &tilingData)\n",

       "    {\n",

       "        ctx.hiStartPos = -static_cast<int64_t>(tilingData.padTop);\n",

       "        ctx.wiStartPos = -static_cast<int64_t>(tilingData.padLeft);\n",

       "        ctx.hiStartPos = ctx.hiStartPos > 0 ? 0 : ctx.hiStartPos;\n",

       "\n",

       "        fmapGm.SetGlobalBuffer(reinterpret_cast<__gm__ FMAP_T *>(x));\n",

       "        filterGm.SetGlobalBuffer(reinterpret_cast<__gm__ WEIGHT_T *>(filter));\n",

       "        outputGm.SetGlobalBuffer(reinterpret_cast<__gm__ OUTPUT_T *>(y));\n",

       "\n",

       "        InitContext(ctx, &tilingData);\n",

       "        ctx.hiStartPos = -static_cast<int64_t>(tilingData.padTop);\n",

       "        ctx.wiStartPos = -static_cast<int64_t>(tilingData.padLeft);\n",

       "\n",

       "        ctx.agm = fmapGm;\n",

       "        ctx.bgm = filterGm;\n",

       "\n",

       "        if ASCEND_IS_AIC_CONV {\n",

       "            IterateOne(ctx, outputGm);\n",

       "        }\n",

       "        EndConv(ctx);\n",

       "    }\n",

       "\n",

       "private:\n",

       "    conv::Conv2dContext<FMAP_TYPE, WEIGHT_TYPE, OUTPUT_TYPE> ctx;\n",

       "    GlobalTensor<FMAP_T> fmapGm;\n",

       "    GlobalTensor<WEIGHT_T> filterGm;\n",

       "    GlobalTensor<OUTPUT_T> outputGm;\n",

       "};\n",

       "#endif\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 4.2 卷积形状常量与辅助函数\n",

       "\n",

       "定义本 Demo 使用的卷积形状参数(BATCH、CI、HI、WI、CO、KH、KW 等)。所有后续的 Tiling 计算和内存分配都基于这些常量。\n",

       "\n",

       "`Fp16ToFloat` 是一个纯软件的 fp16 解码函数,用于在主机侧将输出结果转换为 float 以便打印。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "constexpr uint32_t BATCH = 1;\n",

       "constexpr uint32_t CI = 16;   // input channels\n",

       "constexpr uint32_t HI = 8;    // input height\n",

       "constexpr uint32_t WI = 8;    // input width\n",

       "constexpr uint32_t CO = 16;   // output channels (must be multiple of 16 for NZ layout)\n",

       "constexpr uint32_t KH = 3;    // kernel height\n",

       "constexpr uint32_t KW = 3;    // kernel width\n",

       "constexpr uint32_t STRIDE_H = 1;\n",

       "constexpr uint32_t STRIDE_W = 1;\n",

       "constexpr uint32_t DILATION_H = 1;\n",

       "constexpr uint32_t DILATION_W = 1;\n",

       "constexpr uint32_t PAD_TOP = 0;\n",

       "constexpr uint32_t PAD_BOTTOM = 0;\n",

       "constexpr uint32_t PAD_LEFT = 0;\n",

       "constexpr uint32_t PAD_RIGHT = 0;\n",

       "// Standard conv output size formula\n",

       "constexpr uint32_t HO = (HI + PAD_TOP + PAD_BOTTOM - DILATION_H * (KH - 1) - 1) / STRIDE_H + 1;\n",

       "constexpr uint32_t WO = (WI + PAD_LEFT + PAD_RIGHT - DILATION_W * (KW - 1) - 1) / STRIDE_W + 1;\n",

       "\n",

       "__global__ __aicore__ void conv2d_v2_kernel(GM_ADDR x, GM_ADDR filter, GM_ADDR bias,\n",

       "    GM_ADDR offset_w, GM_ADDR y, __kfc_workspace__ GM_ADDR workspace, GM_ADDR tilingGm)\n",

       "{\n",

       "#ifdef __CCE_AICORE__\n",

       "    GET_TILING_DATA(tilingData, tilingGm);\n",

       "\n",

       "    using fmapType = ConvType<TPosition::GM, fmapFormat, half>;\n",

       "    using weightType = ConvType<TPosition::GM, filterFormat, half>;\n",

       "    using outputType = ConvType<TPosition::GM, outputFormat, half>;\n",

       "\n",

       "    Conv2dBase<fmapType, weightType, outputType> baseConv2d;\n",

       "    baseConv2d.RunConv2dKernel(x, filter, y, tilingData);\n",

       "#endif\n",

       "}\n",

       "\n",

       "constexpr uint8_t BLOCK_SIZE = 16;\n",

       "\n",

       "float Fp16ToFloat(uint16_t h)\n",

       "{\n",

       "    uint32_t sign = (h >> 15) & 0x1;\n",

       "    uint32_t exp = (h >> 10) & 0x1F;\n",

       "    uint32_t frac = h & 0x3FF;\n",

       "    float result;\n",

       "    if (exp == 0) {\n",

       "        if (frac == 0) {\n",

       "            result = 0.0f;\n",

       "        } else {\n",

       "            result = std::ldexp(static_cast<float>(frac), -24);\n",

       "        }\n",

       "    } else if (exp == 31) {\n",

       "        result = (frac == 0) ? std::numeric_limits<float>::infinity() : std::numeric_limits<float>::quiet_NaN();\n",

       "    } else {\n",

       "        result = std::ldexp(1.0f + static_cast<float>(frac) / 1024.0f, static_cast<int>(exp) - 15);\n",

       "    }\n",

       "    return sign ? -result : result;\n",

       "}\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 4.3 SetTilingData\n",

       "\n",

       "`SetTilingData` 根据卷积形状常量填充 `Conv2DTilingData` 结构体。\n",

       "\n",

       "关键字段说明:\n",

       "- **kAL1 / kBL1**:L1 侧 K 维度大小 = KH × KW × CI(一次性加载全部 K)。\n",

       "- **kL0**:L0 侧 K 维度大小,本 Demo 中等于 kAL1(单次 Mmad 完成全部 K 累加)。\n",

       "- **mStep**:M 维度对齐到 16 的倍数(Mmad 要求 M 为 BLOCK_L0_M 的整数倍)。\n",

       "- **cinAInCore / cinBInCore**:每核处理的输入通道数(= kAL1 / kernelHW)。\n",

       "- **nL1DivBlockSize**:L1 中 N 方向的块数(= nBL1 / BLOCK_SIZE)。\n",

       "\n",

       "代码中标注了 `[QUIZ]` 的位置是本章课后练习的填空点。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "void SetTilingData(Conv2DTilingData* tiling)\n",

       "{\n",

       "    tiling->padTop = PAD_TOP;\n",

       "    tiling->padLeft = PAD_LEFT;\n",

       "\n",

       "    // ── ApiTiling: memory-hierarchy tiling parameters ──────────────────────\n",

       "    // These tell the kernel how large each on-chip buffer tile is.\n",

       "    // For this full-load demo the entire output tile fits in one L0 block,\n",

       "    // so hoL0/woL0 >= singleCoreHo/Wo and kL0 == kAL1 == kBL1 == total K.\n",

       "    tiling->orgHi = HI;\n",

       "    tiling->orgWi = WI;\n",

       "    tiling->orgHo = HO;\n",

       "    tiling->orgWo = WO;\n",

       "    tiling->singleCoreBatch = BATCH;\n",

       "    tiling->singleCoreHo = HO;\n",

       "    tiling->singleCoreWo = WO;\n",

       "    tiling->orgCi = CI;\n",

       "    tiling->orgCo = CO;\n",

       "    tiling->singleCoreCi = CI;\n",

       "    tiling->singleCoreCo = CO;\n",

       "    // [QUIZ 1/4] kAL1: total K dimension loaded into L1 for the fmap side.\n",

       "    //   K = number of multiply-accumulate steps per output pixel\n",

       "    //     = kernel_h × kernel_w × Cin\n",

       "    //   Hint: use KH, KW, CI\n",

       "    /* BEGIN_TODO */\n",

       "    tiling->kAL1 = KH * KW * CI;\n",

       "    /* END_TODO */\n",

       "    // [QUIZ 2/4] kBL1: same K dimension for the weight side (must equal kAL1).\n",

       "    /* BEGIN_TODO */\n",

       "    tiling->kBL1 = KH * KW * CI;\n",

       "    /* END_TODO */\n",

       "    tiling->nBL1 = 16;\n",

       "    tiling->hoL0 = 16;\n",

       "    tiling->woL0 = WO;\n",

       "    // [QUIZ 3/4] kL0: K dimension actually loaded into L0A/L0B per Mmad call.\n",

       "    //   In this full-load demo kL0 == kAL1 (one shot, no K-loop).\n",

       "    /* BEGIN_TODO */\n",

       "    tiling->kL0 = KH * KW * CI;\n",

       "    /* END_TODO */\n",

       "    tiling->nL0 = 16;\n",

       "    tiling->orgHixWi = HI * WI;\n",

       "    tiling->kernelHxkernelW = KH * KW;\n",

       "    tiling->aL1SpaceSize = 8192;\n",

       "\n",

       "    // Derived fields — computed from the shape constants above\n",

       "    uint32_t kernelHW = KH * KW;\n",

       "    tiling->cinAInCore = tiling->kAL1 / kernelHW;\n",

       "    tiling->cinBInCore = tiling->kBL1 / kernelHW;\n",

       "    // [QUIZ 4/4] mStep: M dimension aligned up to the next multiple of 16.\n",

       "    //   M = total output pixels per core = HO × WO\n",

       "    //   Mmad requires M to be a multiple of BLOCK_L0_M (16).\n",

       "    //   Hint: AlignUp(HO * WO, 16)\n",

       "    /* BEGIN_TODO */\n",

       "    tiling->mStep = ((HO * WO + 15) / 16) * 16;\n",

       "    /* END_TODO */\n",

       "    tiling->kStep = 1;\n",

       "    tiling->fmapKStride = 1;\n",

       "    tiling->coutOffsetBlock = CI * kernelHW;\n",

       "    tiling->nL1DivBlockSize = tiling->nBL1 / BLOCK_SIZE;\n",

       "    tiling->kernelH = KH;\n",

       "    tiling->kernelW = KW;\n",

       "    tiling->strideH = STRIDE_H;\n",

       "    tiling->strideW = STRIDE_W;\n",

       "    tiling->dilationH = DILATION_H;\n",

       "    tiling->dilationW = DILATION_W;\n",

       "    tiling->offsetx = 0;\n",

       "}\n",

       "\n",

       "// ============================================================================\n",

       "// Space validation\n",

       "// ============================================================================\n",

       "//\n",

       "// Before launching the kernel, verify that the tiling parameters actually fit\n",

       "// in the NPU's on-chip SRAM.  If they don't, the kernel will silently corrupt\n",

       "// memory or produce wrong results — there is no hardware bounds check.\n",

       "//\n",

       "// On-chip buffer sizes (Ascend 950PR/Ascend 950DT):\n",

       "//   L1  : 512 MB  per core  (fmap + weight staging area)\n",

       "//   L0A : 64 KB per core  (matrix A — im2col output)\n",

       "//   L0B : 64 KB per core  (matrix B — weight NZ)\n",

       "//   L0C : 256 KB per core (accumulator, fp32)\n",

       "//\n",

       "// Layout sizes:\n",

       "//   AL1 : hiLoad * wiLoad * CI * sizeof(fp16)\n",

       "//         where hiLoad = (HO-1)*strideH + dilatedKH\n",

       "//               wiLoad = (WO-1)*strideW + dilatedKW\n",

       "//   BL1 : nBL1 * kBL1 * sizeof(fp16)\n",

       "//   AL0 : AlignUp(HO*WO, 16) * kL0 * sizeof(fp16)   [NZ: M-blocks × K]\n",

       "//   BL0 : AlignUp(CO, 16)    * kL0 * sizeof(fp16)   [NZ: N-blocks × K]\n",

       "//   CL0 : AlignUp(HO*WO, 16) * AlignUp(CO, 16) * sizeof(fp32)\n",

       "//\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 4.4 CheckTilingSpace\n",

       "\n",

       "在启动核函数之前,必须验证 Tiling 参数是否真正适合 NPU 的片上 SRAM。若超出限制,核函数会静默地越界访问片上内存,产生错误结果或硬件异常——没有任何硬件边界检查。\n",

       "\n",

       "各缓冲区的大小限制(Ascend 950PR/Ascend 950DT):\n",

       "\n",

       "<table style=\"text-align: left; margin-left: 0;\">\n",

       "<tr style=\"background-color:#f0f0f0\">\n",

       "  <td align=\"center\" style=\"width: 150px\"><strong>缓冲区</strong></td>\n",

       "  <td align=\"center\"><strong>大小</strong></td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\"> L1 </td>\n",

       "  <td align=\"left\">512 MB / 核</td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">L0A  </td>\n",

       "  <td align=\"left\">  64 KB / 核 </td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\">L0B  </td>\n",

       "  <td align=\"left\">64 KB / 核 </td>\n",

       "</tr>\n",

       "<tr>\n",

       "  <td align=\"left\"> L0C</td>\n",

       "  <td align=\"left\"> 256 KB / 核</td>\n",

       "</tr>\n",

       "</table>\n",

       "\n",

       "代码中标注了 `[QUIZ 1/5]` 到 `[QUIZ 5/5]` 的位置是本章课后练习的填空点。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "static bool CheckTilingSpace(const Conv2DTilingData* a)\n",

       "{\n",

       "    const uint32_t fp16 = sizeof(uint16_t);\n",

       "    const uint32_t fp32 = sizeof(float);\n",

       "    const uint32_t align16 = 16;\n",

       "\n",

       "    // L1 — fmap patch that load3d reads from\n",

       "    // [QUIZ 1/5] Effective kernel size after dilation.\n",

       "    //   dilatedKH = 1 + (KH - 1) * DILATION_H\n",

       "    //   (a 3×3 kernel with dilation=2 spans 5 rows in the input)\n",

       "    /* BEGIN_TODO */\n",

       "    uint32_t dilatedKH = 1 + (KH - 1) * DILATION_H;\n",

       "    uint32_t dilatedKW = 1 + (KW - 1) * DILATION_W;\n",

       "    /* END_TODO */\n",

       "    // [QUIZ 2/5] Input patch height/width that L1 must hold for one output tile.\n",

       "    //   The last output row at index (HO-1) reads input row (HO-1)*strideH + (dilatedKH-1),\n",

       "    //   so the patch spans: hiLoad = (HO-1)*STRIDE_H + dilatedKH rows.\n",

       "    /* BEGIN_TODO */\n",

       "    uint32_t hiLoad = (HO - 1) * STRIDE_H + dilatedKH;\n",

       "    uint32_t wiLoad = (WO - 1) * STRIDE_W + dilatedKW;\n",

       "    /* END_TODO */\n",

       "    // [QUIZ 3/5] AL1 bytes: the fmap patch is hiLoad × wiLoad × CI fp16 elements.\n",

       "    /* BEGIN_TODO */\n",

       "    uint64_t al1Bytes = (uint64_t)hiLoad * wiLoad * CI * fp16;\n",

       "    /* END_TODO */\n",

       "\n",

       "    // L1 — weight block\n",

       "    uint64_t bl1Bytes = (uint64_t)a->nBL1 * a->kBL1 * fp16;\n",

       "\n",

       "    // L1 total\n",

       "    const uint64_t L1_SIZE = 1024 * 1024ULL;\n",

       "    uint64_t l1Total = al1Bytes + bl1Bytes;\n",

       "\n",

       "    // L0A — im2col result: M-blocks × K elements, fp16\n",

       "    // [QUIZ 4/5] AL0 bytes.\n",

       "    //   After im2col, L0A holds mAlign × kL0 fp16 elements in NZ layout.\n",

       "    //   mAlign = AlignUp(HO*WO, 16)  — M must be a multiple of 16 for Mmad.\n",

       "    /* BEGIN_TODO */\n",

       "    uint32_t mAlign = ((HO * WO + align16 - 1) / align16) * align16;\n",

       "    uint64_t al0Bytes = (uint64_t)mAlign * a->kL0 * fp16;\n",

       "    /* END_TODO */\n",

       "\n",

       "    // L0B — weight NZ: N-blocks × K elements, fp16\n",

       "    uint32_t nAlign = ((CO + align16 - 1) / align16) * align16;\n",

       "    uint64_t bl0Bytes = (uint64_t)nAlign * a->kL0 * fp16;\n",

       "\n",

       "    // L0C — accumulator: M-blocks × N-blocks, fp32\n",

       "    // [QUIZ 5/5] CL0 bytes.\n",

       "    //   L0C stores the Mmad result as fp32 (even though inputs are fp16).\n",

       "    //   Size = mAlign × nAlign × sizeof(float).\n",

       "    //   Note: fp32 = 4 bytes, NOT fp16 = 2 bytes — this is the most common mistake.\n",

       "    /* BEGIN_TODO */\n",

       "    uint64_t cl0Bytes = (uint64_t)mAlign * nAlign * fp32;\n",

       "    /* END_TODO */\n",

       "\n",

       "    const uint64_t L0A_LIMIT = 64 * 1024ULL;\n",

       "    const uint64_t L0B_LIMIT = 64 * 1024ULL;\n",

       "    const uint64_t L0C_LIMIT = 256 * 1024ULL;\n",

       "\n",

       "    bool ok = true;\n",

       "    if (l1Total > L1_SIZE) {\n",

       "        std::cerr << \"[TILING ERROR] L1 overflow: need \" << l1Total\n",

       "                  << \" bytes (AL1=\" << al1Bytes << \" + BL1=\" << bl1Bytes\n",

       "                  << \"), limit=\" << L1_SIZE << \"\\n\";\n",

       "        ok = false;\n",

       "    }\n",

       "    if (al0Bytes > L0A_LIMIT) {\n",

       "        std::cerr << \"[TILING ERROR] L0A overflow: need \" << al0Bytes\n",

       "                  << \" bytes (M=\" << mAlign << \" K=\" << a->kL0\n",

       "                  << \"), limit=\" << L0A_LIMIT << \"\\n\";\n",

       "        ok = false;\n",

       "    }\n",

       "    if (bl0Bytes > L0B_LIMIT) {\n",

       "        std::cerr << \"[TILING ERROR] L0B overflow: need \" << bl0Bytes\n",

       "                  << \" bytes (N=\" << nAlign << \" K=\" << a->kL0\n",

       "                  << \"), limit=\" << L0B_LIMIT << \"\\n\";\n",

       "        ok = false;\n",

       "    }\n",

       "    if (cl0Bytes > L0C_LIMIT) {\n",

       "        std::cerr << \"[TILING ERROR] L0C overflow: need \" << cl0Bytes\n",

       "                  << \" bytes (M=\" << mAlign << \" N=\" << nAlign\n",

       "                  << \"), limit=\" << L0C_LIMIT << \"\\n\";\n",

       "        ok = false;\n",

       "    }\n",

       "    if (ok) {\n",

       "        std::cout << \"[TILING OK] L1=\" << l1Total << \"/\" << L1_SIZE\n",

       "                  << \"  L0A=\" << al0Bytes << \"/\" << L0A_LIMIT\n",

       "                  << \"  L0B=\" << bl0Bytes << \"/\" << L0B_LIMIT\n",

       "                  << \"  L0C=\" << cl0Bytes << \"/\" << L0C_LIMIT << \"\\n\";\n",

       "    }\n",

       "    return ok;\n",

       "}\n",

       "\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "### 4.5 main 函数\n",

       "\n",

       "`main` 函数完整演示了 AscendC 核函数的调用生命周期:\n",

       "\n",

       "1. 调用 `SetTilingData` 填充 Tiling 结构体。\n",

       "2. 调用 `CheckTilingSpace` 验证片上空间,若溢出则提前退出。\n",

       "3. 初始化 ACL 运行环境(`aclInit` → `aclrtSetDevice` → `aclrtCreateStream`)。\n",

       "4. 分配主机和设备内存,将输入数据(全 1.0 的 fp16)从主机复制到设备。\n",

       "5. 通过 `<<<numBlocks, nullptr, stream>>>` 语法启动核函数,并用 `aclrtSynchronizeStream` 等待完成。\n",

       "6. 将输出从设备复制回主机,并写入文本文件。\n",

       "7. 释放所有内存和运行时资源。"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile -a Sources/01.03/minimal_demo.asc\n",

       "int32_t main(int32_t argc, char* argv[])\n",

       "{\n",

       "    size_t inputSize = BATCH * CI * HI * WI * sizeof(uint16_t);\n",

       "    size_t filterSize = CO * CI * KH * KW * sizeof(uint16_t);\n",

       "    size_t outputSize = BATCH * CO * HO * WO * sizeof(uint16_t);\n",

       "    size_t tilingSize = sizeof(Conv2DTilingData);\n",

       "    size_t workspaceSize = 16 * 1024 * 1024;\n",

       "    uint32_t numBlocks = 1;\n",

       "\n",

       "    Conv2DTilingData tilingBuf;\n",

       "    memset(&tilingBuf, 0, sizeof(Conv2DTilingData));\n",

       "    SetTilingData(&tilingBuf);\n",

       "\n",

       "    // Validate that the tiling fits in on-chip SRAM before touching the device.\n",

       "    // An overflow here means the kernel would read/write out-of-bounds on-chip\n",

       "    // memory, producing silent wrong results or a hardware exception.\n",

       "    if (!CheckTilingSpace(&tilingBuf)) {\n",

       "        std::cerr << \"Tiling validation failed. Adjust shape constants or tiling params.\\n\";\n",

       "        return 1;\n",

       "    }\n",

       "\n",

       "    aclInit(nullptr);\n",

       "    int32_t deviceId = 0;\n",

       "    aclrtSetDevice(deviceId);\n",

       "    aclrtStream stream = nullptr;\n",

       "    aclrtCreateStream(&stream);\n",

       "\n",

       "    uint8_t* xHost;\n",

       "    uint8_t* xDevice;\n",

       "    aclrtMallocHost((void**)(&xHost), inputSize);\n",

       "    aclrtMalloc((void**)&xDevice, inputSize, ACL_MEM_MALLOC_HUGE_FIRST);\n",

       "    uint16_t* xHalf = reinterpret_cast<uint16_t*>(xHost);\n",

       "    for (uint32_t i = 0; i < BATCH * CI * HI * WI; ++i) {\n",

       "        xHalf[i] = 0x3C00; // fp16 1.0\n",

       "    }\n",

       "    aclrtMemcpy(xDevice, inputSize, xHost, inputSize, ACL_MEMCPY_HOST_TO_DEVICE);\n",

       "\n",

       "    uint8_t* filterHost;\n",

       "    uint8_t* filterDevice;\n",

       "    aclrtMallocHost((void**)(&filterHost), filterSize);\n",

       "    aclrtMalloc((void**)&filterDevice, filterSize, ACL_MEM_MALLOC_HUGE_FIRST);\n",

       "    uint16_t* filterHalf = reinterpret_cast<uint16_t*>(filterHost);\n",

       "    for (uint32_t i = 0; i < CO * CI * KH * KW; ++i) {\n",

       "        filterHalf[i] = 0x3C00; // fp16 1.0\n",

       "    }\n",

       "    aclrtMemcpy(filterDevice, filterSize, filterHost, filterSize, ACL_MEMCPY_HOST_TO_DEVICE);\n",

       "\n",

       "    uint8_t* yDevice;\n",

       "    uint8_t* yHost;\n",

       "    aclrtMallocHost((void**)(&yHost), outputSize);\n",

       "    aclrtMalloc((void**)&yDevice, outputSize, ACL_MEM_MALLOC_HUGE_FIRST);\n",

       "\n",

       "    uint8_t* workspaceDevice;\n",

       "    aclrtMalloc((void**)&workspaceDevice, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);\n",

       "\n",

       "    uint8_t* tilingHost;\n",

       "    uint8_t* tilingDevice;\n",

       "    aclrtMallocHost((void**)(&tilingHost), tilingSize);\n",

       "    aclrtMalloc((void**)&tilingDevice, tilingSize, ACL_MEM_MALLOC_HUGE_FIRST);\n",

       "    memcpy(tilingHost, &tilingBuf, tilingSize);\n",

       "    aclrtMemcpy(tilingDevice, tilingSize, tilingHost, tilingSize, ACL_MEMCPY_HOST_TO_DEVICE);\n",

       "\n",

       "    conv2d_v2_kernel<<<numBlocks, nullptr, stream>>>(\n",

       "        xDevice, filterDevice, nullptr, nullptr, yDevice, workspaceDevice, tilingDevice);\n",

       "    aclrtSynchronizeStream(stream);\n",

       "\n",

       "    aclrtMemcpy(yHost, outputSize, yDevice, outputSize, ACL_MEMCPY_DEVICE_TO_HOST);\n",

       "    std::cout << \"Conv2D V2 kernel execution completed.\" << std::endl;\n",

       "\n",

       "    // Write input matrix to file\n",

       "    {\n",

       "        std::ofstream ofs(\"input_matrix.txt\");\n",

       "        uint16_t* xData = reinterpret_cast<uint16_t*>(xHost);\n",

       "        ofs << \"Input shape: [\" << BATCH << \", \" << CI << \", \" << HI << \", \" << WI << \"]\" << std::endl;\n",

       "        for (uint32_t n = 0; n < BATCH; ++n) {\n",

       "            for (uint32_t c = 0; c < CI; ++c) {\n",

       "                ofs << \"N=\" << n << \", C=\" << c << \":\" << std::endl;\n",

       "                for (uint32_t h = 0; h < HI; ++h) {\n",

       "                    for (uint32_t w = 0; w < WI; ++w) {\n",

       "                        uint32_t idx = ((n * CI + c) * HI + h) * WI + w;\n",

       "                        ofs << Fp16ToFloat(xData[idx]) << \" \";\n",

       "                    }\n",

       "                    ofs << std::endl;\n",

       "                }\n",

       "            }\n",

       "        }\n",

       "        ofs.close();\n",

       "        std::cout << \"Input matrix written to input_matrix.txt\" << std::endl;\n",

       "    }\n",

       "\n",

       "    // Write output matrix to file\n",

       "    {\n",

       "        std::ofstream ofs(\"output_matrix.txt\");\n",

       "        uint16_t* yData = reinterpret_cast<uint16_t*>(yHost);\n",

       "        ofs << \"Output shape: [\" << BATCH << \", \" << CO << \", \" << HO << \", \" << WO << \"]\" << std::endl;\n",

       "        for (uint32_t n = 0; n < BATCH; ++n) {\n",

       "            for (uint32_t c = 0; c < CO; ++c) {\n",

       "                ofs << \"N=\" << n << \", C=\" << c << \":\" << std::endl;\n",

       "                for (uint32_t h = 0; h < HO; ++h) {\n",

       "                    for (uint32_t w = 0; w < WO; ++w) {\n",

       "                        uint32_t idx = ((n * CO + c) * HO + h) * WO + w;\n",

       "                        ofs << Fp16ToFloat(yData[idx]) << \" \";\n",

       "                    }\n",

       "                    ofs << std::endl;\n",

       "                }\n",

       "            }\n",

       "        }\n",

       "        ofs.close();\n",

       "        std::cout << \"Output matrix written to output_matrix.txt\" << std::endl;\n",

       "    }\n",

       "\n",

       "    aclrtFree(xDevice);\n",

       "    aclrtFreeHost(xHost);\n",

       "    aclrtFree(filterDevice);\n",

       "    aclrtFreeHost(filterHost);\n",

       "    aclrtFree(yDevice);\n",

       "    aclrtFreeHost(yHost);\n",

       "    aclrtFree(workspaceDevice);\n",

       "    aclrtFree(tilingDevice);\n",

       "    aclrtFreeHost(tilingHost);\n",

       "\n",

       "    aclrtDestroyStream(stream);\n",

       "    aclrtResetDevice(deviceId);\n",

       "    aclFinalize();\n",

       "\n",

       "    return 0;\n",

       "}\n"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "---\n",

       "\n",

       "## 5. CMakeLists.txt\n",

       "\n",

       "以下是本项目的 CMake 构建配置。与 Add 算子示例相比,Conv2D V2 Demo 需要额外链接多个 CANN 运行时库(`tiling_api`、`ascendc_runtime`、`ascendcl` 等),并通过 `target_include_directories` 指定 AscendC 内部头文件路径。\n"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "%%writefile Sources/01.03/CMakeLists.txt\n",

       "cmake_minimum_required(VERSION 3.16)\n",

       "\n",

       "find_package(ASC REQUIRED)\n",

       "\n",

       "project(conv2d_v2_demo_minimal LANGUAGES ASC CXX)\n",

       "\n",

       "add_executable(minimal_demo\n",

       "    minimal_demo.asc\n",

       ")\n",

       "\n",

       "target_include_directories(minimal_demo PRIVATE\n",

       "    ${CMAKE_CURRENT_SOURCE_DIR}\n",

       "    ${ASCEND_CANN_PACKAGE_LINUX_PATH}/asc/impl\n",

       "    ${ASCEND_CANN_PACKAGE_LINUX_PATH}/asc/impl/basic_api\n",

       "    ${ASCEND_CANN_PACKAGE_LINUX_PATH}/asc/impl/basic_api/reg_compute\n",

       "    ${ASCEND_CANN_PACKAGE_LINUX_PATH}/asc/impl/simt_api\n",

       "    ${ASCEND_CANN_PACKAGE_LINUX_PATH}/include\n",

       "    ${ASCEND_CANN_PACKAGE_LINUX_PATH}/tikcpp/tikcfw\n",

       ")\n",

       "\n",

       "target_link_directories(minimal_demo PRIVATE\n",

       "    ${ASCEND_CANN_PACKAGE_PATH}/lib64\n",

       ")\n",

       "\n",

       "target_link_libraries(minimal_demo PRIVATE\n",

       "    tiling_api\n",

       "    register\n",

       "    platform\n",

       "    ascendc_runtime\n",

       "    runtime\n",

       "    ascendcl\n",

       "    error_manager\n",

       "    profapi\n",

       "    ge_common_base\n",

       "    unified_dlog\n",

       "    mmpa\n",

       "    ascend_dump\n",

       "    c_sec\n",

       "    m\n",

       "    dl\n",

       ")\n",

       "\n",

       "target_compile_options(minimal_demo PRIVATE\n",

       "    $<$<COMPILE_LANGUAGE:ASC>:--npu-arch=dav-3510>\n",

       ")"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "---\n\n## 6. 构建脚本 build.sh\n\n除了直接在 Jupyter 中运行 CMake 命令,`src/conv2d_op/build.sh` 提供了一个等价的一键构建脚本,适合在终端中使用。\n\n```bash\n#!/bin/bash\nset -e\n\nSCRIPT_DIR=$(cd \"$(dirname \"$0\")\" && pwd)\nBUILD_DIR=${SCRIPT_DIR}/build\n# TODO: Set ASCEND_HOME_PATH in your environment, or replace the fallback path below\n#       with your actual CANN installation directory, e.g. /usr/local/Ascend/ascend-toolkit/latest\nCANN_PATH=${ASCEND_HOME_PATH:-/path/to/your/cann}\n\nrm -rf ${BUILD_DIR}\nmkdir -p ${BUILD_DIR} && cd ${BUILD_DIR}\n\ncmake .. -DASCEND_CANN_PACKAGE_PATH=${CANN_PATH}\nmake -j$(nproc)\n\necho \"Build success: ${BUILD_DIR}/minimal_demo\"\n```\n\n**使用方式**:\n\n```bash\n# 步骤一:设置环境变量\nexport ASCEND_HOME_PATH=/your/cann/install/path\nbash src/conv2d_op/build.sh\n\n# 步骤二:修改脚本中的 CANN_PATH 默认值为你的实际路径\n```\n\n> **注意**:脚本中 `CANN_PATH` 的默认值 `/path/to/your/cann` 是占位符,请替换为你的实际 CANN 安装目录,或在运行前设置 `ASCEND_HOME_PATH` 环境变量。"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "---\n",

       "\n",

       "## 7. 编译与运行\n",

       "\n",

       "执行以下命令编译可执行文件:"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "!cd Sources/01.03 && mkdir -p build\n",

       "!export ASC_DIR=$ASCEND_HOME_PATH/aarch64-linux/tikcpp/ascendc_kernel_cmake/ && \\\n",

       "cd Sources/01.03/build/ && \\\n",

       "cmake .. && \\\n",

       "make"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "再执行以下代码,进行算子的实际运行:"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "!cd Sources/01.03/build/ && ./minimal_demo"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "---\n",

       "\n",

       "## 课后实践\n",

       "\n",

       "本节共有 **9 处填空**,分布在 `SetTilingData`(4 处)和 `CheckTilingSpace`(5 处)中。\n",

       "\n",

       "请根据注释中的提示,在下面的代码框中补充完整的 `SetTilingData` 和 `CheckTilingSpace` 函数实现。\n",

       "\n",

       "**SetTilingData 填空(4 处):**\n",

       "\n",

       "- `[QUIZ 1/4]` kAL1:L1 侧特征图的 K 维度大小 = KH × KW × CI\n",

       "- `[QUIZ 2/4]` kBL1:L1 侧权重的 K 维度大小(与 kAL1 相等)\n",

       "- `[QUIZ 3/4]` kL0:L0 侧每次 Mmad 的 K 维度大小(本 Demo 中等于 kAL1)\n",

       "- `[QUIZ 4/4]` mStep:M 维度对齐到 16 的倍数,M = HO × WO\n",

       "\n",

       "**CheckTilingSpace 填空(5 处):**\n",

       "\n",

       "- `[QUIZ 1/5]` dilatedKH/W:膨胀后的有效卷积核尺寸\n",

       "- `[QUIZ 2/5]` hiLoad/wiLoad:L1 需要加载的输入 patch 尺寸\n",

       "- `[QUIZ 3/5]` al1Bytes:AL1 缓冲区字节数\n",

       "- `[QUIZ 4/5]` al0Bytes:AL0 缓冲区字节数(im2col 结果)\n",

       "- `[QUIZ 5/5]` cl0Bytes:L0C 累加器字节数(注意是 fp32!)"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "# 在此处填写你的答案,然后运行本单元格验证\n",

       "# 参考 SetTilingData 和 CheckTilingSpace 中的 [QUIZ] 注释\n",

       "\n",

       "# [QUIZ 1/4] kAL1 = ?\n",

       "kAL1 = None  # TODO: KH * KW * CI\n",

       "\n",

       "# [QUIZ 2/4] kBL1 = ?\n",

       "kBL1 = None  # TODO: KH * KW * CI\n",

       "\n",

       "# [QUIZ 3/4] kL0 = ?\n",

       "kL0 = None   # TODO: KH * KW * CI\n",

       "\n",

       "# [QUIZ 4/4] mStep = ?\n",

       "HO = 6; WO = 6\n",

       "mStep = None  # TODO: AlignUp(HO * WO, 16)\n",

       "\n",

       "# [QUIZ 1/5] dilatedKH, dilatedKW = ?\n",

       "KH = 3; KW = 3; DILATION_H = 1; DILATION_W = 1\n",

       "dilatedKH = None  # TODO: 1 + (KH - 1) * DILATION_H\n",

       "dilatedKW = None  # TODO: 1 + (KW - 1) * DILATION_W\n",

       "\n",

       "# [QUIZ 2/5] hiLoad, wiLoad = ?\n",

       "STRIDE_H = 1; STRIDE_W = 1\n",

       "hiLoad = None  # TODO: (HO - 1) * STRIDE_H + dilatedKH\n",

       "wiLoad = None  # TODO: (WO - 1) * STRIDE_W + dilatedKW\n",

       "\n",

       "# [QUIZ 3/5] al1Bytes = ?\n",

       "CI = 16\n",

       "al1Bytes = None  # TODO: hiLoad * wiLoad * CI * 2  (fp16 = 2 bytes)\n",

       "\n",

       "# [QUIZ 4/5] mAlign, al0Bytes = ?\n",

       "mAlign = None   # TODO: ((HO * WO + 15) // 16) * 16\n",

       "al0Bytes = None  # TODO: mAlign * kL0 * 2  (fp16 = 2 bytes)\n",

       "\n",

       "# [QUIZ 5/5] cl0Bytes = ?\n",

       "CO = 16\n",

       "nAlign = ((CO + 15) // 16) * 16\n",

       "cl0Bytes = None  # TODO: mAlign * nAlign * 4  (fp32 = 4 bytes, NOT 2!)\n",

       "\n",

       "print(\"Your answers:\")\n",

       "print(f\"  kAL1={kAL1}, kBL1={kBL1}, kL0={kL0}, mStep={mStep}\")\n",

       "print(f\"  dilatedKH={dilatedKH}, dilatedKW={dilatedKW}\")\n",

       "print(f\"  hiLoad={hiLoad}, wiLoad={wiLoad}\")\n",

       "print(f\"  al1Bytes={al1Bytes}, al0Bytes={al0Bytes}, cl0Bytes={cl0Bytes}\")"

      ]

     },

     {

      "cell_type": "markdown",

      "metadata": {},

      "source": [

       "完成填写后,执行以下命令查看答案:"

      ]

     },

     {

      "cell_type": "code",

      "execution_count": null,

      "metadata": {},

      "outputs": [],

      "source": [

       "!cat ./answer/01.03_answer.txt"

      ]

     }

    ],

    "metadata": {

     "kernelspec": {

      "display_name": "Python 3",

      "language": "python",

      "name": "python3"

     },

     "language_info": {

      "name": "python",

      "version": "3.8.0"

     }

    },

    "nbformat": 4,

    "nbformat_minor": 5

   }