已开启
【代码侦探Challenge05】新增 MulCustom 逐元素乘法算子代码提交 #2840
gcw_JQJBklQH创建于 23 天前
【代码侦探Challenge05】新增 MulCustom 逐元素乘法算子代码提交 #2840
已开启
共 3 个文件变更+298-0
| @@ -0,0 +1,10 @@ | |||
| 1 | +cmake_minimum_required(VERSION 3.16) | ||
| 2 | +find_package(ASC REQUIRED) | ||
| 3 | +project(kernel_samples LANGUAGES ASC CXX) | ||
| 4 | + | ||
| 5 | +add_executable(mul_test | ||
| 6 | + mul_custom.asc | ||
| 7 | +) | ||
| 8 | +target_compile_options(mul_test PRIVATE | ||
| 9 | + $<$<COMPILE_LANGUAGE:ASC>:--npu-arch=dav-2201> | ||
| 10 | +) | ||
| @@ -0,0 +1,279 @@ | |||
| 1 | +// mul_custom.asc | ||
| 2 | +// 逐元素乘法算子:z[i] = x[i] * y[i] | ||
| 3 | +// | ||
| 4 | +// 学习自第2章 Add 算子示例,完成以下部分: | ||
| 5 | +// 1. KernelMul 类成员变量声明 | ||
| 6 | +// 2. Init —— 初始化 TPipe、TQue、GlobalTensor | ||
| 7 | +// 3. Process —— 主循环调用 CopyIn → Compute → CopyOut | ||
| 8 | +// 4. CopyIn —— 从 GM 搬入数据到 UB | ||
| 9 | +// 5. Compute —— 执行 Mul 计算 | ||
| 10 | +// 6. CopyOut —— 将结果从 UB 搬回 GM | ||
| 11 | +// 7. mul_custom kernel 入口函数 | ||
| 12 | +// 8. kernel_mul host 侧函数 | ||
| 13 | +// 9. main 函数中调用 kernel_mul | ||
| 14 | + | ||
| 15 | +#include <cstdint> | ||
| 16 | +#include <iostream> | ||
| 17 | +#include <vector> | ||
| 18 | +#include <algorithm> | ||
| 19 | +#include <iterator> | ||
| 20 | +#include "acl/acl.h" | ||
| 21 | +#include "kernel_operator.h" | ||
| 22 | + | ||
| 23 | +constexpr uint32_t BUFFER_NUM = 2; // Double Buffer 队列深度 | ||
| 24 | + | ||
| 25 | +struct MulCustomTilingData | ||
| 26 | +{ | ||
| 27 | + uint32_t totalLength; | ||
| 28 | + uint32_t tileNum; | ||
| 29 | +}; | ||
| 30 | + | ||
| 31 | +class KernelMul { | ||
| 32 | +public: | ||
| 33 | + __aicore__ inline KernelMul() {} | ||
| 34 | + __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z, uint32_t totalLength, uint32_t tileNum) | ||
| 35 | + { | ||
| 36 | + // 多核数据切分:每个 AI Core 处理一段连续数据,核内再按双缓冲 tile 切分。 | ||
| 37 | + this->blockLength = totalLength / AscendC::GetBlockNum(); | ||
| 38 | + this->tileNum = tileNum; | ||
| 39 | + this->tileLength = this->blockLength / this->tileNum / BUFFER_NUM; | ||
| 40 | + | ||
| 41 | + // 根据当前核号计算本核在 GM 中的起始偏移,并设置三个 GlobalTensor 的起始地址和长度。 | ||
| 42 | + uint32_t blockOffset = this->blockLength * AscendC::GetBlockIdx(); | ||
| 43 | + xGm.SetGlobalBuffer((__gm__ float *)x + blockOffset, this->blockLength); | ||
| 44 | + yGm.SetGlobalBuffer((__gm__ float *)y + blockOffset, this->blockLength); | ||
| 45 | + zGm.SetGlobalBuffer((__gm__ float *)z + blockOffset, this->blockLength); | ||
| 46 | + | ||
| 47 | + // 为输入/输出队列分配内存:每个队列 BUFFER_NUM 个块,每块 tileLength * sizeof(float)。 | ||
| 48 | + pipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileLength * sizeof(float)); | ||
| 49 | + pipe.InitBuffer(inQueueY, BUFFER_NUM, this->tileLength * sizeof(float)); | ||
| 50 | + pipe.InitBuffer(outQueueZ, BUFFER_NUM, this->tileLength * sizeof(float)); | ||
| 51 | + } | ||
| 52 | + __aicore__ inline void Process() | ||
| 53 | + { | ||
| 54 | + // 循环次数 = tileNum * BUFFER_NUM,每次迭代执行 CopyIn → Compute → CopyOut。 | ||
| 55 | + int32_t loopCount = this->tileNum * BUFFER_NUM; | ||
| 56 | + for (int32_t i = 0; i < loopCount; i++) { | ||
| 57 | + CopyIn(i); | ||
| 58 | + Compute(i); | ||
| 59 | + CopyOut(i); | ||
| 60 | + } | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | +private: | ||
| 64 | + __aicore__ inline void CopyIn(int32_t progress) | ||
| 65 | + { | ||
| 66 | + // 从输入队列申请 UB 缓冲区。 | ||
| 67 | + AscendC::LocalTensor<float> xLocal = inQueueX.AllocTensor<float>(); | ||
| 68 | + AscendC::LocalTensor<float> yLocal = inQueueY.AllocTensor<float>(); | ||
| 69 | + | ||
| 70 | + // 从 GM 搬运 tileLength 个元素到 UB。 | ||
| 71 | + uint32_t offset = progress * this->tileLength; | ||
| 72 | + AscendC::DataCopy(xLocal, xGm[offset], this->tileLength); | ||
| 73 | + AscendC::DataCopy(yLocal, yGm[offset], this->tileLength); | ||
| 74 | + | ||
| 75 | + // 入队,标识数据搬运完成,可供 Compute 阶段 DeQue 使用。 | ||
| 76 | + inQueueX.EnQue(xLocal); | ||
| 77 | + inQueueY.EnQue(yLocal); | ||
| 78 | + } | ||
| 79 | + __aicore__ inline void Compute(int32_t progress) | ||
| 80 | + { | ||
| 81 | + // 从输入队列出队取回 xLocal、yLocal,并向输出队列申请 zLocal。 | ||
| 82 | + AscendC::LocalTensor<float> xLocal = inQueueX.DeQue<float>(); | ||
| 83 | + AscendC::LocalTensor<float> yLocal = inQueueY.DeQue<float>(); | ||
| 84 | + AscendC::LocalTensor<float> zLocal = outQueueZ.AllocTensor<float>(); | ||
| 85 | + | ||
| 86 | + // 逐元素乘法:zLocal[i] = xLocal[i] * yLocal[i]。 | ||
| 87 | + AscendC::Mul(zLocal, xLocal, yLocal, this->tileLength); | ||
| 88 | + | ||
| 89 | + // 计算结果入队供 CopyOut 使用,并释放输入缓冲区。 | ||
| 90 | + outQueueZ.EnQue<float>(zLocal); | ||
| 91 | + inQueueX.FreeTensor(xLocal); | ||
| 92 | + inQueueY.FreeTensor(yLocal); | ||
| 93 | + } | ||
| 94 | + __aicore__ inline void CopyOut(int32_t progress) | ||
| 95 | + { | ||
| 96 | + // 从输出队列出队取回计算结果 zLocal。 | ||
| 97 | + AscendC::LocalTensor<float> zLocal = outQueueZ.DeQue<float>(); | ||
| 98 | + | ||
| 99 | + // 将结果从 UB 搬回 GM。 | ||
| 100 | + uint32_t offset = progress * this->tileLength; | ||
| 101 | + AscendC::DataCopy(zGm[offset], zLocal, this->tileLength); | ||
| 102 | + | ||
| 103 | + // 释放输出缓冲区。 | ||
| 104 | + outQueueZ.FreeTensor(zLocal); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | +private: | ||
| 108 | + AscendC::TPipe pipe; | ||
| 109 | + AscendC::TQue<AscendC::TPosition::VECIN, BUFFER_NUM> inQueueX; | ||
| 110 | + AscendC::TQue<AscendC::TPosition::VECIN, BUFFER_NUM> inQueueY; | ||
| 111 | + AscendC::TQue<AscendC::TPosition::VECOUT, BUFFER_NUM> outQueueZ; | ||
| 112 | + AscendC::GlobalTensor<float> xGm; | ||
| 113 | + AscendC::GlobalTensor<float> yGm; | ||
| 114 | + AscendC::GlobalTensor<float> zGm; | ||
| 115 | + uint32_t blockLength; | ||
| 116 | + uint32_t tileNum; | ||
| 117 | + uint32_t tileLength; | ||
| 118 | +}; | ||
| 119 | + | ||
| 120 | +// Kernel 入口函数 | ||
| 121 | +__global__ __aicore__ void mul_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, MulCustomTilingData tiling) | ||
| 122 | +{ | ||
| 123 | + KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY); | ||
| 124 | + KernelMul op; | ||
| 125 | + op.Init(x, y, z, tiling.totalLength, tiling.tileNum); | ||
| 126 | + op.Process(); | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +// ----- Host 侧:kernel 直调封装 ----- | ||
| 130 | + | ||
| 131 | +// ACL 调用返回值检查,失败时打印错误信息并返回 false。 | ||
| 132 | +static bool CheckAclResult(aclError result, const char *operation) | ||
| 133 | +{ | ||
| 134 | + if (result == ACL_SUCCESS) { | ||
| 135 | + return true; | ||
| 136 | + } | ||
| 137 | + std::cerr << "[Error] " << operation << " failed, error code: " << result << std::endl; | ||
| 138 | + return false; | ||
| 139 | +} | ||
| 140 | + | ||
| 141 | +std::vector<float> kernel_mul(std::vector<float> &x, std::vector<float> &y) | ||
| 142 | +{ | ||
| 143 | + constexpr uint32_t blockDim = 8; | ||
| 144 | + constexpr uint32_t tileNum = 8; | ||
| 145 | + const uint32_t totalLength = static_cast<uint32_t>(x.size()); | ||
| 146 | + | ||
| 147 | + MulCustomTilingData tiling{totalLength, tileNum}; | ||
| 148 | + | ||
| 149 | + int32_t deviceId = 0; | ||
| 150 | + aclrtStream stream = nullptr; | ||
| 151 | + uint8_t *xDevice = nullptr; | ||
| 152 | + uint8_t *yDevice = nullptr; | ||
| 153 | + uint8_t *zDevice = nullptr; | ||
| 154 | + bool deviceSet = false; | ||
| 155 | + bool aclInitialized = false; | ||
| 156 | + | ||
| 157 | + // 资源释放 lambda:无论成功或失败,统一释放设备内存、销毁流、复位设备。 | ||
| 158 | + auto cleanup = [&]() { | ||
| 159 | + if (xDevice != nullptr) { | ||
| 160 | + aclrtFree(xDevice); | ||
| 161 | + } | ||
| 162 | + if (yDevice != nullptr) { | ||
| 163 | + aclrtFree(yDevice); | ||
| 164 | + } | ||
| 165 | + if (zDevice != nullptr) { | ||
| 166 | + aclrtFree(zDevice); | ||
| 167 | + } | ||
| 168 | + if (stream != nullptr) { | ||
| 169 | + aclrtDestroyStream(stream); | ||
| 170 | + } | ||
| 171 | + if (deviceSet) { | ||
| 172 | + aclrtResetDevice(deviceId); | ||
| 173 | + } | ||
| 174 | + if (aclInitialized) { | ||
| 175 | + aclFinalize(); | ||
| 176 | + } | ||
| 177 | + }; | ||
| 178 | + | ||
| 179 | + // 初始化 ACL。 | ||
| 180 | + if (!CheckAclResult(aclInit(nullptr), "aclInit")) { | ||
| 181 | + return {}; | ||
| 182 | + } | ||
| 183 | + aclInitialized = true; | ||
| 184 | + | ||
| 185 | + if (!CheckAclResult(aclrtSetDevice(deviceId), "aclrtSetDevice")) { | ||
| 186 | + cleanup(); | ||
| 187 | + return {}; | ||
| 188 | + } | ||
| 189 | + deviceSet = true; | ||
| 190 | + | ||
| 191 | + if (!CheckAclResult(aclrtCreateStream(&stream), "aclrtCreateStream")) { | ||
| 192 | + cleanup(); | ||
| 193 | + return {}; | ||
| 194 | + } | ||
| 195 | + | ||
| 196 | + // 申请 Device 内存。 | ||
| 197 | + size_t byteSize = static_cast<size_t>(totalLength) * sizeof(float); | ||
| 198 | + if (!CheckAclResult( | ||
| 199 | + aclrtMalloc(reinterpret_cast<void **>(&xDevice), byteSize, ACL_MEM_MALLOC_HUGE_FIRST), | ||
| 200 | + "aclrtMalloc(xDevice)") || | ||
| 201 | + !CheckAclResult( | ||
| 202 | + aclrtMalloc(reinterpret_cast<void **>(&yDevice), byteSize, ACL_MEM_MALLOC_HUGE_FIRST), | ||
| 203 | + "aclrtMalloc(yDevice)") || | ||
| 204 | + !CheckAclResult( | ||
| 205 | + aclrtMalloc(reinterpret_cast<void **>(&zDevice), byteSize, ACL_MEM_MALLOC_HUGE_FIRST), | ||
| 206 | + "aclrtMalloc(zDevice)")) { | ||
| 207 | + cleanup(); | ||
| 208 | + return {}; | ||
| 209 | + } | ||
| 210 | + | ||
| 211 | + // 将输入数据从 Host 拷贝到 Device(H2D)。 | ||
| 212 | + if (!CheckAclResult( | ||
| 213 | + aclrtMemcpy(xDevice, byteSize, x.data(), byteSize, ACL_MEMCPY_HOST_TO_DEVICE), | ||
| 214 | + "aclrtMemcpy(xHostToDevice)") || | ||
| 215 | + !CheckAclResult( | ||
| 216 | + aclrtMemcpy(yDevice, byteSize, y.data(), byteSize, ACL_MEMCPY_HOST_TO_DEVICE), | ||
| 217 | + "aclrtMemcpy(yHostToDevice)")) { | ||
| 218 | + cleanup(); | ||
| 219 | + return {}; | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + // 调用 kernel。 | ||
| 223 | + mul_custom<<<blockDim, nullptr, stream>>>(xDevice, yDevice, zDevice, tiling); | ||
| 224 | + if (!CheckAclResult(aclrtSynchronizeStream(stream), "aclrtSynchronizeStream")) { | ||
| 225 | + cleanup(); | ||
| 226 | + return {}; | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + // 将结果从 Device 拷贝回 Host(D2H)。 | ||
| 230 | + std::vector<float> z(totalLength, 0.0f); | ||
| 231 | + if (!CheckAclResult( | ||
| 232 | + aclrtMemcpy(z.data(), byteSize, zDevice, byteSize, ACL_MEMCPY_DEVICE_TO_HOST), | ||
| 233 | + "aclrtMemcpy(zDeviceToHost)")) { | ||
| 234 | + cleanup(); | ||
| 235 | + return {}; | ||
| 236 | + } | ||
| 237 | + | ||
| 238 | + cleanup(); | ||
| 239 | + return z; | ||
| 240 | +} | ||
| 241 | + | ||
| 242 | +// ----- 验证与主函数 ----- | ||
| 243 | + | ||
| 244 | +uint32_t VerifyResult(std::vector<float> &output, std::vector<float> &golden) | ||
| 245 | +{ | ||
| 246 | + auto printTensor = [](std::vector<float> &tensor, const char *name) { | ||
| 247 | + constexpr size_t maxPrintSize = 20; | ||
| 248 | + std::cout << name << ": "; | ||
| 249 | + std::copy(tensor.begin(), tensor.begin() + std::min(tensor.size(), maxPrintSize), | ||
| 250 | + std::ostream_iterator<float>(std::cout, " ")); | ||
| 251 | + if (tensor.size() > maxPrintSize) { | ||
| 252 | + std::cout << "..."; | ||
| 253 | + } | ||
| 254 | + std::cout << std::endl; | ||
| 255 | + }; | ||
| 256 | + printTensor(output, "Output"); | ||
| 257 | + printTensor(golden, "Golden"); | ||
| 258 | + if (output.size() == golden.size() && std::equal(golden.begin(), golden.end(), output.begin())) { | ||
| 259 | + std::cout << "[Success] Case accuracy is verification passed." << std::endl; | ||
| 260 | + return 0; | ||
| 261 | + } else { | ||
| 262 | + std::cout << "[Failed] Case accuracy is verification failed!" << std::endl; | ||
| 263 | + return 1; | ||
| 264 | + } | ||
| 265 | +} | ||
| 266 | + | ||
| 267 | +int32_t main(int32_t argc, char *argv[]) | ||
| 268 | +{ | ||
| 269 | + constexpr uint32_t totalLength = 8 * 2048; | ||
| 270 | + constexpr float valueX = 1.2f; | ||
| 271 | + constexpr float valueY = 2.3f; | ||
| 272 | + std::vector<float> x(totalLength, valueX); | ||
| 273 | + std::vector<float> y(totalLength, valueY); | ||
| 274 | + | ||
| 275 | + std::vector<float> output = kernel_mul(x, y); | ||
| 276 | + | ||
| 277 | + std::vector<float> golden(totalLength, valueX * valueY); | ||
| 278 | + return VerifyResult(output, golden); | ||
| 279 | +} | ||
| @@ -0,0 +1,9 @@ | |||
| 1 | +#!/bin/bash | ||
| 2 | +# 激活cann环境,可根据实际情况修改,在线环境一般不用修改 | ||
| 3 | +source "$ASCEND_TOOLKIT_HOME/set_env.sh" | ||
| 4 | +mkdir -p build | ||
| 5 | +export ASC_DIR="$ASCEND_HOME_PATH/aarch64-linux/tikcpp/ascendc_kernel_cmake/" | ||
| 6 | +cd build/ && \ | ||
| 7 | +cmake .. && \ | ||
| 8 | +make && \ | ||
| 9 | +./mul_test | ||