已合并
【CANN开源开放社区任务】【社区任务】AscendC实现Polar算子贡献 #2923
bububu创建于 5月23日
【CANN开源开放社区任务】【社区任务】AscendC实现Polar算子贡献 #2923
已合并
共 39 个文件变更+4650-0
| @@ -0,0 +1,11 @@ | |||
| 1 | +# ---------------------------------------------------------------------------- | ||
| 2 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | +# CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | +# Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | +# See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | +# ---------------------------------------------------------------------------- | ||
| 10 | + | ||
| 11 | +add_all_modules_sources(OPTYPE polar ACLNNTYPE aclnn_exclude) | ||
| @@ -0,0 +1,84 @@ | |||
| 1 | +# Polar | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +| 产品 | 是否支持 | | ||
| 6 | +| :----------------------------------------------------------- | :------: | | ||
| 7 | +| <term>Atlas A3 训练系列产品/Atlas A3 推理系列产品</term> | √ | | ||
| 8 | +| <term>Atlas A2 训练系列产品/Atlas A2 推理系列产品</term> | √ | | ||
| 9 | +| <term>Ascend 950PR/Ascend 950DT</term> | × | | ||
| 10 | +| <term>Atlas 200I/500 A2 推理产品</term> | × | | ||
| 11 | +| <term>Atlas 推理系列产品</term> | × | | ||
| 12 | +| <term>Atlas 训练系列产品</term> | × | | ||
| 13 | + | ||
| 14 | +## 功能说明 | ||
| 15 | + | ||
| 16 | +- 算子功能:由模长(abs)与幅角(angle)构造极坐标复数张量,对应 PyTorch 接口 `torch.polar(abs, angle)`。 | ||
| 17 | + | ||
| 18 | +- 计算公式: | ||
| 19 | + | ||
| 20 | + $$ | ||
| 21 | + out_i = input_i \times (\cos(angle_i) + i \cdot \sin(angle_i)) | ||
| 22 | + $$ | ||
| 23 | + | ||
| 24 | +## 参数说明 | ||
| 25 | + | ||
| 26 | +<table style="undefined;table-layout: fixed; width: 980px"><colgroup> | ||
| 27 | + <col style="width: 100px"> | ||
| 28 | + <col style="width: 150px"> | ||
| 29 | + <col style="width: 280px"> | ||
| 30 | + <col style="width: 330px"> | ||
| 31 | + <col style="width: 120px"> | ||
| 32 | + </colgroup> | ||
| 33 | + <thead> | ||
| 34 | + <tr> | ||
| 35 | + <th>参数名</th> | ||
| 36 | + <th>输入/输出/属性</th> | ||
| 37 | + <th>描述</th> | ||
| 38 | + <th>数据类型</th> | ||
| 39 | + <th>数据格式</th> | ||
| 40 | + </tr></thead> | ||
| 41 | + <tbody> | ||
| 42 | + <tr> | ||
| 43 | + <td>input</td> | ||
| 44 | + <td>输入</td> | ||
| 45 | + <td>极坐标模长分量,公式中的input。</td> | ||
| 46 | + <td>FLOAT</td> | ||
| 47 | + <td>ND</td> | ||
| 48 | + </tr> | ||
| 49 | + <tr> | ||
| 50 | + <td>angle</td> | ||
| 51 | + <td>输入</td> | ||
| 52 | + <td>极坐标幅角(弧度),公式中的angle。</td> | ||
| 53 | + <td>FLOAT</td> | ||
| 54 | + <td>ND</td> | ||
| 55 | + </tr> | ||
| 56 | + <tr> | ||
| 57 | + <td>out</td> | ||
| 58 | + <td>输出</td> | ||
| 59 | + <td>计算结果,公式中的out;shape为input与angle广播后的shape。</td> | ||
| 60 | + <td>COMPLEX64</td> | ||
| 61 | + <td>ND</td> | ||
| 62 | + </tr> | ||
| 63 | + </tbody></table> | ||
| 64 | + | ||
| 65 | +## 约束说明 | ||
| 66 | + | ||
| 67 | +- input与angle的数据类型必须一致(FLOAT)。 | ||
| 68 | +- input与angle的shape满足NumPy广播关系,out的shape为两者广播后的shape。 | ||
| 69 | +- input、angle的维度数不超过8维。 | ||
| 70 | + | ||
| 71 | +## 调用说明 | ||
| 72 | + | ||
| 73 | +| 调用方式 | 调用样例 | 说明 | | ||
| 74 | +|--------------|------------------------------------------------------------------------|--------------------------------------------------------------| | ||
| 75 | +| aclnn调用 | [test_aclnn_polar](./examples/test_aclnn_polar.cpp) | 通过[aclnnPolar](./docs/aclnnPolar.md)接口方式调用Polar算子。 | | ||
| 76 | +| 图模式调用 | | | | ||
| 77 | + | ||
| 78 | +本目录包含Polar算子的aclnn接口及AscendC实现;关于该算子的设计与自测,请参考[设计文档](./docs/design.md)与[自测报告](./tests/自测报告.md)。 | ||
F | |||
| 79 | + | ||
| 80 | +## 贡献说明 | ||
| 81 | + | ||
| 82 | +| 贡献者 | 贡献方 | 贡献算子 | 贡献时间 | 贡献内容 | | ||
| 83 | +| ---- | ---- | ---- | ---- | ---- | | ||
| 84 | +| 开源社区贡献者 | 开源社区 | Polar | 2026/06/30 | Polar算子适配开源仓 | | ||
| @@ -0,0 +1,379 @@ | |||
| 1 | +# aclnnPolar | ||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +| 产品 | 是否支持 | | ||
| 6 | +| :----------------------------------------------------------- | :------: | | ||
| 7 | +| <term>Atlas A3 训练系列产品/Atlas A3 推理系列产品</term> | √ | | ||
| 8 | +| <term>Atlas A2 训练系列产品/Atlas A2 推理系列产品</term> | √ | | ||
| 9 | +| <term>Ascend 950PR/Ascend 950DT</term> | × | | ||
| 10 | +| <term>Atlas 200I/500 A2 推理产品</term> | × | | ||
| 11 | +| <term>Atlas 推理系列产品</term> | × | | ||
| 12 | +| <term>Atlas 训练系列产品</term> | × | | ||
| 13 | + | ||
| 14 | +## 功能说明 | ||
| 15 | + | ||
| 16 | +- 接口功能:由模长(abs)与幅角(angle)构造极坐标复数张量,对应 PyTorch 接口 `torch.polar(abs, angle)`。 | ||
| 17 | + | ||
| 18 | +- 计算公式: | ||
| 19 | + | ||
| 20 | + $$ | ||
| 21 | + out_i = input_i \times (\cos(angle_i) + i \cdot \sin(angle_i)) | ||
| 22 | + $$ | ||
| 23 | + | ||
| 24 | +## 函数原型 | ||
| 25 | + | ||
| 26 | +每个算子分为[两段式接口](../../../../docs/zh/context/两段式接口.md),必须先调用 “aclnnPolarGetWorkspaceSize” 接口获取入参并根据计算流程计算所需workspace大小,再调用 “aclnnPolar” 接口执行计算。 | ||
| 27 | + | ||
| 28 | +```Cpp | ||
| 29 | +aclnnStatus aclnnPolarGetWorkspaceSize( | ||
| 30 | + const aclTensor* input, | ||
| 31 | + const aclTensor* angle, | ||
| 32 | + aclTensor* out, | ||
| 33 | + uint64_t* workspaceSize, | ||
| 34 | + aclOpExecutor** executor) | ||
| 35 | +``` | ||
| 36 | + | ||
| 37 | +```Cpp | ||
| 38 | +aclnnStatus aclnnPolar( | ||
| 39 | + void* workspace, | ||
| 40 | + uint64_t workspaceSize, | ||
| 41 | + aclOpExecutor* executor, | ||
| 42 | + const aclrtStream stream) | ||
| 43 | +``` | ||
| 44 | + | ||
| 45 | +## aclnnPolarGetWorkspaceSize | ||
| 46 | + | ||
| 47 | +- **参数说明:** | ||
| 48 | + | ||
| 49 | + <table style="undefined;table-layout: fixed; width: 1547px"><colgroup> | ||
| 50 | + <col style="width: 150px"> | ||
| 51 | + <col style="width: 121px"> | ||
| 52 | + <col style="width: 206px"> | ||
| 53 | + <col style="width: 456px"> | ||
| 54 | + <col style="width: 211px"> | ||
| 55 | + <col style="width: 122px"> | ||
| 56 | + <col style="width: 135px"> | ||
| 57 | + <col style="width: 146px"> | ||
| 58 | + </colgroup> | ||
| 59 | + <thead> | ||
| 60 | + <tr> | ||
| 61 | + <th>参数名</th> | ||
| 62 | + <th>输入/输出</th> | ||
| 63 | + <th>描述</th> | ||
| 64 | + <th>使用说明</th> | ||
| 65 | + <th>数据类型</th> | ||
| 66 | + <th>数据格式</th> | ||
| 67 | + <th>维度(shape)</th> | ||
| 68 | + <th>非连续Tensor</th> | ||
| 69 | + </tr></thead> | ||
| 70 | + <tbody> | ||
| 71 | + <tr> | ||
| 72 | + <td>input</td> | ||
| 73 | + <td>输入</td> | ||
| 74 | + <td>公式中的input(极坐标模长分量)。</td> | ||
| 75 | + <td> | ||
| 76 | + <ul> | ||
| 77 | + <li>数据类型需与angle一致。</li> | ||
| 78 | + <li>input与angle的shape满足<a href="../../../../docs/zh/context/broadcast关系.md" target="_blank">broadcast关系</a>。</li> | ||
| 79 | + </ul> | ||
| 80 | + </td> | ||
| 81 | + <td>FLOAT</td> | ||
| 82 | + <td>ND</td> | ||
| 83 | + <td>0-8</td> | ||
| 84 | + <td>√</td> | ||
| 85 | + </tr> | ||
| 86 | + <tr> | ||
| 87 | + <td>angle</td> | ||
| 88 | + <td>输入</td> | ||
| 89 | + <td>公式中的angle(极坐标幅角,弧度)。</td> | ||
| 90 | + <td> | ||
| 91 | + <ul> | ||
| 92 | + <li>数据类型需与input一致。</li> | ||
| 93 | + <li>input与angle的shape满足<a href="../../../../docs/zh/context/broadcast关系.md" target="_blank">broadcast关系</a>。</li> | ||
| 94 | + </ul> | ||
| 95 | + </td> | ||
| 96 | + <td>FLOAT</td> | ||
| 97 | + <td>ND</td> | ||
| 98 | + <td>0-8</td> | ||
| 99 | + <td>√</td> | ||
| 100 | + </tr> | ||
| 101 | + <tr> | ||
| 102 | + <td>out</td> | ||
| 103 | + <td>输出</td> | ||
| 104 | + <td>公式中的out。</td> | ||
| 105 | + <td>out的shape与input、angle做broadcast后的shape一致;数据类型恒为COMPLEX64。</td> | ||
| 106 | + <td>COMPLEX64</td> | ||
| 107 | + <td>ND</td> | ||
| 108 | + <td>0-8</td> | ||
| 109 | + <td>√</td> | ||
| 110 | + </tr> | ||
| 111 | + <tr> | ||
| 112 | + <td>workspaceSize</td> | ||
| 113 | + <td>输出</td> | ||
| 114 | + <td>返回需要在Device侧申请的workspace大小。</td> | ||
| 115 | + <td>-</td> | ||
| 116 | + <td>-</td> | ||
| 117 | + <td>-</td> | ||
| 118 | + <td>-</td> | ||
| 119 | + <td>-</td> | ||
| 120 | + </tr> | ||
| 121 | + <tr> | ||
| 122 | + <td>executor</td> | ||
| 123 | + <td>输出</td> | ||
| 124 | + <td>返回op执行器,包含了算子计算流程。</td> | ||
| 125 | + <td>-</td> | ||
| 126 | + <td>-</td> | ||
| 127 | + <td>-</td> | ||
| 128 | + <td>-</td> | ||
| 129 | + <td>-</td> | ||
| 130 | + </tr> | ||
| 131 | + </tbody> | ||
| 132 | + </table> | ||
| 133 | + | ||
| 134 | +- **返回值:** | ||
| 135 | + | ||
| 136 | + aclnnStatus:返回状态码,具体参见[aclnn返回码](../../../../docs/zh/context/aclnn返回码.md)。 | ||
| 137 | + | ||
| 138 | + 第一段接口完成入参校验,出现如下场景时报错: | ||
| 139 | + | ||
| 140 | + <table style="undefined;table-layout: fixed; width: 1166px"><colgroup> | ||
| 141 | + <col style="width: 267px"> | ||
| 142 | + <col style="width: 124px"> | ||
| 143 | + <col style="width: 775px"> | ||
| 144 | + </colgroup> | ||
| 145 | + <thead> | ||
| 146 | + <tr> | ||
| 147 | + <th>返回值</th> | ||
| 148 | + <th>错误码</th> | ||
| 149 | + <th>描述</th> | ||
| 150 | + </tr></thead> | ||
| 151 | + <tbody> | ||
| 152 | + <tr> | ||
| 153 | + <td>ACLNN_ERR_PARAM_NULLPTR</td> | ||
| 154 | + <td>161001</td> | ||
| 155 | + <td>传入的input、angle、out是空指针。</td> | ||
| 156 | + </tr> | ||
| 157 | + <tr> | ||
| 158 | + <td rowspan="5">ACLNN_ERR_PARAM_INVALID</td> | ||
| 159 | + <td rowspan="5">161002</td> | ||
| 160 | + <td>input、angle的数据类型或数据格式不在支持的范围之内。</td> | ||
| 161 | + </tr> | ||
| 162 | + <tr> | ||
| 163 | + <td>input与angle的数据类型不一致。</td> | ||
| 164 | + </tr> | ||
| 165 | + <tr> | ||
| 166 | + <td>input或angle的shape超过8维。</td> | ||
| 167 | + </tr> | ||
| 168 | + <tr> | ||
| 169 | + <td>input与angle的shape不满足broadcast推导关系。</td> | ||
| 170 | + </tr> | ||
| 171 | + <tr> | ||
| 172 | + <td>out的shape与input、angle做broadcast后的shape不一致,或out的数据类型不是COMPLEX64。</td> | ||
| 173 | + </tr> | ||
| 174 | + </tbody> | ||
| 175 | + </table> | ||
| 176 | + | ||
| 177 | +## aclnnPolar | ||
| 178 | + | ||
| 179 | +- **参数说明:** | ||
| 180 | + | ||
| 181 | + <table style="undefined;table-layout: fixed; width: 1166px"><colgroup> | ||
| 182 | + <col style="width: 173px"> | ||
| 183 | + <col style="width: 133px"> | ||
| 184 | + <col style="width: 860px"> | ||
| 185 | + </colgroup> | ||
| 186 | + <thead> | ||
| 187 | + <tr> | ||
| 188 | + <th>参数名</th> | ||
| 189 | + <th>输入/输出</th> | ||
| 190 | + <th>描述</th> | ||
| 191 | + </tr></thead> | ||
| 192 | + <tbody> | ||
| 193 | + <tr> | ||
| 194 | + <td>workspace</td> | ||
| 195 | + <td>输入</td> | ||
| 196 | + <td>在Device侧申请的workspace内存地址。</td> | ||
| 197 | + </tr> | ||
| 198 | + <tr> | ||
| 199 | + <td>workspaceSize</td> | ||
| 200 | + <td>输入</td> | ||
| 201 | + <td>在Device侧申请的workspace大小,由第一段接口aclnnPolarGetWorkspaceSize获取。</td> | ||
| 202 | + </tr> | ||
| 203 | + <tr> | ||
| 204 | + <td>executor</td> | ||
| 205 | + <td>输入</td> | ||
| 206 | + <td>op执行器,包含了算子计算流程。</td> | ||
| 207 | + </tr> | ||
| 208 | + <tr> | ||
| 209 | + <td>stream</td> | ||
| 210 | + <td>输入</td> | ||
| 211 | + <td>指定执行任务的Stream。</td> | ||
| 212 | + </tr> | ||
| 213 | + </tbody> | ||
| 214 | + </table> | ||
| 215 | + | ||
| 216 | +- **返回值:** | ||
| 217 | + | ||
| 218 | + aclnnStatus:返回状态码,具体参见[aclnn返回码](../../../../docs/zh/context/aclnn返回码.md)。 | ||
| 219 | + | ||
| 220 | +## 约束说明 | ||
| 221 | + | ||
| 222 | +- input与angle的数据类型必须一致(均为FLOAT)。 | ||
| 223 | +- out的数据类型恒为COMPLEX64(与input无关)。 | ||
| 224 | +- input、angle的维度数不超过8维,数据格式仅支持ND。 | ||
| 225 | +- 确定性计算:aclnnPolar默认确定性实现。 | ||
| 226 | + | ||
| 227 | +## 调用示例 | ||
| 228 | + | ||
| 229 | +示例代码如下,仅供参考,具体编译和执行过程请参考[编译与运行样例](../../../../docs/zh/context/编译与运行样例.md)。 | ||
| 230 | + | ||
| 231 | +```Cpp | ||
| 232 | +#include <iostream> | ||
| 233 | +#include <vector> | ||
| 234 | +#include "acl/acl.h" | ||
| 235 | +#include "aclnnop/aclnn_polar.h" | ||
| 236 | + | ||
| 237 | +#define CHECK_RET(cond, return_expr) \ | ||
| 238 | + do { \ | ||
| 239 | + if (!(cond)) { \ | ||
| 240 | + return_expr; \ | ||
| 241 | + } \ | ||
| 242 | + } while (0) | ||
| 243 | + | ||
| 244 | +#define LOG_PRINT(message, ...) \ | ||
| 245 | + do { \ | ||
| 246 | + printf(message, ##__VA_ARGS__); \ | ||
| 247 | + } while (0) | ||
| 248 | + | ||
| 249 | +int64_t GetShapeSize(const std::vector<int64_t>& shape) { | ||
| 250 | + int64_t shapeSize = 1; | ||
| 251 | + for (auto i : shape) { | ||
| 252 | + shapeSize *= i; | ||
| 253 | + } | ||
| 254 | + return shapeSize; | ||
| 255 | +} | ||
| 256 | + | ||
| 257 | +int Init(int32_t deviceId, aclrtStream* stream) { | ||
| 258 | + // 固定写法,资源初始化 | ||
| 259 | + auto ret = aclInit(nullptr); | ||
| 260 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret); | ||
| 261 | + ret = aclrtSetDevice(deviceId); | ||
| 262 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret); | ||
| 263 | + ret = aclrtCreateStream(stream); | ||
| 264 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret); | ||
| 265 | + return 0; | ||
| 266 | +} | ||
| 267 | + | ||
| 268 | +template <typename T> | ||
| 269 | +int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, | ||
| 270 | + aclDataType dataType, aclTensor** tensor) { | ||
| 271 | + auto size = GetShapeSize(shape) * sizeof(T); | ||
| 272 | + // 调用aclrtMalloc申请device侧内存 | ||
| 273 | + auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); | ||
| 274 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret); | ||
| 275 | + // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上 | ||
| 276 | + ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); | ||
| 277 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret); | ||
| 278 | + | ||
| 279 | + // 计算连续tensor的strides | ||
| 280 | + std::vector<int64_t> strides(shape.size(), 1); | ||
| 281 | + for (int64_t i = shape.size() - 2; i >= 0; i--) { | ||
| 282 | + strides[i] = shape[i + 1] * strides[i + 1]; | ||
| 283 | + } | ||
| 284 | + | ||
| 285 | + // 调用aclCreateTensor接口创建aclTensor | ||
| 286 | + *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, | ||
| 287 | + shape.data(), shape.size(), *deviceAddr); | ||
| 288 | + return 0; | ||
| 289 | +} | ||
| 290 | + | ||
| 291 | +int main() { | ||
| 292 | + // 1. (固定写法)device/stream初始化,参考acl API手册 | ||
| 293 | + int32_t deviceId = 0; | ||
| 294 | + aclrtStream stream; | ||
| 295 | + auto ret = Init(deviceId, &stream); | ||
| 296 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); | ||
| 297 | + | ||
| 298 | + // 2. 构造输入与输出,需要根据API的接口自定义构造 | ||
| 299 | + std::vector<int64_t> inputShape = {4, 2}; | ||
| 300 | + std::vector<int64_t> angleShape = {4, 2}; | ||
| 301 | + std::vector<int64_t> outShape = {4, 2}; | ||
| 302 | + void* inputDeviceAddr = nullptr; | ||
| 303 | + void* angleDeviceAddr = nullptr; | ||
| 304 | + void* outDeviceAddr = nullptr; | ||
| 305 | + aclTensor* input = nullptr; | ||
| 306 | + aclTensor* angle = nullptr; | ||
| 307 | + aclTensor* out = nullptr; | ||
| 308 | + std::vector<float> inputHostData = {1, 1, 1, 1, 2, 2, 2, 2}; | ||
| 309 | + std::vector<float> angleHostData = {0, 1.5707964f, 3.1415927f, -1.5707964f, 0, 1.5707964f, 3.1415927f, -1.5707964f}; | ||
| 310 | + | ||
| 311 | + // 创建input/angle aclTensor(FLOAT) | ||
| 312 | + ret = CreateAclTensor(inputHostData, inputShape, &inputDeviceAddr, aclDataType::ACL_FLOAT, &input); | ||
| 313 | + CHECK_RET(ret == ACL_SUCCESS, return ret); | ||
| 314 | + ret = CreateAclTensor(angleHostData, angleShape, &angleDeviceAddr, aclDataType::ACL_FLOAT, &angle); | ||
| 315 | + CHECK_RET(ret == ACL_SUCCESS, return ret); | ||
| 316 | + | ||
| 317 | + // 创建out aclTensor(COMPLEX64:每元素 = real/imag 两个 float) | ||
| 318 | + int64_t outNum = GetShapeSize(outShape); | ||
| 319 | + std::vector<float> outHostData(outNum * 2, 0); | ||
| 320 | + size_t outByte = static_cast<size_t>(outNum) * 2 * sizeof(float); | ||
| 321 | + ret = aclrtMalloc(&outDeviceAddr, outByte, ACL_MEM_MALLOC_HUGE_FIRST); | ||
| 322 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret); | ||
| 323 | + ret = aclrtMemcpy(outDeviceAddr, outByte, outHostData.data(), outByte, ACL_MEMCPY_HOST_TO_DEVICE); | ||
| 324 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret); | ||
| 325 | + std::vector<int64_t> outStrides(outShape.size(), 1); | ||
| 326 | + for (int64_t i = outShape.size() - 2; i >= 0; i--) { | ||
| 327 | + outStrides[i] = outShape[i + 1] * outStrides[i + 1]; | ||
| 328 | + } | ||
| 329 | + out = aclCreateTensor(outShape.data(), outShape.size(), aclDataType::ACL_COMPLEX64, outStrides.data(), 0, | ||
| 330 | + aclFormat::ACL_FORMAT_ND, outShape.data(), outShape.size(), outDeviceAddr); | ||
| 331 | + | ||
| 332 | + // 3. 调用CANN算子库API | ||
| 333 | + uint64_t workspaceSize = 0; | ||
| 334 | + aclOpExecutor* executor; | ||
| 335 | + // 调用aclnnPolar第一段接口 | ||
| 336 | + ret = aclnnPolarGetWorkspaceSize(input, angle, out, &workspaceSize, &executor); | ||
| 337 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnPolarGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); | ||
| 338 | + // 根据第一段接口计算出的workspaceSize申请device内存 | ||
| 339 | + void* workspaceAddr = nullptr; | ||
| 340 | + if (workspaceSize > 0) { | ||
| 341 | + ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); | ||
| 342 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); | ||
| 343 | + } | ||
| 344 | + // 调用aclnnPolar第二段接口 | ||
| 345 | + ret = aclnnPolar(workspaceAddr, workspaceSize, executor, stream); | ||
| 346 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnPolar failed. ERROR: %d\n", ret); return ret); | ||
| 347 | + | ||
| 348 | + // 4. (固定写法)同步等待任务执行结束 | ||
| 349 | + ret = aclrtSynchronizeStream(stream); | ||
| 350 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); | ||
| 351 | + | ||
| 352 | + // 5. 获取输出的值(complex64:每元素 real/imag 两个 float) | ||
| 353 | + std::vector<float> resultData(outNum * 2, 0); | ||
| 354 | + ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, outByte, | ||
| 355 | + ACL_MEMCPY_DEVICE_TO_HOST); | ||
| 356 | + CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); | ||
| 357 | + for (int64_t i = 0; i < outNum; i++) { | ||
| 358 | + LOG_PRINT("out[%ld] = %f + %fi\n", i, resultData[2 * i], resultData[2 * i + 1]); | ||
| 359 | + } | ||
| 360 | + | ||
| 361 | + // 6. 释放aclTensor,需要根据具体API的接口定义修改 | ||
| 362 | + aclDestroyTensor(input); | ||
| 363 | + aclDestroyTensor(angle); | ||
| 364 | + aclDestroyTensor(out); | ||
| 365 | + | ||
| 366 | + // 7. 释放device资源 | ||
| 367 | + aclrtFree(inputDeviceAddr); | ||
| 368 | + aclrtFree(angleDeviceAddr); | ||
| 369 | + aclrtFree(outDeviceAddr); | ||
| 370 | + if (workspaceSize > 0) { | ||
| 371 | + aclrtFree(workspaceAddr); | ||
| 372 | + } | ||
| 373 | + aclrtDestroyStream(stream); | ||
| 374 | + aclrtResetDevice(deviceId); | ||
| 375 | + aclFinalize(); | ||
| 376 | + | ||
| 377 | + return 0; | ||
| 378 | +} | ||
| 379 | +``` | ||
| @@ -0,0 +1,239 @@ | |||
| 1 | +# 需求背景(required) | ||
| 2 | + | ||
| 3 | +## 需求来源 | ||
| 4 | + | ||
| 5 | +昇腾 CANN 训练营第二季社区任务(序号 **04-5 Polar**):基于 Ascend C 实现 `aclnnPolar`,对齐开源仓 l0 拼接参考实现并**新增广播支持**,验收通过后贡献至 `cann/ops-math`。 | ||
| 6 | + | ||
| 7 | +## 背景介绍 | ||
| 8 | + | ||
| 9 | +`torch.polar(abs, angle)` 由极坐标构造复数:`out = input·(cos(angle)+i·sin(angle))`,即 `out.real=input·cos(angle)`、`out.imag=input·sin(angle)`。 | ||
| 10 | + | ||
| 11 | +### Polar 算子实现优化 | ||
| 12 | + | ||
| 13 | +> 本算子**无 TBE 历史实现**(任务书:"暂无 tbe 实现,和 `aclnn_polar.cpp` 对齐即可"),以**开源仓 l0 拼接参考实现**为对齐基准与性能基线。 | ||
| 14 | + | ||
| 15 | +参考源码:`cann/ops-math` 仓 `math/complex/op_host/op_api/aclnn_polar.cpp`(https://gitcode.com/cann/ops-math ),依赖 l0 API `sin.h/cos.h/mul.h/complex.h`(`l0op::Complex` 为已注册设备算子)。系统 `libopapi.so` 已导出 `aclnnPolar`/`aclnnPolarGetWorkspaceSize`。 | ||
| 16 | + | ||
| 17 | +### Polar 参考(l0)实现现状分析 | ||
| 18 | + | ||
| 19 | +| 参数 | 含义 | 支持 dtype | 约束 | 形状 | | ||
| 20 | +| --- | --- | --- | --- | --- | | ||
| 21 | +| input | 模长 abs | float32 | 与 angle 同 dtype | ≤8维 | | ||
| 22 | +| angle | 角度(弧度) | float32 | 与 input 同 dtype | ≤8维,可与 input 广播 | | ||
| 23 | +| out | 复数结果 | complex64 | 恒 complex64 | broadcast(input,angle) | | ||
| 24 | + | ||
| 25 | +**实现逻辑**(与源码一致):`aclnnPolarGetWorkspaceSize` 为纯 host 端 l0 拼接 —— `CheckParams`(空指针/dtype/广播 shape) → `Contiguous`×2 → `Sin(angle)`、`Cos(angle)` → `Mul(·,input)`×2 得虚/实部 → `Complex` → `ViewCopy`;约束 `DTYPE={DT_FLOAT}`、`OUTPUT={DT_COMPLEX64}`、dtype 须匹配、`MAX_DIM=8`、广播经 `OP_CHECK_BROADCAST_AND_INFER_SHAPE`。 | ||
| 26 | + | ||
| 27 | +**核心问题**:拆成 `Contiguous→Sin→Cos→Mul→Mul→Complex` 约 6 个独立设备算子,每个有 kernel 启动 dispatch 开销,中间结果必经 GM↔UB 搬运(≥10 次),小 shape 下启动延迟远超计算、带宽浪费。Ascend C 单 kernel 融合可解:CopyIn 一次搬入、Compute 在 UB 链式完成 `Cos→Sin→Mul→交织`、中间结果驻留 UB、CopyOut 一次写出 —— **1 次启动、2 次 GM↔UB 搬运**,广播在单 kernel CopyIn 内完成。 | ||
| 28 | + | ||
| 29 | +**参考(l0)实现流程图**(与源码逻辑一致,重点关注): | ||
| 30 | + | ||
| 31 | +```mermaid | ||
| 32 | +flowchart TD | ||
| 33 | + A["input, angle, out"] --> B{"CheckParams: null/dtype/bcast"} | ||
| 34 | + B -->|Fail| Z["error code"] | ||
| 35 | + B -->|Pass| C{"empty?"} | ||
| 36 | + C -->|Yes| Y["ws=0, return"] | ||
| 37 | + C -->|No| D["Contiguous x2"] | ||
| 38 | + D --> F["Sin(angle)"] | ||
| 39 | + D --> G["Cos(angle)"] | ||
| 40 | + F --> H["Mul -> imag"] | ||
| 41 | + G --> I["Mul -> real"] | ||
| 42 | + H --> J["Complex -> output"] | ||
| 43 | + I --> J | ||
| 44 | + J --> K["ViewCopy -> out"] | ||
| 45 | +``` | ||
| 46 | + | ||
| 47 | +### 算子功能规格与原型 | ||
| 48 | + | ||
| 49 | +| 名称 | 类别 | dtype | format | 介绍 | | ||
| 50 | +|------|------|-------|--------|------| | ||
| 51 | +| input | 输入 | float32 | ND | 极坐标模长 abs | | ||
| 52 | +| angle | 输入 | float32 | ND | 极坐标幅角(弧度),dtype 须与 input 一致 | | ||
| 53 | +| out | 输出 | complex64 | ND | 复数 tensor,real/imag 交替存储 | | ||
| 54 | + | ||
| 55 | +数学公式 `out=input·cos(angle)+i·input·sin(angle)`;无属性(attr)。支持广播(input 与 angle 维度可不一致,numpy 规则;**本任务新增功能点**)。 | ||
| 56 | + | ||
| 57 | +### 相关约束 | ||
| 58 | + | ||
| 59 | +Atlas A2 训练系列产品(ascend910b) / Atlas A3 训练系列产品(ascend910_93) 双平台支持;input 与 angle dtype 必须一致(float32);≤8 维;angle 为弧度无范围限制(周期函数);不支持 fp16/bf16/fp64/complex128(任务范围外,参考亦不支持)。 | ||
| 60 | + | ||
| 61 | +--- | ||
| 62 | + | ||
| 63 | +# 需求分析(required) | ||
| 64 | + | ||
| 65 | +## 外部组件依赖 | ||
| 66 | + | ||
| 67 | +不涉及外部组件依赖。 | ||
| 68 | + | ||
| 69 | +## 内部适配模块 | ||
| 70 | + | ||
| 71 | +适配 Aclnn 两段式接口直调:`aclnnPolarGetWorkspaceSize(input, angle, out, *workspaceSize, **executor)` 与 `aclnnPolar(workspace, workspaceSize, executor, stream)`。 | ||
| 72 | + | ||
| 73 | +## 需求描述 | ||
| 74 | + | ||
| 75 | +使用 Ascend C 将 `Cos+Sin+Mul+复数构造` 融合为单 kernel,减少 GM↔UB 搬运与 kernel 启动开销;**新增 NumPy 广播语义**(input 与 angle 维度可不一致)。 | ||
| 76 | + | ||
| 77 | +## 需求拆解 | ||
| 78 | + | ||
| 79 | +1. 实现 `Cos+Sin+Mul×2+复数交织` 全融合 kernel,消除小算子拼接瓶颈 | ||
| 80 | +2. 广播迁移到 Kernel 侧:host 算广播 shape 与 stride,kernel 矢量化索引映射加载,统一在单 kernel CopyIn 完成 | ||
| 81 | +3. fp32(input/angle)→complex64(out),与参考一致,≤8 维 | ||
| 82 | +4. 性能 ≥ 参考 l0 的 95%("所有核参与场景"为核心验收);精度满足 AscendOpTest 默认阈值 | ||
| 83 | + | ||
| 84 | +## AscendC 算子原型 | ||
| 85 | + | ||
| 86 | +| 名称 | 类别 | dtype | format | shape | 介绍 | | ||
| 87 | +| --- | --- | --- | --- | --- | --- | | ||
| 88 | +| input | 输入 | float32 | ND | ≤8维 | 模长 abs | | ||
| 89 | +| angle | 输入 | float32 | ND | ≤8维,可广播 | 角度(弧度) | | ||
| 90 | +| out | 输出 | complex64 | ND | broadcast(input,angle) | 复数结果 | | ||
| 91 | + | ||
| 92 | +无 attr;input/angle 须同 dtype(float32);out 恒 complex64。 | ||
| 93 | + | ||
| 94 | +## AscendC 算子相关约束(与参考相比缺失功能) | ||
| 95 | + | ||
| 96 | +无功能缺失:参考支持范围(fp32→complex64 + numpy 广播 + ≤8 维)本实现全部对齐,并原生实现广播(任务新增点)。fp64→complex128 任务书未要求、参考亦不支持,按范围不适配。 | ||
| 97 | + | ||
| 98 | +--- | ||
| 99 | + | ||
| 100 | +# 详细设计(required) | ||
| 101 | + | ||
| 102 | +## 算子分析 | ||
| 103 | + | ||
| 104 | +**数学公式**:`out.real=input·cos(angle)`,`out.imag=input·sin(angle)`。 | ||
| 105 | + | ||
| 106 | +**算子特性**:逐元素、各元素独立(天然多核并行);确定性计算(无 Reduce/Scatter);每输出元素 2 次三角函数 + 2 次乘法、读 2 输入写 1 复数;输出元素间无依赖(无需跨核同步,单核流水线满排)。 | ||
| 107 | + | ||
| 108 | +**支持数据类型**:input、angle = float32(须同 dtype);out = complex64。910B vector 不原生支持 complex64,经 `ReinterpretCast` 视作 2N 个交织 fp32。 | ||
| 109 | + | ||
| 110 | +**支持形状**:ND,≤8 维;input 与 angle 满足 numpy 广播,`out.shape=broadcast(input,angle)`。 | ||
| 111 | + | ||
| 112 | +## 算子实现 | ||
| 113 | + | ||
| 114 | +总体思路:将参考约 6 个串接 l0 设备算子**融合为单 Ascend C 算子**,一趟 UB 内完成 `Cos/Sin+Mul×2+复数交织`,消除中间 HBM 往返与多次 dispatch(性能提升来源)。算子分 Host 侧(参数校验、广播 shape/stride 计算、Tiling 下发)与 Kernel 侧(Init + Process 三段式 CopyIn→Compute→CopyOut)。 | ||
| 115 | + | ||
| 116 | +### 3.2.1 host 侧设计 | ||
| 117 | + | ||
| 118 | +**InferShape/InferDataType**:InferShape 按 numpy 广播推导 out shape(右对齐各轴取 max,对齐参考 `OP_CHECK_BROADCAST_AND_INFER_SHAPE`);InferDataType 将 out 恒置 `DT_COMPLEX64`。 | ||
| 119 | + | ||
| 120 | +**广播 Shape 计算**:右对齐、缺失轴首部补 1;逐维相等或其一为 1 否则失败;每维取 max。例:`[3,1,4]`+`[2,4]`→`[3,2,4]`。host 对 input/angle 分别算其在各输出轴的元素 stride(广播轴=0),并判定全形快路径标志 `操作数 numel==out numel ⟺ 未广播`。 | ||
| 121 | + | ||
| 122 | +**1. 分核策略**:优先满核。取平台 AIV 核数;**小 case 少核**(每核 ≥`MIN_PER_CORE=2048` 元素,降 launch/同步开销);**强制偶数核**(vector core 两两绑定,>2 时下取偶)。能均分则无大小核;不能均分则余块分到前几核(前 `rem` 核各 `per+1`,其余 `per`)。 | ||
| 123 | + | ||
| 124 | +**2. 数据分块与内存优化**:充分利用 UB(910B=192KB)。`BUFFER_NUM=1`,尾块同路径(DataCopyPad 任意字节无碎片)。tileLen 计算:固定占用 ≈52×tileLen 字节,分布于 8 个 UB buffer —— `qAbs/qAng` 各 `tileLen·4B`(VECIN)、`qOut/bufPacked/bufOff` 各 `2·tileLen·4B`(复数/实虚 packed/交织偏移表)、`bufBC` `5·tileLen·4B`(矢量化 unravel scratch)、`bufInFull/bufAnFull` 广播操作数 numel×4B(全形时仅 32B 占位)。取 **tileLen=2048**(固定 ≈104KB ≤192KB,余量留广播操作数整块载入)。tile 循环 `while(done<coreLen){n=min(coreLen-done,tileLen);…done+=n;}`。 | ||
| 125 | +> 注:TPipe 单 kernel InitBuffer 张量上限 8(BUFFER_NUM=2 每个计 2);本算子 BUFFER_NUM=1、8 buffer 恰在上限(超限直接 run failed 无报错)。 | ||
| 126 | + | ||
| 127 | +**3. tilingkey 策略**:**单 kernel 全路径**,全形/广播按 tiling 的 `inFull/anFull` **运行期判断**,**不设多 tilingkey**(统一 0)—— 避免多 funcEntry 编译/维护成本,轻量分支无性能损失。仅 fp32→complex64,OpDef 已限 dtype,无 bf16 兜底。 | ||
| 128 | + | ||
| 129 | +**4. TilingData 字段**:`totalLen, tileLen, bigCoreNum/coreNum(偶数), bigCoreLen/smallCoreLen, outRank, inFull/anFull(全形快路径标志), inNumel/anNumel(广播整块载入用), outDims[8], inStr[8]/anStr[8](各输出轴元素 stride,广播轴=0)`(`outDims/inStr/anStr` 经 `TILING_DATA_FIELD_DEF_ARR`)。 | ||
| 130 | + | ||
| 131 | +### 3.2.2 kernel 侧设计 | ||
| 132 | + | ||
| 133 | +单 kernel,Init + Process(CopyIn/Compute/CopyOut),全程 fp32。 | ||
| 134 | + | ||
| 135 | +- **Init**:按 blockIdx 算本核区间;`BuildOffsetTable()` 纯 int32 矢量构造交织静态偏移表 `off[j]=4·(j>>1)+(j&1)·4T`(一次性、全 tile 复用、无逐元素标量);广播操作数经 `LoadFull` 整块载入 UB。 | ||
| 136 | +- **CopyIn(FetchOperand)**:全形操作数→连续 `DataCopyPad` 快路径;广播操作数→**矢量化 unravel**(对 tile 输出线性 idx,逐输出轴 `Muls(1/D)+Floor` 求商 + 两次 `CompareScalar+Select` 精确 ±1 修正算坐标,累加偏移 → `Cast` int32 → `Gather` 从 UB 整块操作数取数,全矢量无逐元素标量)。 | ||
| 137 | +- **Compute**:`Cos(angle)`→packed 实部区、`Sin(angle)`→packed 虚部区 → 原地 `Mul ×abs` → `Gather` 用静态偏移表交织为 `(r,i,r,i,…)`。 | ||
| 138 | +- **CopyOut**:`DataCopyPad` 写 complex64 GM(视作 2N fp32 大块对齐)。 | ||
| 139 | + | ||
| 140 | +**AscendC 实现流程图**(重点关注): | ||
| 141 | + | ||
| 142 | +```mermaid | ||
| 143 | +flowchart TD | ||
| 144 | + S["polar(): GET_TILING_DATA; Init; Process"] --> I1["Init: read TilingData + this-core range"] | ||
| 145 | + I1 --> I2["Init: InitBuffer x8 + BuildOffsetTable (once)"] | ||
| 146 | + I2 --> I3{"input/angle need broadcast?"} | ||
| 147 | + I3 -->|Yes| I4["LoadFull operand into UB"] | ||
| 148 | + I3 -->|No| L0["tile loop"] | ||
| 149 | + I4 --> L0 | ||
| 150 | + L0 --> CI["CopyIn: FetchOperand abs/angle"] | ||
| 151 | + CI --> B1{"operand == output shape?"} | ||
| 152 | + B1 -->|Yes| F1["DataCopyPad contiguous (fast path)"] | ||
| 153 | + B1 -->|No| U1["vectorized unravel: Muls/Floor + CompareScalar/Select correction -> Cast int32 -> Gather"] | ||
| 154 | + F1 --> CM["Compute: Cos->real, Sin->imag; Mul x abs; Gather interleave (r,i,r,i)"] | ||
| 155 | + U1 --> CM | ||
| 156 | + CM --> CO["CopyOut: DataCopyPad complex64 GM"] | ||
| 157 | + CO --> CK{"all tiles done?"} | ||
| 158 | + CK -->|No| L0 | ||
| 159 | + CK -->|Yes| E["End"] | ||
| 160 | +``` | ||
| 161 | + | ||
| 162 | +**复数交织方案选型**(910B/dav_c220 输出需将分离 real/imag 交织为 `[r0,i0,r1,i1,…]`): | ||
| 163 | + | ||
| 164 | +| 方案 | 结果 | 原因 | | ||
| 165 | +|------|------|------| | ||
| 166 | +| `Copy<float,true>` mask | ❌ | mask 同时作用 src/dst,无法错位 | | ||
| 167 | +| `Transpose` NCHW2NHWC | ✅但慢 | 受 16×16 分形约束,占绝大部分耗时 | | ||
| 168 | +| `vintlv`/`Interleave` | ❌ | dav_c310+ 独有,c220 不支持 | | ||
| 169 | +| strided `DataCopyPad` | ❌ | VECOUT 32B 粒度,逐 4B 写出 MTE 灾难 | | ||
| 170 | +| **Gather+静态偏移表** | **✅选用** | 纯向量指令,偏移表 Init 一次构造全 tile 复用 | | ||
| 171 | + | ||
| 172 | +**与参考流程图的差异点及原因**: | ||
| 173 | + | ||
| 174 | +| # | 参考 l0 | 本 Ascend C | 原因 | | ||
| 175 | +|---|---|---|---| | ||
| 176 | +| 1 | 约 6 独立设备算子串接,各 HBM 往返+dispatch | 单 kernel 融合 | 省 ≥5 次中间 HBM+多 dispatch(核心场景快 1.78×) | | ||
| 177 | +| 2 | `l0op::Complex` 构造复数 | Gather+静态偏移表交织 | 910B vector 不支持 complex64;规避 strided/Transpose 约束 | | ||
| 178 | +| 3 | 广播由 l0 Mul 内部处理 | host 算 stride + kernel 矢量化 unravel | 原生融合需自实现广播;全矢量避免标量 | | ||
| 179 | +| 4 | Contiguous+ViewCopy 处理非连续 | 框架传连续 GM 无需 | aclnn/测试输入连续,省拷贝 | | ||
| 180 | + | ||
| 181 | +**融合优势**:kernel 启动 约 6 次→1 次;GM↔UB 搬运 ≥10 次→2 次;中间结果 GM→UB(片上);angle 一次读入 cos+sin 同 tile 计算(参考无法复用)。 | ||
| 182 | + | ||
| 183 | +## 支持硬件 | ||
| 184 | + | ||
| 185 | +| 芯片版本 | 勾选 | | ||
| 186 | +| --- | --- | | ||
| 187 | +| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 (ascend910b) | √ | | ||
| 188 | +| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 (ascend910_93) | √ | | ||
| 189 | + | ||
| 190 | +## 使能方式 | ||
| 191 | + | ||
| 192 | +| 上层框架 | 勾选 | | ||
| 193 | +| --- | :---: | | ||
| 194 | +| TF 训练/推理 | | | ||
| 195 | +| Pytorch 训练/推理 | √(pybind 调 aclnn 测试) | | ||
| 196 | +| ATC 推理 | | | ||
| 197 | +| **Aclnn 直调** | **√** | | ||
| 198 | +| OPAT 调优 | | | ||
| 199 | +| SGAT 子图切分 | | | ||
| 200 | + | ||
| 201 | +主要适配 **ACLNN** 调用框架。 | ||
| 202 | + | ||
| 203 | +## 算子约束限制 | ||
| 204 | + | ||
| 205 | +- dtype:input/angle 仅 float32(须同 dtype);out 仅 complex64。不支持 fp16/bf16/fp64/complex128 | ||
| 206 | +- ≤8 维;input/angle 须满足 numpy 广播 | ||
| 207 | +- 广播操作数 numel 需可整体载入 UB(验收 shape 量级满足;超大广播待分块优化) | ||
| 208 | +- 无 attr | ||
| 209 | + | ||
| 210 | +# 特性交叉分析 | ||
| 211 | + | ||
| 212 | +逐元素计算,输出仅依赖对应位置输入,不涉及跨元素聚合(Reduce/Scatter)、数据搬移(Reshape/Transpose 语义)、量化(Quant),与现有特性无冲突。广播 ×(rank/shape/角度范围/正负 abs/32B 对齐性)已交叉验证:8D 满秩同形、5D 中间轴广播、双向多轴广播、标量 input×高维 angle、负 abs、大角度归约、非 32B 对齐 inner+广播、大向量×标量(用例 case9–16),均严格判据 PASS,无异常。 | ||
| 213 | + | ||
| 214 | +--- | ||
| 215 | + | ||
| 216 | +# 可维可测分析 | ||
| 217 | + | ||
| 218 | +## 精度标准/性能标准 | ||
| 219 | + | ||
| 220 | +| 验收标准 | 描述 | 来源 | | ||
| 221 | +| --- | --- | --- | | ||
| 222 | +| 精度 | 满足 AscendOpTest 默认阈值(不低于参考):fp32→complex64 实/虚部各 max_abs=1e-4、rate=1e-4 | AscendOpTest(HIT1920/AscendOpTest) | | ||
| 223 | +| 性能 | 所有核参与场景 ≥ 参考 l0 的 95%;小 shape 提供仿真分析 | 《算子任务书》 | | ||
| 224 | + | ||
| 225 | +**精度细化与实测**:complex64 在 AscendOpTest `accuracy_config` 无内置默认 → 用例 JSON 显式配 `err_threshold=[1e-4,1e-4]`(fp32 分量默认);`compare_complex` 为实/虚部各自纯绝对误差判定。**官方 AscendOpTest 实跑 6 用例(同 shape 小/16M + 广播低→高/标量/双向 + 高维非对齐)全 PASS**(含 16M [4096,4096])。全程 fp32 计算不引入 Cast 中间精度损失,complex64 经 ReinterpretCast 视作交织 fp32 分量精度等价 fp32。 | ||
| 226 | + | ||
| 227 | +**性能实测**(vs 系统 l0 参考基线,官方 AscendOpTest msprof / 每调用设备时): | ||
| 228 | + | ||
| 229 | +| 场景 | l0 基线 | 本算子 | 结论 | | ||
| 230 | +|---|---:|---:|---| | ||
| 231 | +| 小 [2,6,10] | 14.25 µs | 12.52 µs | 更快 ✓ | | ||
| 232 | +| **16M [4096,4096](所有核)** | **1665 µs** | **937.98 µs** | **快 1.78×,核心验收达标** ✓✓ | | ||
| 233 | +| 广播 [4,1,8]×[4,5,8] | 15.17 µs | 14.92 µs | 更快 ✓ | | ||
| 234 | + | ||
| 235 | +全场景优于 l0 参考,远超 ≥95% 要求。 | ||
| 236 | + | ||
| 237 | +## 兼容性分析 | ||
| 238 | + | ||
| 239 | +新算子(昇腾仓原无 Ascend C 原生 Polar,亦无 TBE),不涉及历史兼容性。aclnn 接口遵循 CANN 标准规范,提供与 PyTorch `torch.polar` 等效功能,支持模型迁移。 | ||
| @@ -0,0 +1,104 @@ | |||
| 1 | +# Polar inner-broadcast 性能优化(方案 A) | ||
| 2 | + | ||
| 3 | +> 2026-05-21。本轮针对 acceptance 50-case 中**唯一性能不达 baseline 的两个 case**(Test_036 / Test_044) | ||
| 4 | +> 做 angle inner-broadcast 专用优化,从输基线 0.8x 反超到 1.7-3.5x,并使所有 angle 周期广播 case 普遍 ~2x 提速。 | ||
| 5 | + | ||
| 6 | +## 1. 问题定位 | ||
| 7 | + | ||
| 8 | +测试口径:pybind + EXEC_NPU_CMD(aclnnPolar),msprof op_summary Task Duration(每调用设备时,50 次取中位)。 | ||
| 9 | +baseline = `torch.polar`(NPU 内置,Sin/Cos/Mul/Complex 基础算子拼接)。 | ||
| 10 | + | ||
| 11 | +优化前全 50 case 仅这两个输基线: | ||
| 12 | + | ||
| 13 | +| Case | input × angle | out 元素 | 优化前 ours | baseline | vs base | | ||
| 14 | +|---|---|---:|---:|---:|---:| | ||
| 15 | +| Test_036 | [256,256,64] × **[64]** | 4M | 168.8 µs | 147.2 µs | **0.87x** ❌ | | ||
| 16 | +| Test_044 | [128,128,512] × **[512]** | 8M | 331.4 µs | 276.6 µs | **0.83x** ❌ | | ||
| 17 | + | ||
| 18 | +**根因**:这两个是 "angle 为 input trailing-dim 的周期重复" 广播。原实现在 op_api 层用 `BroadcastTo` | ||
| 19 | +把 angle 物理展开到 out shape(同 shape kernel 要求),于是: | ||
| 20 | +- Test_036:先写 16 MB(angle 展开)→ kernel 再读 input 16 MB + 读展开后 angle 16 MB + 写 out 32 MB = **80 MB HBM** | ||
| 21 | +- baseline 用 view(stride=0)读 angle,省掉展开的一次 HBM 写,故反而更快。 | ||
| 22 | + | ||
| 23 | +## 2. 方案 A:inner-broadcast 周期 pre-compute(kernel 内运行时分支,无 tilingKey) | ||
| 24 | + | ||
| 25 | +**核心观察**:angle 周期长度 K(= angle.numel),每个 tile 取整数个 period → | ||
| 26 | +**cos/sin tile pattern 对所有 tile 完全相同**。 | ||
| 27 | + | ||
| 28 | +实现(三处资格判定严格一致:op_api / op_host tiling / pybind): | ||
| 29 | +``` | ||
| 30 | +资格 = inN==outN && anN<inN && inN%anN==0 && anN<=2048 && anN%8==0 | ||
| 31 | +``` | ||
| 32 | + | ||
| 33 | +- **op_api (`aclnn_polar.cpp`)**:资格命中时 angle **不 BroadcastTo**,保留原 shape [K] 传 kernel | ||
| 34 | +- **op_host (`polar_tiling.cpp`)**:识别 `bcastMode=1`,按 **K-block 分核**(每核负载 K 整数倍 → tile 起点 period 对齐) | ||
| 35 | +- **op_kernel (`polar.h`)**:`Process()` 内 `if(bcastMode==1)` 分支(纯 data 字段,无需多 kernel 注册) | ||
| 36 | + - `PrecomputeBcastCosSin()`:读 angle[0:K] → Cos/Sin 得 cosT/sinT[K] → 循环 m=T/K 次 broadcast 填满 | ||
| 37 | + 常驻 buffer(cosTile[0:T] / sinTile[T:2T]),**只做一次** | ||
| 38 | + - `ProcessTileBcast()`:main loop **只读 input** → `Mul(cosTile,input)`→real / `Mul(sinTile,input)`→imag | ||
| 39 | + → Gather 交织 → 写 out。无 angle 搬运、无每 tile Sin/Cos 重算。 | ||
| 40 | + | ||
| 41 | +**HBM/算力收益(Test_036, 4M)**:80 MB → **48 MB**(省 angle 展开 16 MB + angle 读 16 MB→256 B); | ||
| 42 | +Sin/Cos 从"每 tile 重算"降到"一次 precompute"。 | ||
| 43 | + | ||
| 44 | +> ⚠️ 实现陷阱:`DataCopy(srcStride=0)` **不是** "重复同段 broadcast"——它连续读 src,会把 cosT 之后的 | ||
| 45 | +> sinT 读进第二 period(症状 `npu.real==golden.imag`)。正解是把 broadcast 提到 Precompute 一次性循环填满。 | ||
| 46 | + | ||
| 47 | +## 3. 优化结果 | ||
| 48 | + | ||
| 49 | +### 关注的两个 case:从输基线反超 | ||
| 50 | + | ||
| 51 | +| Case | K | 优化前 | **优化后** | kernel 提速 | baseline | vs base 前→后 | | ||
| 52 | +|---|---|---:|---:|---:|---:|---:| | ||
| 53 | +| Test_036 | 64 | 168.8 | **42.1** | **4.0x** | 147.2 | 0.87x → **3.50x** | | ||
| 54 | +| Test_044 | 512 | 331.4 | **161.9** | 2.0x | 276.6 | 0.83x → **1.71x** | | ||
| 55 | + | ||
| 56 | +### 其它 angle 周期广播 case(顺带普遍 ~2x) | ||
| 57 | + | ||
| 58 | +| Case | out 元素 | K | 优化前 | 优化后 | baseline | vs base | | ||
| 59 | +|---|---:|---:|---:|---:|---:|---:| | ||
| 60 | +| Test_035 | 262K | 64 | 17.6 | 15.7 | 54.7 | 3.47x | | ||
| 61 | +| Test_037 | 16M | 64 | 654 | 321 | 765 | 2.38x | | ||
| 62 | +| Test_038 | 67M | 64 | 2595 | 1261 | 3340 | 2.65x | | ||
| 63 | +| Test_039 | 134M | 32 | 5181 | 2531 | 6771 | 2.67x | | ||
| 64 | +| Test_040 | 268M | 16 | 10354 | 5144 | 13633 | 2.65x | | ||
| 65 | +| Test_041 | 537M | 8 | 20701 | 10287 | 27361 | 2.66x | | ||
| 66 | +| Test_047 | 134M | 8 | 5181 | 2581 | 6784 | 2.63x | | ||
| 67 | +| Test_048 | 33M | 32 | 1301 | 641 | 1639 | 2.56x | | ||
| 68 | + | ||
| 69 | +未启用优化(K=1/2/4 不满足 32B 对齐,走 same-shape 兜底)的 Test_042/045/046:因规模大(268-537M) | ||
| 70 | +baseline 同为 HBM bound,same-shape 已 1.31-1.47x 胜基线,无需再优化。 | ||
| 71 | + | ||
| 72 | +### 全 50-case 结论:无一输基线 | ||
| 73 | + | ||
| 74 | +| 类别 | case | 加速比 | | ||
| 75 | +|---|---|---| | ||
| 76 | +| 小同 shape (1-12) | same-shape | 2.5-3.4x | | ||
| 77 | +| 大同 shape (13-29) | same-shape | 2.5-3.1x | | ||
| 78 | +| 小广播 (30-34, K<8) | same-shape 兜底 | 3.2-5.3x | | ||
| 79 | +| 大广播 (35-50) | bcast 资格 ~2x kernel 提速;非资格规模大也胜 | 1.3-3.5x | | ||
| 80 | + | ||
| 81 | +## 4. 官方 AscendOpTest 验证(ops-math 工程,customize_math vendor) | ||
| 82 | + | ||
| 83 | +``` | ||
| 84 | +python run_test.py -i .../acceptance/op.json -c <Test_036,Test_044> \ | ||
| 85 | + --op-type custom --op-path $ASCEND_OPP_PATH/vendors/customize_math/op_api --build | ||
| 86 | +``` | ||
| 87 | +结果 `result.csv`: | ||
| 88 | + | ||
| 89 | +| case | compare_result | | ||
| 90 | +|---|---| | ||
| 91 | +| Test_036 | **pass** | | ||
| 92 | +| Test_044 | **pass** | | ||
| 93 | + | ||
| 94 | +精度判据 err_threshold=[1e-3, 1e-3],官方工具确认数值正确。 | ||
| 95 | + | ||
| 96 | +## 5. 改动文件(ops-math) | ||
| 97 | + | ||
| 98 | +- `op_kernel/polar_tiling_data.h`:+ `inN/anN/bcastMode` | ||
| 99 | +- `op_host/polar_tiling.cpp`:inner-bcast 检测 + K-block 分核 | ||
| 100 | +- `op_kernel/polar.h`:`Process()` 分支 + `PrecomputeBcastCosSin()` + `ProcessTileBcast()` | ||
| 101 | +- `op_api/aclnn_polar.cpp`:资格命中时 angle 不 BroadcastTo | ||
| 102 | +- `op_api/polar.cpp`:注释更正 | ||
| 103 | + | ||
| 104 | +同 shape 与非 inner-bcast 广播路径(中间轴 / input 侧广播,如 Test_043/049/050)行为不变,由 same-shape kernel + op_api BroadcastTo 兜底。 | ||
| @@ -0,0 +1,207 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * Polar 算子独立 aclnn 调用测试(精度 + 性能) | ||
| 13 | + * | ||
| 14 | + * 参考官方范式:cann/ops-math math/sin/examples/test_aclnn_sin.cpp(两段式 aclnn 标准调用) | ||
| 15 | + * 适配 Polar:2×float32 输入(input, angle) + complex64 输出(out=broadcast(input,angle)) | ||
| 16 | + * | ||
| 17 | + * 编译(在已安装自定义 Polar 包的 NPU 环境,路径按实际 CANN/vendor 调整): | ||
| 18 | + * source /home/ma-user/Ascend/cann-8.5.0/set_env.sh | ||
| 19 | + * OPP=$ASCEND_HOME_PATH/opp/vendors/customize | ||
| 20 | + * g++ -O2 -std=c++17 test_aclnn_polar.cpp -o test_aclnn_polar \ | ||
| 21 | + * -I$ASCEND_HOME_PATH/include -I$OPP/op_api/include \ | ||
| 22 | + * -L$ASCEND_HOME_PATH/lib64 -L$OPP/op_api/lib \ | ||
| 23 | + * -lascendcl -lnnopbase -lcust_opapi | ||
| 24 | + * 运行: | ||
| 25 | + * export LD_LIBRARY_PATH=$OPP/op_api/lib:$LD_LIBRARY_PATH | ||
| 26 | + * ./test_aclnn_polar # 精度 + 自带计时 | ||
| 27 | + * msprof --application="./test_aclnn_polar" # 设备侧性能采集 | ||
| 28 | + */ | ||
| 29 | +#include <iostream> | ||
| 30 | +#include <vector> | ||
| 31 | +#include <cmath> | ||
| 32 | +#include <chrono> | ||
| 33 | +#include "acl/acl.h" | ||
| 34 | +#include "aclnn_polar.h" // 自定义算子生成的 aclnn 头(custom vendor op_api/include) | ||
| 35 | + | ||
| 36 | +#define CHECK_RET(cond, expr) \ | ||
| 37 | + do { \ | ||
| 38 | + if (!(cond)) { \ | ||
| 39 | + expr; \ | ||
| 40 | + } \ | ||
| 41 | + } while (0) | ||
| 42 | +#define LOG(msg, ...) \ | ||
| 43 | + do { \ | ||
| 44 | + printf(msg, ##__VA_ARGS__); \ | ||
| 45 | + } while (0) | ||
| 46 | + | ||
| 47 | +struct C64 { | ||
| 48 | + float re; | ||
| 49 | + float im; | ||
| 50 | +}; // 与 complex64 内存布局一致(8 字节) | ||
| 51 | + | ||
| 52 | +static int64_t ShapeSize(const std::vector<int64_t>& s) | ||
| 53 | +{ | ||
| 54 | + int64_t n = 1; | ||
| 55 | + for (auto v : s) | ||
| 56 | + n *= v; | ||
| 57 | + return n; | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +static int Init(int32_t deviceId, aclrtStream* stream) | ||
| 61 | +{ | ||
| 62 | + auto ret = aclInit(nullptr); | ||
| 63 | + CHECK_RET(ret == ACL_SUCCESS, LOG("aclInit failed %d\n", ret); return ret); | ||
| 64 | + ret = aclrtSetDevice(deviceId); | ||
| 65 | + CHECK_RET(ret == ACL_SUCCESS, LOG("aclrtSetDevice failed %d\n", ret); return ret); | ||
| 66 | + ret = aclrtCreateStream(stream); | ||
| 67 | + CHECK_RET(ret == ACL_SUCCESS, LOG("aclrtCreateStream failed %d\n", ret); return ret); | ||
| 68 | + return 0; | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +template <typename T> | ||
| 72 | +static int CreateAclTensor(const std::vector<T>& host, const std::vector<int64_t>& shape, void** devAddr, | ||
| 73 | + aclDataType dt, aclTensor** tensor) | ||
| 74 | +{ | ||
| 75 | + auto bytes = ShapeSize(shape) * sizeof(T); | ||
| 76 | + auto ret = aclrtMalloc(devAddr, bytes, ACL_MEM_MALLOC_HUGE_FIRST); | ||
| 77 | + CHECK_RET(ret == ACL_SUCCESS, LOG("aclrtMalloc failed %d\n", ret); return ret); | ||
| 78 | + ret = aclrtMemcpy(*devAddr, bytes, host.data(), bytes, ACL_MEMCPY_HOST_TO_DEVICE); | ||
| 79 | + CHECK_RET(ret == ACL_SUCCESS, LOG("aclrtMemcpy H2D failed %d\n", ret); return ret); | ||
| 80 | + std::vector<int64_t> strides(shape.size(), 1); | ||
| 81 | + for (int64_t i = (int64_t)shape.size() - 2; i >= 0; --i) | ||
| 82 | + strides[i] = shape[i + 1] * strides[i + 1]; | ||
| 83 | + *tensor = aclCreateTensor(shape.data(), shape.size(), dt, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), | ||
| 84 | + shape.size(), *devAddr); | ||
| 85 | + return 0; | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +// numpy 右对齐广播 out shape | ||
| 89 | +static std::vector<int64_t> BroadcastShape(const std::vector<int64_t>& a, const std::vector<int64_t>& b) | ||
| 90 | +{ | ||
| 91 | + size_t r = std::max(a.size(), b.size()); | ||
| 92 | + std::vector<int64_t> o(r, 1); | ||
| 93 | + for (size_t i = 0; i < r; ++i) { | ||
| 94 | + int64_t da = (i + a.size() >= r) ? a[i + a.size() - r] : 1; | ||
| 95 | + int64_t db = (i + b.size() >= r) ? b[i + b.size() - r] : 1; | ||
| 96 | + o[i] = std::max(da, db); | ||
| 97 | + } | ||
| 98 | + return o; | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +// 一组用例:构造 input/angle,调用 aclnnPolar(warmup + 计时重复),校验 + 报告 | ||
| 102 | +static int RunCase(const char* name, aclrtStream stream, const std::vector<int64_t>& inShape, | ||
| 103 | + const std::vector<int64_t>& anShape, int repeat = 100) | ||
| 104 | +{ | ||
| 105 | + auto outShape = BroadcastShape(inShape, anShape); | ||
| 106 | + int64_t inN = ShapeSize(inShape), anN = ShapeSize(anShape), outN = ShapeSize(outShape); | ||
| 107 | + | ||
| 108 | + std::vector<float> inH(inN), anH(anN); | ||
| 109 | + for (int64_t i = 0; i < inN; ++i) | ||
| 110 | + inH[i] = 0.5f + (float)(i % 17); // abs ≥ 0 区间 | ||
| 111 | + for (int64_t i = 0; i < anN; ++i) | ||
| 112 | + anH[i] = -3.1415926f + (float)(i % 11); // 角度 | ||
| 113 | + std::vector<C64> outH(outN, {0.f, 0.f}); | ||
| 114 | + | ||
| 115 | + void *inD = nullptr, *anD = nullptr, *outD = nullptr; | ||
| 116 | + aclTensor *in = nullptr, *an = nullptr, *out = nullptr; | ||
| 117 | + CHECK_RET(CreateAclTensor(inH, inShape, &inD, ACL_FLOAT, &in) == 0, return -1); | ||
| 118 | + CHECK_RET(CreateAclTensor(anH, anShape, &anD, ACL_FLOAT, &an) == 0, return -1); | ||
| 119 | + CHECK_RET(CreateAclTensor(outH, outShape, &outD, ACL_COMPLEX64, &out) == 0, return -1); | ||
| 120 | + | ||
| 121 | + uint64_t wsSize = 0; | ||
| 122 | + aclOpExecutor* exec = nullptr; | ||
| 123 | + auto ret = aclnnPolarGetWorkspaceSize(in, an, out, &wsSize, &exec); | ||
| 124 | + CHECK_RET(ret == ACL_SUCCESS, LOG("[%s] GetWorkspaceSize failed %d\n", name, ret); return ret); | ||
| 125 | + void* wsAddr = nullptr; | ||
| 126 | + if (wsSize > 0) { | ||
| 127 | + ret = aclrtMalloc(&wsAddr, wsSize, ACL_MEM_MALLOC_HUGE_FIRST); | ||
| 128 | + CHECK_RET(ret == ACL_SUCCESS, LOG("[%s] ws malloc failed %d\n", name, ret); return ret); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + // warmup | ||
| 132 | + for (int i = 0; i < 10; ++i) { | ||
| 133 | + ret = aclnnPolar(wsAddr, wsSize, exec, stream); | ||
| 134 | + CHECK_RET(ret == ACL_SUCCESS, LOG("[%s] aclnnPolar failed %d\n", name, ret); return ret); | ||
| 135 | + } | ||
| 136 | + aclrtSynchronizeStream(stream); | ||
| 137 | + | ||
| 138 | + // 计时(壁钟,msprof 另采设备时) | ||
| 139 | + auto t0 = std::chrono::high_resolution_clock::now(); | ||
| 140 | + for (int i = 0; i < repeat; ++i) { | ||
| 141 | + ret = aclnnPolar(wsAddr, wsSize, exec, stream); | ||
| 142 | + CHECK_RET(ret == ACL_SUCCESS, LOG("[%s] aclnnPolar failed %d\n", name, ret); return ret); | ||
| 143 | + } | ||
| 144 | + aclrtSynchronizeStream(stream); | ||
| 145 | + auto t1 = std::chrono::high_resolution_clock::now(); | ||
| 146 | + double us = std::chrono::duration<double, std::micro>(t1 - t0).count() / repeat; | ||
| 147 | + | ||
| 148 | + // 读回 + CPU 参考校验(polar = abs*(cos+ i sin),按广播规则) | ||
| 149 | + aclrtMemcpy(outH.data(), outN * sizeof(C64), outD, outN * sizeof(C64), ACL_MEMCPY_DEVICE_TO_HOST); | ||
| 150 | + int64_t err = 0; | ||
| 151 | + for (int64_t g = 0; g < outN; ++g) { | ||
| 152 | + // 由 g 反解广播下的 input/angle 元素(行主序,size==1 维不进位) | ||
| 153 | + int64_t inIdx = 0, anIdx = 0, accIn = 1, accAn = 1, rem = g; | ||
| 154 | + for (int64_t d = (int64_t)outShape.size() - 1; d >= 0; --d) { | ||
| 155 | + int64_t c = rem % outShape[d]; | ||
| 156 | + rem /= outShape[d]; | ||
| 157 | + int64_t di = (d + (int64_t)inShape.size() >= (int64_t)outShape.size()) ? | ||
| 158 | + inShape[d + inShape.size() - outShape.size()] : | ||
| 159 | + 1; | ||
| 160 | + int64_t da = (d + (int64_t)anShape.size() >= (int64_t)outShape.size()) ? | ||
| 161 | + anShape[d + anShape.size() - outShape.size()] : | ||
| 162 | + 1; | ||
| 163 | + if (di != 1) | ||
| 164 | + inIdx += (c % di) * accIn; | ||
| 165 | + if (da != 1) | ||
| 166 | + anIdx += (c % da) * accAn; | ||
| 167 | + accIn *= di; | ||
| 168 | + accAn *= da; | ||
| 169 | + } | ||
| 170 | + float a = inH[inIdx % inN], th = anH[anIdx % anN]; | ||
| 171 | + float er = a * std::cos(th), ei = a * std::sin(th); | ||
| 172 | + if (std::fabs(er - outH[g].re) > 1e-4f || std::fabs(ei - outH[g].im) > 1e-4f) | ||
| 173 | + ++err; | ||
| 174 | + } | ||
| 175 | + LOG("[%-22s] in=%lldel an=%lldel out=%lldel avg=%.2f us/call err=%lld/%lld %s\n", name, (long long)inN, | ||
| 176 | + (long long)anN, (long long)outN, us, (long long)err, (long long)outN, (err <= outN * 1e-4 ? "PASS" : "FAIL")); | ||
| 177 | + | ||
| 178 | + aclDestroyTensor(in); | ||
| 179 | + aclDestroyTensor(an); | ||
| 180 | + aclDestroyTensor(out); | ||
| 181 | + aclrtFree(inD); | ||
| 182 | + aclrtFree(anD); | ||
| 183 | + aclrtFree(outD); | ||
| 184 | + if (wsSize > 0) | ||
| 185 | + aclrtFree(wsAddr); | ||
| 186 | + return 0; | ||
| 187 | +} | ||
| 188 | + | ||
| 189 | +int main() | ||
| 190 | +{ | ||
| 191 | + int32_t deviceId = 0; | ||
| 192 | + aclrtStream stream; | ||
| 193 | + auto ret = Init(deviceId, &stream); | ||
| 194 | + CHECK_RET(ret == ACL_SUCCESS, LOG("Init acl failed %d\n", ret); return ret); | ||
| 195 | + | ||
| 196 | + // 多组用例:同 shape(小/大-性能)+ 广播(多种模式) | ||
| 197 | + RunCase("same-small [2,6,10]", stream, {2, 6, 10}, {2, 6, 10}); | ||
| 198 | + RunCase("same-large [4096,4096]", stream, {4096, 4096}, {4096, 4096}, 50); | ||
| 199 | + RunCase("bcast [4,1,8]x[4,5,8]", stream, {4, 1, 8}, {4, 5, 8}); | ||
| 200 | + RunCase("bcast scalar [1]x[3,4,5]", stream, {1}, {3, 4, 5}); | ||
| 201 | + RunCase("bcast [8,1]x[1,7]", stream, {8, 1}, {1, 7}); | ||
| 202 | + | ||
| 203 | + aclrtDestroyStream(stream); | ||
| 204 | + aclrtResetDevice(deviceId); | ||
| 205 | + aclFinalize(); | ||
| 206 | + return 0; | ||
| 207 | +} | ||
| @@ -0,0 +1,213 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * Polar 基线对比 REF 变体:调用系统 l0 参考 aclnnPolar(同测试/同计时) | ||
| 13 | + * | ||
| 14 | + * 参考官方范式:cann/ops-math math/sin/examples/test_aclnn_sin.cpp(两段式 aclnn 标准调用) | ||
| 15 | + * 适配 Polar:2×float32 输入(input, angle) + complex64 输出(out=broadcast(input,angle)) | ||
| 16 | + * | ||
| 17 | + * 编译(在已安装自定义 Polar 包的 NPU 环境,路径按实际 CANN/vendor 调整): | ||
| 18 | + * source /home/ma-user/Ascend/cann-8.5.0/set_env.sh | ||
| 19 | + * OPP=$ASCEND_HOME_PATH/opp/vendors/customize | ||
| 20 | + * g++ -O2 -std=c++17 test_aclnn_polar.cpp -o test_aclnn_polar \ | ||
| 21 | + * -I$ASCEND_HOME_PATH/include -I$OPP/op_api/include \ | ||
| 22 | + * -L$ASCEND_HOME_PATH/lib64 -L$OPP/op_api/lib \ | ||
| 23 | + * -lascendcl -lnnopbase -lcust_opapi | ||
| 24 | + * 运行: | ||
| 25 | + * export LD_LIBRARY_PATH=$OPP/op_api/lib:$LD_LIBRARY_PATH | ||
| 26 | + * ./test_aclnn_polar # 精度 + 自带计时 | ||
| 27 | + * msprof --application="./test_aclnn_polar" # 设备侧性能采集 | ||
| 28 | + */ | ||
| 29 | +#include <iostream> | ||
| 30 | +#include <vector> | ||
| 31 | +#include <cmath> | ||
| 32 | +#include <chrono> | ||
| 33 | +#include "acl/acl.h" | ||
| 34 | +#include "aclnn/aclnn_base.h" | ||
| 35 | +// [REF 变体] 不含自定义头,前向声明 → 链接系统 libopapi.so 的 l0 参考 aclnnPolar | ||
| 36 | +extern "C" { | ||
| 37 | +aclnnStatus aclnnPolarGetWorkspaceSize(const aclTensor* input, const aclTensor* angle, aclTensor* out, | ||
| 38 | + uint64_t* workspaceSize, aclOpExecutor** executor); | ||
| 39 | +aclnnStatus aclnnPolar(void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream); | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +#define CHECK_RET(cond, expr) \ | ||
| 43 | + do { \ | ||
| 44 | + if (!(cond)) { \ | ||
| 45 | + expr; \ | ||
| 46 | + } \ | ||
| 47 | + } while (0) | ||
| 48 | +#define LOG(msg, ...) \ | ||
| 49 | + do { \ | ||
| 50 | + printf(msg, ##__VA_ARGS__); \ | ||
| 51 | + } while (0) | ||
| 52 | + | ||
| 53 | +struct C64 { | ||
| 54 | + float re; | ||
| 55 | + float im; | ||
| 56 | +}; // 与 complex64 内存布局一致(8 字节) | ||
| 57 | + | ||
| 58 | +static int64_t ShapeSize(const std::vector<int64_t>& s) | ||
| 59 | +{ | ||
| 60 | + int64_t n = 1; | ||
| 61 | + for (auto v : s) | ||
| 62 | + n *= v; | ||
| 63 | + return n; | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +static int Init(int32_t deviceId, aclrtStream* stream) | ||
| 67 | +{ | ||
| 68 | + auto ret = aclInit(nullptr); | ||
| 69 | + CHECK_RET(ret == ACL_SUCCESS, LOG("aclInit failed %d\n", ret); return ret); | ||
| 70 | + ret = aclrtSetDevice(deviceId); | ||
| 71 | + CHECK_RET(ret == ACL_SUCCESS, LOG("aclrtSetDevice failed %d\n", ret); return ret); | ||
| 72 | + ret = aclrtCreateStream(stream); | ||
| 73 | + CHECK_RET(ret == ACL_SUCCESS, LOG("aclrtCreateStream failed %d\n", ret); return ret); | ||
| 74 | + return 0; | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +template <typename T> | ||
| 78 | +static int CreateAclTensor(const std::vector<T>& host, const std::vector<int64_t>& shape, void** devAddr, | ||
| 79 | + aclDataType dt, aclTensor** tensor) | ||
| 80 | +{ | ||
| 81 | + auto bytes = ShapeSize(shape) * sizeof(T); | ||
| 82 | + auto ret = aclrtMalloc(devAddr, bytes, ACL_MEM_MALLOC_HUGE_FIRST); | ||
| 83 | + CHECK_RET(ret == ACL_SUCCESS, LOG("aclrtMalloc failed %d\n", ret); return ret); | ||
| 84 | + ret = aclrtMemcpy(*devAddr, bytes, host.data(), bytes, ACL_MEMCPY_HOST_TO_DEVICE); | ||
| 85 | + CHECK_RET(ret == ACL_SUCCESS, LOG("aclrtMemcpy H2D failed %d\n", ret); return ret); | ||
| 86 | + std::vector<int64_t> strides(shape.size(), 1); | ||
| 87 | + for (int64_t i = (int64_t)shape.size() - 2; i >= 0; --i) | ||
| 88 | + strides[i] = shape[i + 1] * strides[i + 1]; | ||
| 89 | + *tensor = aclCreateTensor(shape.data(), shape.size(), dt, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), | ||
| 90 | + shape.size(), *devAddr); | ||
| 91 | + return 0; | ||
| 92 | +} | ||
| 93 | + | ||
| 94 | +// numpy 右对齐广播 out shape | ||
| 95 | +static std::vector<int64_t> BroadcastShape(const std::vector<int64_t>& a, const std::vector<int64_t>& b) | ||
| 96 | +{ | ||
| 97 | + size_t r = std::max(a.size(), b.size()); | ||
| 98 | + std::vector<int64_t> o(r, 1); | ||
| 99 | + for (size_t i = 0; i < r; ++i) { | ||
| 100 | + int64_t da = (i + a.size() >= r) ? a[i + a.size() - r] : 1; | ||
| 101 | + int64_t db = (i + b.size() >= r) ? b[i + b.size() - r] : 1; | ||
| 102 | + o[i] = std::max(da, db); | ||
| 103 | + } | ||
| 104 | + return o; | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +// 一组用例:构造 input/angle,调用 aclnnPolar(warmup + 计时重复),校验 + 报告 | ||
| 108 | +static int RunCase(const char* name, aclrtStream stream, const std::vector<int64_t>& inShape, | ||
| 109 | + const std::vector<int64_t>& anShape, int repeat = 100) | ||
| 110 | +{ | ||
| 111 | + auto outShape = BroadcastShape(inShape, anShape); | ||
| 112 | + int64_t inN = ShapeSize(inShape), anN = ShapeSize(anShape), outN = ShapeSize(outShape); | ||
| 113 | + | ||
| 114 | + std::vector<float> inH(inN), anH(anN); | ||
| 115 | + for (int64_t i = 0; i < inN; ++i) | ||
| 116 | + inH[i] = 0.5f + (float)(i % 17); // abs ≥ 0 区间 | ||
| 117 | + for (int64_t i = 0; i < anN; ++i) | ||
| 118 | + anH[i] = -3.1415926f + (float)(i % 11); // 角度 | ||
| 119 | + std::vector<C64> outH(outN, {0.f, 0.f}); | ||
| 120 | + | ||
| 121 | + void *inD = nullptr, *anD = nullptr, *outD = nullptr; | ||
| 122 | + aclTensor *in = nullptr, *an = nullptr, *out = nullptr; | ||
| 123 | + CHECK_RET(CreateAclTensor(inH, inShape, &inD, ACL_FLOAT, &in) == 0, return -1); | ||
| 124 | + CHECK_RET(CreateAclTensor(anH, anShape, &anD, ACL_FLOAT, &an) == 0, return -1); | ||
| 125 | + CHECK_RET(CreateAclTensor(outH, outShape, &outD, ACL_COMPLEX64, &out) == 0, return -1); | ||
| 126 | + | ||
| 127 | + uint64_t wsSize = 0; | ||
| 128 | + aclOpExecutor* exec = nullptr; | ||
| 129 | + auto ret = aclnnPolarGetWorkspaceSize(in, an, out, &wsSize, &exec); | ||
| 130 | + CHECK_RET(ret == ACL_SUCCESS, LOG("[%s] GetWorkspaceSize failed %d\n", name, ret); return ret); | ||
| 131 | + void* wsAddr = nullptr; | ||
| 132 | + if (wsSize > 0) { | ||
| 133 | + ret = aclrtMalloc(&wsAddr, wsSize, ACL_MEM_MALLOC_HUGE_FIRST); | ||
| 134 | + CHECK_RET(ret == ACL_SUCCESS, LOG("[%s] ws malloc failed %d\n", name, ret); return ret); | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + // warmup | ||
| 138 | + for (int i = 0; i < 10; ++i) { | ||
| 139 | + ret = aclnnPolar(wsAddr, wsSize, exec, stream); | ||
| 140 | + CHECK_RET(ret == ACL_SUCCESS, LOG("[%s] aclnnPolar failed %d\n", name, ret); return ret); | ||
| 141 | + } | ||
| 142 | + aclrtSynchronizeStream(stream); | ||
| 143 | + | ||
| 144 | + // 计时(壁钟,msprof 另采设备时) | ||
| 145 | + auto t0 = std::chrono::high_resolution_clock::now(); | ||
| 146 | + for (int i = 0; i < repeat; ++i) { | ||
| 147 | + ret = aclnnPolar(wsAddr, wsSize, exec, stream); | ||
| 148 | + CHECK_RET(ret == ACL_SUCCESS, LOG("[%s] aclnnPolar failed %d\n", name, ret); return ret); | ||
| 149 | + } | ||
| 150 | + aclrtSynchronizeStream(stream); | ||
| 151 | + auto t1 = std::chrono::high_resolution_clock::now(); | ||
| 152 | + double us = std::chrono::duration<double, std::micro>(t1 - t0).count() / repeat; | ||
| 153 | + | ||
| 154 | + // 读回 + CPU 参考校验(polar = abs*(cos+ i sin),按广播规则) | ||
| 155 | + aclrtMemcpy(outH.data(), outN * sizeof(C64), outD, outN * sizeof(C64), ACL_MEMCPY_DEVICE_TO_HOST); | ||
| 156 | + int64_t err = 0; | ||
| 157 | + for (int64_t g = 0; g < outN; ++g) { | ||
| 158 | + // 由 g 反解广播下的 input/angle 元素(行主序,size==1 维不进位) | ||
| 159 | + int64_t inIdx = 0, anIdx = 0, accIn = 1, accAn = 1, rem = g; | ||
| 160 | + for (int64_t d = (int64_t)outShape.size() - 1; d >= 0; --d) { | ||
| 161 | + int64_t c = rem % outShape[d]; | ||
| 162 | + rem /= outShape[d]; | ||
| 163 | + int64_t di = (d + (int64_t)inShape.size() >= (int64_t)outShape.size()) ? | ||
| 164 | + inShape[d + inShape.size() - outShape.size()] : | ||
| 165 | + 1; | ||
| 166 | + int64_t da = (d + (int64_t)anShape.size() >= (int64_t)outShape.size()) ? | ||
| 167 | + anShape[d + anShape.size() - outShape.size()] : | ||
| 168 | + 1; | ||
| 169 | + if (di != 1) | ||
| 170 | + inIdx += (c % di) * accIn; | ||
| 171 | + if (da != 1) | ||
| 172 | + anIdx += (c % da) * accAn; | ||
| 173 | + accIn *= di; | ||
| 174 | + accAn *= da; | ||
| 175 | + } | ||
| 176 | + float a = inH[inIdx % inN], th = anH[anIdx % anN]; | ||
| 177 | + float er = a * std::cos(th), ei = a * std::sin(th); | ||
| 178 | + if (std::fabs(er - outH[g].re) > 1e-4f || std::fabs(ei - outH[g].im) > 1e-4f) | ||
| 179 | + ++err; | ||
| 180 | + } | ||
| 181 | + LOG("[%-22s] in=%lldel an=%lldel out=%lldel avg=%.2f us/call err=%lld/%lld %s\n", name, (long long)inN, | ||
| 182 | + (long long)anN, (long long)outN, us, (long long)err, (long long)outN, (err <= outN * 1e-4 ? "PASS" : "FAIL")); | ||
| 183 | + | ||
| 184 | + aclDestroyTensor(in); | ||
| 185 | + aclDestroyTensor(an); | ||
| 186 | + aclDestroyTensor(out); | ||
| 187 | + aclrtFree(inD); | ||
| 188 | + aclrtFree(anD); | ||
| 189 | + aclrtFree(outD); | ||
| 190 | + if (wsSize > 0) | ||
| 191 | + aclrtFree(wsAddr); | ||
| 192 | + return 0; | ||
| 193 | +} | ||
| 194 | + | ||
| 195 | +int main() | ||
| 196 | +{ | ||
| 197 | + int32_t deviceId = 0; | ||
| 198 | + aclrtStream stream; | ||
| 199 | + auto ret = Init(deviceId, &stream); | ||
| 200 | + CHECK_RET(ret == ACL_SUCCESS, LOG("Init acl failed %d\n", ret); return ret); | ||
| 201 | + | ||
| 202 | + // 多组用例:同 shape(小/大-性能)+ 广播(多种模式) | ||
| 203 | + RunCase("same-small [2,6,10]", stream, {2, 6, 10}, {2, 6, 10}); | ||
| 204 | + RunCase("same-large [4096,4096]", stream, {4096, 4096}, {4096, 4096}, 50); | ||
| 205 | + RunCase("bcast [4,1,8]x[4,5,8]", stream, {4, 1, 8}, {4, 5, 8}); | ||
| 206 | + RunCase("bcast scalar [1]x[3,4,5]", stream, {1}, {3, 4, 5}); | ||
| 207 | + RunCase("bcast [8,1]x[1,7]", stream, {8, 1}, {1, 7}); | ||
| 208 | + | ||
| 209 | + aclrtDestroyStream(stream); | ||
| 210 | + aclrtResetDevice(deviceId); | ||
| 211 | + aclFinalize(); | ||
| 212 | + return 0; | ||
| 213 | +} | ||
| @@ -0,0 +1,189 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file aclnn_polar.cpp | ||
| 13 | + * \brief Polar L2 API:Contiguous + numpy 广播 (BroadcastTo) + L0 Polar + ViewCopy。 | ||
| 14 | + * kernel 假设同 shape elementwise;所有广播/非连续处理在本层完成 —— | ||
| 15 | + * 因此任意 shape / 任意广播组合(≤8 维)都能正确处理,不会因 outNumel 大或 | ||
| 16 | + * 中间轴广播触发 kernel 内的 fp32 unravel 失精或 UB 容量越界。 | ||
| 17 | + */ | ||
| 18 | + | ||
| 19 | +#include "aclnn_polar.h" | ||
| 20 | +#include "polar.h" | ||
| 21 | + | ||
| 22 | +#include "aclnn_kernels/contiguous.h" | ||
| 23 | +#include "aclnn_kernels/common/op_error_check.h" | ||
| 24 | +#include "conversion/broadcast_to/op_api/broadcast_to.h" // l0op::BroadcastTo | ||
| 25 | +#include "opdev/op_log.h" | ||
| 26 | +#include "opdev/op_dfx.h" | ||
| 27 | +#include "opdev/common_types.h" | ||
| 28 | +#include "opdev/data_type_utils.h" | ||
| 29 | +#include "opdev/make_op_executor.h" | ||
| 30 | +#include "opdev/platform.h" | ||
| 31 | +#include "opdev/shape_utils.h" | ||
| 32 | +#include "op_api/aclnn_check.h" | ||
| 33 | +#include "op_api/op_api_def.h" | ||
| 34 | + | ||
| 35 | +using namespace op; | ||
| 36 | + | ||
| 37 | +#ifdef __cplusplus | ||
| 38 | +extern "C" { | ||
| 39 | +#endif | ||
| 40 | + | ||
| 41 | +#define ACLNN_MAX_SHAPE_RANK 8 | ||
| 42 | + | ||
| 43 | +static const std::initializer_list<op::DataType> INPUT_DTYPE_SUPPORT_LIST = {DataType::DT_FLOAT}; | ||
| 44 | + | ||
| 45 | +static const std::initializer_list<op::DataType> OUTPUT_DTYPE_SUPPORT_LIST = {DataType::DT_COMPLEX64}; | ||
| 46 | + | ||
| 47 | +static bool CheckNotNull(const aclTensor* input, const aclTensor* angle, const aclTensor* out) | ||
| 48 | +{ | ||
| 49 | + OP_CHECK_NULL(input, return false); | ||
| 50 | + OP_CHECK_NULL(angle, return false); | ||
| 51 | + OP_CHECK_NULL(out, return false); | ||
| 52 | + return true; | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +static bool CheckDtypeValid(const aclTensor* input, const aclTensor* angle, const aclTensor* out) | ||
| 56 | +{ | ||
| 57 | + if (!CheckType(input->GetDataType(), INPUT_DTYPE_SUPPORT_LIST)) { | ||
| 58 | + OP_LOGE(ACLNN_ERR_PARAM_INVALID, "input dtype %s not in support list (expect FLOAT).", | ||
| 59 | + op::ToString(input->GetDataType()).GetString()); | ||
| 60 | + return false; | ||
| 61 | + } | ||
| 62 | + if (input->GetDataType() != angle->GetDataType()) { | ||
| 63 | + OP_LOGE(ACLNN_ERR_PARAM_INVALID, "input dtype %s != angle dtype %s.", | ||
| 64 | + op::ToString(input->GetDataType()).GetString(), op::ToString(angle->GetDataType()).GetString()); | ||
| 65 | + return false; | ||
| 66 | + } | ||
| 67 | + if (!CheckType(out->GetDataType(), OUTPUT_DTYPE_SUPPORT_LIST)) { | ||
| 68 | + OP_LOGE(ACLNN_ERR_PARAM_INVALID, "out dtype %s not in support list (expect COMPLEX64).", | ||
| 69 | + op::ToString(out->GetDataType()).GetString()); | ||
| 70 | + return false; | ||
| 71 | + } | ||
| 72 | + return true; | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +static bool CheckBroadcastShape(const aclTensor* input, const aclTensor* angle, const aclTensor* out) | ||
| 76 | +{ | ||
| 77 | + OP_CHECK_MAX_DIM(input, ACLNN_MAX_SHAPE_RANK, return false); | ||
| 78 | + OP_CHECK_MAX_DIM(angle, ACLNN_MAX_SHAPE_RANK, return false); | ||
| 79 | + OP_CHECK_MAX_DIM(out, ACLNN_MAX_SHAPE_RANK, return false); | ||
| 80 | + | ||
| 81 | + OP_CHECK_BROADCAST(input, angle, return false); | ||
| 82 | + Shape broadcastShape; | ||
| 83 | + BroadcastInferShape(input->GetViewShape(), angle->GetViewShape(), broadcastShape); | ||
| 84 | + | ||
| 85 | + if (broadcastShape != out->GetViewShape()) { | ||
| 86 | + OP_LOGE(ACLNN_ERR_PARAM_INVALID, "broadcast(input, angle) shape %s != out shape %s.", | ||
| 87 | + op::ToString(broadcastShape).GetString(), op::ToString(out->GetViewShape()).GetString()); | ||
| 88 | + return false; | ||
| 89 | + } | ||
| 90 | + return true; | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +static aclnnStatus CheckParams(const aclTensor* input, const aclTensor* angle, const aclTensor* out) | ||
| 94 | +{ | ||
| 95 | + CHECK_RET(CheckNotNull(input, angle, out), ACLNN_ERR_PARAM_NULLPTR); | ||
| 96 | + CHECK_RET(CheckDtypeValid(input, angle, out), ACLNN_ERR_PARAM_INVALID); | ||
| 97 | + CHECK_RET(CheckBroadcastShape(input, angle, out), ACLNN_ERR_PARAM_INVALID); | ||
| 98 | + return ACLNN_SUCCESS; | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +static aclIntArray* GetShapeAsIntArray(const aclTensor* tensor, aclOpExecutor* executor) | ||
| 102 | +{ | ||
| 103 | + int64_t dimNum = static_cast<int64_t>(tensor->GetViewShape().GetDimNum()); | ||
| 104 | + if (dimNum == 0) { | ||
| 105 | + int64_t shape[1] = {1}; | ||
| 106 | + return executor->AllocIntArray(shape, 1); | ||
| 107 | + } | ||
| 108 | + std::vector<int64_t> shape(dimNum); | ||
| 109 | + for (int64_t i = 0; i < dimNum; i++) { | ||
| 110 | + shape[i] = tensor->GetViewShape()[i]; | ||
| 111 | + } | ||
| 112 | + return executor->AllocIntArray(shape.data(), dimNum); | ||
| 113 | +} | ||
| 114 | + | ||
| 115 | +aclnnStatus aclnnPolarGetWorkspaceSize(const aclTensor* input, const aclTensor* angle, aclTensor* out, | ||
| 116 | + uint64_t* workspaceSize, aclOpExecutor** executor) | ||
| 117 | +{ | ||
| 118 | + L2_DFX_PHASE_1(aclnnPolar, DFX_IN(input, angle), DFX_OUT(out)); | ||
| 119 | + | ||
| 120 | + auto uniqueExecutor = CREATE_EXECUTOR(); | ||
| 121 | + CHECK_RET(uniqueExecutor.get() != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR); | ||
| 122 | + | ||
| 123 | + auto ret = CheckParams(input, angle, out); | ||
| 124 | + CHECK_RET(ret == ACLNN_SUCCESS, ret); | ||
| 125 | + | ||
| 126 | + if (input->IsEmpty() || angle->IsEmpty()) { | ||
| 127 | + *workspaceSize = 0; | ||
| 128 | + uniqueExecutor.ReleaseTo(executor); | ||
| 129 | + return ACLNN_SUCCESS; | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + // 1. make contiguous(解决 view/transpose 等非连续输入) | ||
| 133 | + auto inputContiguous = l0op::Contiguous(input, uniqueExecutor.get()); | ||
| 134 | + CHECK_RET(inputContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR); | ||
| 135 | + auto angleContiguous = l0op::Contiguous(angle, uniqueExecutor.get()); | ||
| 136 | + CHECK_RET(angleContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR); | ||
| 137 | + | ||
| 138 | + // 2. broadcast 到 output shape。 | ||
| 139 | + // input 永远 BroadcastTo out(kernel 要求 input/out 同 shape)。 | ||
| 140 | + // angle:满足 inner-broadcast 资格时**保留原 shape [K]**(kernel bcastMode=1 周期复用,省一次 BroadcastTo | ||
| 141 | + // 的 HBM 写 + 每 tile angle 搬运 + Sin/Cos 重算);否则 BroadcastTo 兜底。 | ||
| 142 | + // 资格判定与 op_host/polar_tiling.cpp 严格一致:inN==outN, anN<inN, inN%anN==0, anN<=2048, anN%8==0。 | ||
| 143 | + auto outShape = GetShapeAsIntArray(out, uniqueExecutor.get()); | ||
| 144 | + CHECK_RET(outShape != nullptr, ACLNN_ERR_INNER_NULLPTR); | ||
| 145 | + | ||
| 146 | + if (inputContiguous->GetViewShape() != out->GetViewShape()) { | ||
| 147 | + inputContiguous = l0op::BroadcastTo(inputContiguous, outShape, uniqueExecutor.get()); | ||
| 148 | + CHECK_RET(inputContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR); | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + constexpr int64_t POLAR_TILE_LEN = 2048; | ||
| 152 | + auto numelOf = [](const op::Shape& s) -> int64_t { | ||
| 153 | + int64_t n = 1; | ||
| 154 | + for (size_t i = 0; i < s.GetDimNum(); ++i) | ||
| 155 | + n *= s[i]; | ||
| 156 | + return n; | ||
| 157 | + }; | ||
| 158 | + int64_t outN = numelOf(out->GetViewShape()); | ||
| 159 | + int64_t inNn = numelOf(inputContiguous->GetViewShape()); | ||
| 160 | + int64_t anNn = numelOf(angleContiguous->GetViewShape()); | ||
| 161 | + bool innerBcast = (inNn == outN) && (anNn > 0) && (anNn < inNn) && (inNn % anNn == 0) && (anNn <= POLAR_TILE_LEN) && | ||
| 162 | + (anNn % 8 == 0); | ||
| 163 | + if (!innerBcast && angleContiguous->GetViewShape() != out->GetViewShape()) { | ||
| 164 | + angleContiguous = l0op::BroadcastTo(angleContiguous, outShape, uniqueExecutor.get()); | ||
| 165 | + CHECK_RET(angleContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR); | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + // 3. 调 Polar kernel(同 shape elementwise) | ||
| 169 | + auto opResult = l0op::Polar(inputContiguous, angleContiguous, uniqueExecutor.get()); | ||
| 170 | + CHECK_RET(opResult != nullptr, ACLNN_ERR_INNER_NULLPTR); | ||
| 171 | + | ||
| 172 | + // 4. 把 kernel 输出 copy 到调用者提供的 out | ||
| 173 | + auto viewCopyResult = l0op::ViewCopy(opResult, out, uniqueExecutor.get()); | ||
| 174 | + CHECK_RET(viewCopyResult != nullptr, ACLNN_ERR_INNER_NULLPTR); | ||
| 175 | + | ||
| 176 | + *workspaceSize = uniqueExecutor->GetWorkspaceSize(); | ||
| 177 | + uniqueExecutor.ReleaseTo(executor); | ||
| 178 | + return ACLNN_SUCCESS; | ||
| 179 | +} | ||
| 180 | + | ||
| 181 | +aclnnStatus aclnnPolar(void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream) | ||
| 182 | +{ | ||
| 183 | + L2_DFX_PHASE_2(aclnnPolar); | ||
| 184 | + return CommonOpExecutorRun(workspace, workspaceSize, executor, stream); | ||
| 185 | +} | ||
| 186 | + | ||
| 187 | +#ifdef __cplusplus | ||
| 188 | +} | ||
| 189 | +#endif | ||
| @@ -0,0 +1,61 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +#ifndef OP_API_INC_LEVEL2_ACLNN_POLAR_H_ | ||
| 12 | +#define OP_API_INC_LEVEL2_ACLNN_POLAR_H_ | ||
| 13 | + | ||
| 14 | +#include "aclnn/aclnn_base.h" | ||
| 15 | +// 注意:aclnn_util.h 含 ACLNN_API、CHECK_RET 等内部宏,但**只在 .cpp 内引用**; | ||
| 16 | +// 这里不在公共头暴露 SDK 内部头,避免下游 build_aclnn.sh (CMakeLists 不含 SDK 内部 include path) 找不到。 | ||
| 17 | +// ACLNN_API 在 SDK 内是 visibility=default 宏,下游若未定义这里给个安全 fallback。 | ||
| 18 | +#ifndef ACLNN_API | ||
| 19 | +#define ACLNN_API __attribute__((visibility("default"))) | ||
| 20 | +#endif | ||
| 21 | + | ||
| 22 | +#ifdef __cplusplus | ||
| 23 | +extern "C" { | ||
| 24 | +#endif | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + * @brief aclnnPolar 第一段接口:参数校验 + Contiguous + BroadcastTo + 调 L0 kernel + ViewCopy。 | ||
| 28 | + * @domain aclnn_math | ||
| 29 | + * | ||
| 30 | + * 公式: out = input * (cos(angle) + i*sin(angle)) | ||
| 31 | + * | ||
| 32 | + * 计算图: | ||
| 33 | + * ```mermaid | ||
| 34 | + * graph LR | ||
| 35 | + * A[(input fp32)] --> B([l0op::Contiguous]) --> C([l0op::BroadcastTo]) | ||
| 36 | + * D[(angle fp32)] --> E([l0op::Contiguous]) --> F([l0op::BroadcastTo]) | ||
| 37 | + * C --> G([l0op::Polar]) | ||
| 38 | + * F --> G | ||
| 39 | + * G --> H([l0op::ViewCopy]) --> I[(out complex64)] | ||
| 40 | + * ``` | ||
| 41 | + * | ||
| 42 | + * @param [in] input 模长分量,fp32 | ||
| 43 | + * @param [in] angle 幅角(弧度),fp32;与 input 满足 numpy 广播 | ||
| 44 | + * @param [out] out 复数张量,complex64;shape 等于 input/angle 广播后的 shape | ||
| 45 | + * @param [out] workspaceSize device 侧需要的 workspace 字节数 | ||
| 46 | + * @param [out] executor op 执行器(第二段接口使用) | ||
| 47 | + * @return aclnnStatus | ||
| 48 | + */ | ||
| 49 | +ACLNN_API aclnnStatus aclnnPolarGetWorkspaceSize(const aclTensor* input, const aclTensor* angle, aclTensor* out, | ||
| 50 | + uint64_t* workspaceSize, aclOpExecutor** executor); | ||
| 51 | + | ||
| 52 | +/** | ||
| 53 | + * @brief aclnnPolar 第二段接口:执行计算 | ||
| 54 | + */ | ||
| 55 | +ACLNN_API aclnnStatus aclnnPolar(void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream); | ||
| 56 | + | ||
| 57 | +#ifdef __cplusplus | ||
| 58 | +} | ||
| 59 | +#endif | ||
| 60 | + | ||
| 61 | +#endif // OP_API_INC_LEVEL2_ACLNN_POLAR_H_ | ||
| @@ -0,0 +1,50 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file polar.cpp | ||
| 13 | + * \brief Polar L0 API:分配 complex64 输出张量并调度 AICore kernel。 | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | +#include "polar.h" | ||
| 17 | +#include "opdev/op_log.h" | ||
| 18 | +#include "opdev/op_dfx.h" | ||
| 19 | +#include "opdev/shape_utils.h" | ||
| 20 | +#include "opdev/make_op_executor.h" | ||
| 21 | +#include "op_api/aclnn_check.h" | ||
| 22 | + | ||
| 23 | +using namespace op; | ||
| 24 | + | ||
| 25 | +namespace l0op { | ||
| 26 | + | ||
| 27 | +OP_TYPE_REGISTER(Polar); | ||
| 28 | + | ||
| 29 | +static const aclTensor* PolarAiCore(const aclTensor* input, const aclTensor* angle, const aclTensor* out, | ||
| 30 | + aclOpExecutor* executor) | ||
| 31 | +{ | ||
| 32 | + L0_DFX(PolarAiCore, input, angle, out); | ||
| 33 | + | ||
| 34 | + auto ret = ADD_TO_LAUNCHER_LIST_AICORE(Polar, OP_INPUT(input, angle), OP_OUTPUT(out)); | ||
| 35 | + OP_CHECK(ret == ACLNN_SUCCESS, OP_LOGE(ACLNN_ERR_INNER_NULLPTR, "PolarAiCore add to launcher failed."), | ||
| 36 | + return nullptr); | ||
| 37 | + return out; | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +const aclTensor* Polar(const aclTensor* input, const aclTensor* angle, aclOpExecutor* executor) | ||
| 41 | +{ | ||
| 42 | + // input 经 op_api 层 BroadcastTo 到 out shape;angle 可能保留原 [K](inner-bcast,kernel 周期复用)。 | ||
| 43 | + // out shape = input.shape(= 广播后输出形状),dtype = complex64。 | ||
| 44 | + Shape outShape = input->GetViewShape(); | ||
| 45 | + const aclTensor* out = executor->AllocTensor(outShape, op::DataType::DT_COMPLEX64); | ||
| 46 | + OP_CHECK(out != nullptr, OP_LOGE(ACLNN_ERR_INNER_NULLPTR, "Polar alloc output tensor failed."), return nullptr); | ||
| 47 | + return PolarAiCore(input, angle, out, executor); | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +} // namespace l0op | ||
| @@ -0,0 +1,26 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +#ifndef OP_API_INC_LEVEL0_POLAR_H_ | ||
| 12 | +#define OP_API_INC_LEVEL0_POLAR_H_ | ||
| 13 | + | ||
| 14 | +#include "opdev/op_executor.h" | ||
| 15 | + | ||
| 16 | +namespace l0op { | ||
| 17 | + | ||
| 18 | +/** | ||
| 19 | + * Polar L0:input/angle 已通过 op_api 层 Contiguous + BroadcastTo 对齐到同 shape; | ||
| 20 | + * 此处分配 complex64 输出张量并调度 AICore kernel。 | ||
| 21 | + */ | ||
| 22 | +const aclTensor* Polar(const aclTensor* input, const aclTensor* angle, aclOpExecutor* executor); | ||
| 23 | + | ||
| 24 | +} // namespace l0op | ||
| 25 | + | ||
| 26 | +#endif // OP_API_INC_LEVEL0_POLAR_H_ | ||
| @@ -0,0 +1,48 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file polar_def.cpp | ||
| 13 | + * \brief Polar 算子原型定义:out = input * (cos(angle) + i*sin(angle)) | ||
| 14 | + * input/angle 为 float32(可 numpy 广播),out 恒为 complex64。 | ||
| 15 | + * 支持 Atlas A2 训练系列产品(ascend910b)/ Atlas A3 训练系列产品(ascend910_93)。 | ||
| 16 | + */ | ||
| 17 | +#include "register/op_def_registry.h" | ||
| 18 | + | ||
| 19 | +namespace ops { | ||
| 20 | +class Polar : public OpDef { | ||
| 21 | +public: | ||
| 22 | + explicit Polar(const char* name) : OpDef(name) | ||
| 23 | + { | ||
| 24 | + this->Input("input") | ||
| 25 | + .ParamType(REQUIRED) | ||
| 26 | + .DataType({ge::DT_FLOAT}) | ||
| 27 | + .Format({ge::FORMAT_ND}) | ||
| 28 | + .UnknownShapeFormat({ge::FORMAT_ND}) | ||
| 29 | + .AutoContiguous(); | ||
| 30 | + this->Input("angle") | ||
| 31 | + .ParamType(REQUIRED) | ||
| 32 | + .DataType({ge::DT_FLOAT}) | ||
| 33 | + .Format({ge::FORMAT_ND}) | ||
| 34 | + .UnknownShapeFormat({ge::FORMAT_ND}) | ||
| 35 | + .AutoContiguous(); | ||
| 36 | + this->Output("out") | ||
| 37 | + .ParamType(REQUIRED) | ||
| 38 | + .DataType({ge::DT_COMPLEX64}) | ||
| 39 | + .Format({ge::FORMAT_ND}) | ||
| 40 | + .UnknownShapeFormat({ge::FORMAT_ND}) | ||
| 41 | + .AutoContiguous(); | ||
| 42 | + | ||
| 43 | + this->AICore().AddConfig("ascend910b"); // Atlas A2 训练/推理系列产品 | ||
| 44 | + this->AICore().AddConfig("ascend910_93"); // Atlas A3 训练/推理系列产品 | ||
| 45 | + } | ||
| 46 | +}; | ||
| 47 | +OP_ADD(Polar); | ||
| 48 | +} // namespace ops | ||
| @@ -0,0 +1,54 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file polar_infershape.cpp | ||
| 13 | + * \brief Polar InferShape / InferDataType。 | ||
| 14 | + * InferShape:numpy 广播规则推导 out shape(右对齐各轴取 max), | ||
| 15 | + * 对齐参考 cann/ops-math math/complex/op_host/op_api/aclnn_polar.cpp 的 | ||
| 16 | + * OP_CHECK_BROADCAST_AND_INFER_SHAPE。 | ||
| 17 | + * InferDataType:out 恒置 complex64(与 input dtype 无关)。 | ||
| 18 | + */ | ||
| 19 | +#include "register/op_impl_registry.h" | ||
| 20 | +#include "op_common/log/log.h" | ||
| 21 | + | ||
| 22 | +using namespace ge; | ||
| 23 | + | ||
| 24 | +namespace ops { | ||
| 25 | + | ||
| 26 | +static ge::graphStatus InferShape4Polar(gert::InferShapeContext* context) | ||
| 27 | +{ | ||
| 28 | + const gert::Shape* in = context->GetInputShape(0); | ||
| 29 | + OP_CHECK_NULL_WITH_CONTEXT(context, in); | ||
| 30 | + const gert::Shape* an = context->GetInputShape(1); | ||
| 31 | + OP_CHECK_NULL_WITH_CONTEXT(context, an); | ||
| 32 | + gert::Shape* out = context->GetOutputShape(0); | ||
| 33 | + OP_CHECK_NULL_WITH_CONTEXT(context, out); | ||
| 34 | + size_t inRank = in->GetDimNum(); | ||
| 35 | + size_t anRank = an->GetDimNum(); | ||
| 36 | + size_t outRank = inRank > anRank ? inRank : anRank; | ||
| 37 | + out->SetDimNum(outRank); | ||
| 38 | + for (size_t i = 0; i < outRank; i++) { | ||
| 39 | + int64_t di = (i + inRank >= outRank) ? in->GetDim(i + inRank - outRank) : 1; | ||
| 40 | + int64_t da = (i + anRank >= outRank) ? an->GetDim(i + anRank - outRank) : 1; | ||
| 41 | + out->SetDim(i, di > da ? di : da); | ||
| 42 | + } | ||
| 43 | + return GRAPH_SUCCESS; | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +// 输出恒为 complex64(对齐参考 OUTPUT_DTYPE = {DT_COMPLEX64}) | ||
| 47 | +static ge::graphStatus InferDataType4Polar(gert::InferDataTypeContext* context) | ||
| 48 | +{ | ||
| 49 | + context->SetOutputDataType(0, ge::DT_COMPLEX64); | ||
| 50 | + return ge::GRAPH_SUCCESS; | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +IMPL_OP_INFERSHAPE(Polar).InferShape(InferShape4Polar).InferDataType(InferDataType4Polar); | ||
| 54 | +} // namespace ops | ||
| @@ -0,0 +1,153 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file polar_tiling.cpp | ||
| 13 | + * \brief Polar Tiling —— 同 shape elementwise:仅做 totalLen 计算 + 分核(小 case 少核、强制偶数核)。 | ||
| 14 | + * 广播由 op_api 层 BroadcastTo + Contiguous 完成;kernel 收到的 input/angle 已是同 shape。 | ||
| 15 | + */ | ||
| 16 | +#include "register/op_def_registry.h" | ||
| 17 | +#include "op_common/log/log.h" | ||
| 18 | +#include "op_common/op_host/util/platform_util.h" | ||
| 19 | +#include "lib/math/sin_tiling.h" // GetSinMaxMinTmpSize | ||
| 20 | +#include "lib/math/cos_tiling.h" // GetCosMaxMinTmpSize | ||
| 21 | +#include "../op_kernel/polar_tiling_data.h" | ||
| 22 | +#include "../op_kernel/polar_tiling_key.h" | ||
| 23 | + | ||
| 24 | +namespace optiling { | ||
| 25 | + | ||
| 26 | +static constexpr uint32_t POLAR_TILE_LEN = 2048; // 每 tile fp32 元素数 | ||
| 27 | +static constexpr uint32_t MIN_PER_CORE = 2048u; // 每核最少元素数(小 case 收敛到少核,降 launch 开销) | ||
| 28 | + | ||
| 29 | +static uint64_t Numel(const gert::Shape& s) | ||
| 30 | +{ | ||
| 31 | + uint64_t n = 1; | ||
| 32 | + uint32_t r = static_cast<uint32_t>(s.GetDimNum()); | ||
| 33 | + for (uint32_t i = 0; i < r; i++) { | ||
| 34 | + n *= (uint64_t)s.GetDim(i); | ||
| 35 | + } | ||
| 36 | + return n == 0 ? 1 : n; | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +// angle inner-broadcast 判定(与 op_api/aclnn_polar.cpp 的判定保持一致): | ||
| 40 | +// inN==totalLen, anN<inN, inN%anN==0, anN<=tileLen, anN%8==0(32B 对齐) | ||
| 41 | +static bool IsAngleInnerBcast(uint64_t inN, uint64_t anN, uint64_t totalLen) | ||
| 42 | +{ | ||
| 43 | + return inN == totalLen && anN > 0 && anN < inN && (inN % anN) == 0 && anN <= POLAR_TILE_LEN && (anN % 8u) == 0u; | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +// 核数收敛:小 case 少核(want 上限)+ 强制偶数(>2 时下取偶) | ||
| 47 | +static uint32_t ClampWantEven(uint32_t coreNum, uint32_t want) | ||
| 48 | +{ | ||
| 49 | + if (want < 1u) | ||
| 50 | + want = 1u; | ||
| 51 | + if (coreNum > want) | ||
| 52 | + coreNum = want; | ||
| 53 | + if (coreNum > 2u && (coreNum & 1u)) | ||
| 54 | + coreNum -= 1u; | ||
| 55 | + return coreNum; | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +// bcastMode=1 K-block 分核:每核负载是 K 整数倍 → coreStart % K == 0,tile 起点 period 对齐 | ||
| 59 | +static void SplitCoresBcast(PolarTilingData* tiling, uint64_t totalLen, uint64_t anN, uint32_t& coreNum) | ||
| 60 | +{ | ||
| 61 | + uint32_t K = (uint32_t)anN; | ||
| 62 | + uint64_t totalBlocks = totalLen / K; | ||
| 63 | + coreNum = ClampWantEven(coreNum, (uint32_t)totalBlocks); | ||
| 64 | + if (coreNum == 0) | ||
| 65 | + coreNum = 1; | ||
| 66 | + uint64_t perBlk = totalBlocks / coreNum; | ||
| 67 | + uint32_t remBlk = (uint32_t)(totalBlocks % coreNum); | ||
| 68 | + tiling->bigCoreNum = remBlk; | ||
| 69 | + tiling->bigCoreLen = (uint32_t)((perBlk + 1) * (uint64_t)K); | ||
| 70 | + tiling->smallCoreLen = (uint32_t)(perBlk * (uint64_t)K); | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +// 同 shape elementwise 分核:核数 ① 平台 AIV ② 小 case 少核 ③ 强制偶数,且不超过 totalLen | ||
| 74 | +static void SplitCoresElementwise(PolarTilingData* tiling, uint64_t totalLen, uint32_t& coreNum) | ||
| 75 | +{ | ||
| 76 | + coreNum = ClampWantEven(coreNum, (uint32_t)(totalLen / MIN_PER_CORE)); | ||
| 77 | + if ((uint64_t)coreNum > totalLen) | ||
| 78 | + coreNum = totalLen > 0 ? (uint32_t)totalLen : 1u; | ||
| 79 | + if (coreNum == 0) | ||
| 80 | + coreNum = 1; | ||
| 81 | + uint64_t per = totalLen / coreNum; | ||
| 82 | + uint32_t rem = (uint32_t)(totalLen % coreNum); | ||
| 83 | + tiling->bigCoreNum = rem; | ||
| 84 | + tiling->bigCoreLen = (uint32_t)(per + 1); | ||
| 85 | + tiling->smallCoreLen = (uint32_t)per; | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +// Sin/Cos 显式 sharedTmpBuffer 大小:按 tileLen 元素一次性处理(fp32 noreuse,需 3×N×typeSize 字节)。 | ||
| 89 | +// ★ 取 maxValue(充足)而非 minValue:minValue 仅"勉强够用",Sin/Cos 会按小 buffer 分块多趟计算, | ||
| 90 | +// 大 same-shape case 性能暴跌(实测 16M same-shape: cosMin 5800μs → cosMax ~650μs,~9x)。 | ||
| 91 | +// UB 余量充足(24KB << 192KB),用 maxValue 让 Sin/Cos 一次算完整 tile;再 clamp 到 UB 上限防越界。 | ||
| 92 | +static uint32_t CalcSinCosTmpSize(platform_ascendc::PlatformAscendC& plat) | ||
| 93 | +{ | ||
| 94 | + ge::Shape tileShape({(int64_t)POLAR_TILE_LEN}); | ||
| 95 | + uint32_t cosMax = 0, cosMin = 0; | ||
| 96 | + AscendC::GetCosMaxMinTmpSize(tileShape, 4u, /*isReuseSource=*/false, cosMax, cosMin); | ||
| 97 | + uint64_t ubSize = 0; | ||
| 98 | + plat.GetCoreMemSize(platform_ascendc::CoreMemType::UB, ubSize); | ||
| 99 | + uint32_t tmpSize = cosMax; | ||
| 100 | + if ((uint64_t)tmpSize > ubSize) | ||
| 101 | + tmpSize = (uint32_t)ubSize; | ||
| 102 | + return tmpSize; | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +static ge::graphStatus PolarTilingFunc(gert::TilingContext* context) | ||
| 106 | +{ | ||
| 107 | + PolarTilingData* tiling = context->GetTilingData<PolarTilingData>(); | ||
| 108 | + OP_CHECK_IF(tiling == nullptr, OP_LOGE(context, "tiling is null"), return ge::GRAPH_FAILED); | ||
| 109 | + | ||
| 110 | + // input/out 同 shape(aclnn 层把 input BroadcastTo out);angle inner-bcast 时保留原 shape [K] | ||
| 111 | + auto inShapePtr = context->GetInputShape(0); | ||
| 112 | + OP_CHECK_NULL_WITH_CONTEXT(context, inShapePtr); | ||
| 113 | + auto anShapePtr = context->GetInputShape(1); | ||
| 114 | + OP_CHECK_NULL_WITH_CONTEXT(context, anShapePtr); | ||
| 115 | + uint64_t inN = Numel(inShapePtr->GetStorageShape()); | ||
| 116 | + uint64_t anN = Numel(anShapePtr->GetStorageShape()); | ||
| 117 | + uint64_t totalLen = inN > anN ? inN : anN; | ||
| 118 | + uint32_t bcastMode = IsAngleInnerBcast(inN, anN, totalLen) ? 1u : 0u; | ||
| 119 | + | ||
| 120 | + auto plat = platform_ascendc::PlatformAscendC(context->GetPlatformInfo()); | ||
| 121 | + uint32_t coreNum = plat.GetCoreNumAiv(); | ||
| 122 | + if (coreNum == 0) | ||
| 123 | + coreNum = 8; | ||
| 124 | + | ||
| 125 | + if (bcastMode == 1) { | ||
| 126 | + SplitCoresBcast(tiling, totalLen, anN, coreNum); | ||
| 127 | + } else { | ||
| 128 | + SplitCoresElementwise(tiling, totalLen, coreNum); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + tiling->totalLen = (uint32_t)totalLen; | ||
| 132 | + tiling->tileLen = POLAR_TILE_LEN; | ||
| 133 | + tiling->coreNum = coreNum; | ||
| 134 | + tiling->inN = (uint32_t)inN; | ||
| 135 | + tiling->anN = (uint32_t)anN; | ||
| 136 | + tiling->bcastMode = bcastMode; | ||
| 137 | + tiling->tmpBufferSize = CalcSinCosTmpSize(plat); | ||
| 138 | + | ||
| 139 | + context->SetBlockDim(coreNum); | ||
| 140 | + size_t* currentWorkspace = context->GetWorkspaceSizes(1); | ||
| 141 | + currentWorkspace[0] = 0; | ||
| 142 | + return ge::GRAPH_SUCCESS; | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +struct PolarCompileInfo {}; | ||
| 146 | + | ||
| 147 | +static ge::graphStatus TilingParseForPolar([[maybe_unused]] gert::TilingParseContext* context) | ||
| 148 | +{ | ||
| 149 | + return ge::GRAPH_SUCCESS; | ||
| 150 | +} | ||
| 151 | + | ||
| 152 | +IMPL_OP_OPTILING(Polar).Tiling(PolarTilingFunc).TilingParse<PolarCompileInfo>(TilingParseForPolar); | ||
| 153 | +} // namespace optiling | ||
| @@ -0,0 +1,29 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file polar.cpp | ||
| 13 | + * \brief Polar kernel 入口(单 kernel 全路径;全形/广播按 TilingData 运行期判断) | ||
| 14 | + */ | ||
| 15 | +#include "polar.h" | ||
| 16 | + | ||
| 17 | +using namespace NsPolar; | ||
| 18 | + | ||
| 19 | +template <uint32_t schMode> | ||
| 20 | +__global__ __aicore__ void polar(GM_ADDR input, GM_ADDR angle, GM_ADDR out, GM_ADDR workspace, GM_ADDR tiling) | ||
| 21 | +{ | ||
| 22 | + REGISTER_TILING_DEFAULT(PolarTilingData); | ||
| 23 | + GET_TILING_DATA_WITH_STRUCT(PolarTilingData, tiling_data, tiling); | ||
| 24 | + | ||
| 25 | + AscendC::TPipe pipe; | ||
| 26 | + KernelPolar op; | ||
| 27 | + op.Init(input, angle, out, tiling_data, &pipe); | ||
| 28 | + op.Process(); | ||
| 29 | +} | ||
| @@ -0,0 +1,233 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file polar.h | ||
| 13 | + * \brief Polar kernel —— 同 shape input/angle 逐元素:out = input*(cos(angle)+i*sin(angle))。 | ||
| 14 | + * 广播由 op_api 层 BroadcastTo + Contiguous 完成后再调本 kernel; | ||
| 15 | + * kernel 假设 input.shape == angle.shape == out.shape; | ||
| 16 | + * out 为 complex64(real/imag 交织 fp32,用 Gather + 静态 offset 表组装)。 | ||
| 17 | + * 平台:Atlas A2 (ascend910b) / A3 (ascend910_93);vector 不支持 complex64, | ||
| 18 | + * complex64 当 2*fp32 交织处理。 | ||
| 19 | + * | ||
| 20 | + * 资源布局(BUFFER_NUM=2 double buffer): | ||
| 21 | + * qIn VECIN : 2 buffer × (2×T fp32) = 2 × 16KB —— [abs(0:n) | ang(0:n)] 合并 | ||
| 22 | + * qOut VECOUT : 2 buffer × (2×T fp32) = 2 × 16KB —— complex64 交织 | ||
| 23 | + * bufPacked VECCALC: 1 buffer × (2×T fp32) = 16KB —— [cos | sin] 中间结果 | ||
| 24 | + * bufOff VECCALC: 1 buffer × (2×T u32 ) = 16KB —— Gather 静态 offset 表 | ||
| 25 | + * bufSinCosTmp VECCALC: tilingData.tmpBufferSize —— Sin/Cos 显式 sharedTmpBuffer | ||
| 26 | + * (由 host GetSinMaxMinTmpSize 算) | ||
| 27 | + * TPipe Tensor 数:qIn(2) + qOut(2) + bufPacked(1) + bufOff(1) + bufSinCosTmp(1) = 7 (≤8 上限)。 | ||
| 28 | + * UB 用量:32 + 32 + 16 + 16 + 24 = 120 KB / 192 KB。 | ||
| 29 | + * | ||
| 30 | + * 合并 qAbs+qAng → qIn 的关键:每个 qIn buffer 容纳 2T = 4096 fp32; | ||
| 31 | + * CopyIn 阶段 DataCopyPad input → qIn[0:n],DataCopyPad angle → qIn[T:T+n]; | ||
| 32 | + * Compute 阶段 Sin/Cos 取 qIn[T:T+n] 作为 source,Mul 取 qIn[0:n] 作为 abs。 | ||
| 33 | + */ | ||
| 34 | +#ifndef POLAR_H_ | ||
| 35 | +#define POLAR_H_ | ||
| 36 | + | ||
| 37 | +#include "kernel_operator.h" | ||
| 38 | +#include "kernel_tiling/kernel_tiling.h" | ||
| 39 | +#include "lib/math/sin.h" | ||
| 40 | +#include "lib/math/cos.h" | ||
| 41 | +#include "polar_tiling_data.h" | ||
| 42 | +#include "polar_tiling_key.h" | ||
| 43 | + | ||
| 44 | +namespace NsPolar { | ||
| 45 | + | ||
| 46 | +constexpr int32_t BUFFER_NUM = 2; | ||
| 47 | + | ||
| 48 | +class KernelPolar { | ||
| 49 | +public: | ||
| 50 | + __aicore__ inline KernelPolar() {} | ||
| 51 | + | ||
| 52 | + __aicore__ inline void Init(GM_ADDR input, GM_ADDR angle, GM_ADDR out, const PolarTilingData& t, | ||
| 53 | + AscendC::TPipe* pipe) | ||
| 54 | + { | ||
| 55 | + tileLen_ = t.tileLen; | ||
| 56 | + bcastMode_ = t.bcastMode; | ||
| 57 | + K_ = t.anN; | ||
| 58 | + | ||
| 59 | + uint32_t bid = AscendC::GetBlockIdx(); | ||
| 60 | + if (bid < t.bigCoreNum) { | ||
| 61 | + coreLen_ = t.bigCoreLen; | ||
| 62 | + coreStart_ = bid * t.bigCoreLen; | ||
| 63 | + } else { | ||
| 64 | + coreLen_ = t.smallCoreLen; | ||
| 65 | + coreStart_ = t.bigCoreNum * t.bigCoreLen + (bid - t.bigCoreNum) * t.smallCoreLen; | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + // input/out 永远是 outN 长度(aclnn 层已把 input BroadcastTo out) | ||
| 69 | + inGm_.SetGlobalBuffer((__gm__ float*)input + coreStart_, coreLen_); | ||
| 70 | + outGmF_.SetGlobalBuffer((__gm__ float*)out + 2u * coreStart_, 2u * coreLen_); | ||
| 71 | + // angle:bcastMode=1 取整段 [0:K](不偏移,周期复用);same-shape 按 coreStart 偏移 | ||
| 72 | + if (bcastMode_ == 1u) { | ||
| 73 | + anGm_.SetGlobalBuffer((__gm__ float*)angle, K_); | ||
| 74 | + } else { | ||
| 75 | + anGm_.SetGlobalBuffer((__gm__ float*)angle + coreStart_, coreLen_); | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + // qIn: 每 buffer 容 2T fp32([abs|ang]),BUFFER_NUM=2 double buffer | ||
| 79 | + pipe->InitBuffer(qIn_, BUFFER_NUM, 2u * tileLen_ * sizeof(float)); | ||
| 80 | + pipe->InitBuffer(qOut_, BUFFER_NUM, 2u * tileLen_ * sizeof(float)); | ||
| 81 | + pipe->InitBuffer(bufPacked_, 2u * tileLen_ * sizeof(float)); // [cos | sin] | ||
| 82 | + pipe->InitBuffer(bufOff_, 2u * tileLen_ * sizeof(uint32_t)); // 交织 offset 表 | ||
| 83 | + pipe->InitBuffer(bufSinCosTmp_, t.tmpBufferSize); // Sin/Cos sharedTmpBuffer / bcast 常驻 cos/sin tile | ||
| 84 | + | ||
| 85 | + BuildOffsetTable(); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + __aicore__ inline void Process() | ||
| 89 | + { | ||
| 90 | + if (bcastMode_ == 1u) { | ||
| 91 | + // angle inner-broadcast:周期 K,每 tile 整数个 period → cos/sin tile pattern 恒定,只 precompute 一次 | ||
| 92 | + PrecomputeBcastCosSin(); | ||
| 93 | + uint32_t done = 0; | ||
| 94 | + while (done < coreLen_) { | ||
| 95 | + uint32_t rem = coreLen_ - done; | ||
| 96 | + uint32_t n = rem < tileLen_ ? rem : tileLen_; | ||
| 97 | + if (n >= K_) | ||
| 98 | + n = (n / K_) * K_; // n 取 K 整数倍,保证 period 对齐 | ||
| 99 | + ProcessTileBcast(done, n); | ||
| 100 | + done += n; | ||
| 101 | + } | ||
| 102 | + } else { | ||
| 103 | + uint32_t done = 0; | ||
| 104 | + while (done < coreLen_) { | ||
| 105 | + uint32_t n = (coreLen_ - done) < tileLen_ ? (coreLen_ - done) : tileLen_; | ||
| 106 | + ProcessTile(done, n); | ||
| 107 | + done += n; | ||
| 108 | + } | ||
| 109 | + } | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | +private: | ||
| 113 | + // 交织 offset 表:off[j] = 4·(j>>1) + (j&1)·4T (元素 j 选自 cos 部分还是 sin 部分的字节地址) | ||
| 114 | + // = 4T·j + (4 - 8T)·(j>>1),纯 int32 矢量构造,O(1) 一次 Init 后整 kernel 周期复用 | ||
| 115 | + __aicore__ inline void BuildOffsetTable() | ||
| 116 | + { | ||
| 117 | + const int32_t M = (int32_t)(2u * tileLen_); | ||
| 118 | + const int32_t T = (int32_t)tileLen_; | ||
| 119 | + AscendC::LocalTensor<int32_t> A = bufOff_.Get<uint32_t>().ReinterpretCast<int32_t>(); | ||
| 120 | + AscendC::LocalTensor<int32_t> B = bufPacked_.Get<float>().ReinterpretCast<int32_t>(); | ||
| 121 | + AscendC::CreateVecIndex(A, (int32_t)0, M); | ||
| 122 | + AscendC::ShiftRight(B, A, (int32_t)1, M); | ||
| 123 | + AscendC::Muls(A, A, (int32_t)(4 * T), M); | ||
| 124 | + AscendC::Muls(B, B, (int32_t)(4 - 8 * T), M); | ||
| 125 | + AscendC::Add(A, A, B, M); | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + // 交织 + 写出:Gather 静态 offset 表把 [real|imag] 重排为 [r0,i0,r1,i1,...],complex64 视作 2*fp32、 | ||
| 129 | + // DataCopyPad 写 2n 个 fp32。same-shape 与 inner-bcast 两路共用(packed 均为 real[0:n]/imag[T:T+n])。 | ||
| 130 | + __aicore__ inline void InterleaveAndStore(AscendC::LocalTensor<float>& packed, uint32_t off, uint32_t n) | ||
| 131 | + { | ||
| 132 | + AscendC::LocalTensor<uint32_t> off32 = bufOff_.Get<uint32_t>(); | ||
| 133 | + AscendC::LocalTensor<float> outL = qOut_.AllocTensor<float>(); | ||
| 134 | + AscendC::Gather(outL, packed, off32, (uint32_t)0, 2u * n); | ||
| 135 | + qOut_.EnQue(outL); | ||
| 136 | + outL = qOut_.DeQue<float>(); | ||
| 137 | + AscendC::DataCopyExtParams cpo{1, 2u * n * (uint32_t)sizeof(float), 0, 0, 0}; | ||
| 138 | + AscendC::DataCopyPad(outGmF_[2u * off], outL, cpo); | ||
| 139 | + qOut_.FreeTensor(outL); | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + __aicore__ inline void ProcessTile(uint32_t off, uint32_t n) | ||
| 143 | + { | ||
| 144 | + // CopyIn —— input → qIn[0:n], angle → qIn[T:T+n] | ||
| 145 | + AscendC::LocalTensor<float> inL = qIn_.AllocTensor<float>(); | ||
| 146 | + AscendC::DataCopyExtParams cpi{1, n * (uint32_t)sizeof(float), 0, 0, 0}; | ||
| 147 | + AscendC::DataCopyPadExtParams<float> pad{false, 0, 0, 0}; | ||
| 148 | + AscendC::DataCopyPad(inL, inGm_[off], cpi, pad); | ||
| 149 | + AscendC::DataCopyPad(inL[tileLen_], anGm_[off], cpi, pad); | ||
| 150 | + qIn_.EnQue(inL); | ||
| 151 | + | ||
| 152 | + inL = qIn_.DeQue<float>(); | ||
| 153 | + AscendC::LocalTensor<float> absSeg = inL; // abs at offset 0 | ||
| 154 | + AscendC::LocalTensor<float> angSeg = inL[tileLen_]; // ang at offset T | ||
| 155 | + | ||
| 156 | + // Compute —— Cos/Sin 用显式 sharedTmpBuffer(4-arg 重载),避免 PopStackBuffer 占 TPipe stack | ||
| 157 | + AscendC::LocalTensor<float> packed = bufPacked_.Get<float>(); | ||
| 158 | + AscendC::LocalTensor<uint8_t> sharedTmp = bufSinCosTmp_.Get<uint8_t>(); | ||
| 159 | + AscendC::Cos<float, false>(packed, angSeg, sharedTmp, n); // packed[0:n] = cos(ang) | ||
| 160 | + AscendC::Sin<float, false>(packed[tileLen_], angSeg, sharedTmp, n); // packed[T:T+n] = sin(ang) | ||
| 161 | + // 原地 *abs 得 real/imag | ||
| 162 | + AscendC::Mul(packed, packed, absSeg, n); // real = abs * cos | ||
| 163 | + AscendC::Mul(packed[tileLen_], packed[tileLen_], absSeg, n); // imag = abs * sin | ||
| 164 | + qIn_.FreeTensor(inL); | ||
| 165 | + | ||
| 166 | + InterleaveAndStore(packed, off, n); | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + // ===== bcastMode=1 inner-broadcast 路径 ===== | ||
| 170 | + // angle 周期 K,每 tile = 整数个 period → cos/sin tile pattern 对所有 tile 相同。 | ||
| 171 | + // Init 阶段 pre Cos/Sin angle[0:K] 并 broadcast 填满 bufSinCosTmp_ 的 cosTile[0:T]/sinTile[T:2T] 常驻; | ||
| 172 | + // main loop 只读 input → Mul×2 → Gather 交织,省掉每 tile 的 angle 搬运与 Sin/Cos 重算。 | ||
| 173 | + __aicore__ inline void PrecomputeBcastCosSin() | ||
| 174 | + { | ||
| 175 | + // 1) angle[0:K] → qIn buffer | ||
| 176 | + AscendC::LocalTensor<float> aL = qIn_.AllocTensor<float>(); | ||
| 177 | + AscendC::DataCopyExtParams cpi{1, K_ * (uint32_t)sizeof(float), 0, 0, 0}; | ||
| 178 | + AscendC::DataCopyPadExtParams<float> pad{false, 0, 0, 0}; | ||
| 179 | + AscendC::DataCopyPad(aL, anGm_, cpi, pad); | ||
| 180 | + qIn_.EnQue(aL); | ||
| 181 | + aL = qIn_.DeQue<float>(); | ||
| 182 | + | ||
| 183 | + // 2) Cos/Sin 写 bufPacked_[0:K]/[K:2K],scratch 用 bufPacked_ 尾部 [2K:2T](N=K 所需 tmp 远小于该区) | ||
| 184 | + AscendC::LocalTensor<float> packed = bufPacked_.Get<float>(); | ||
| 185 | + AscendC::LocalTensor<uint8_t> scratch = bufPacked_.Get<uint8_t>()[2u * K_ * sizeof(float)]; | ||
| 186 | + AscendC::Cos<float, false>(packed[0], aL, scratch, K_); | ||
| 187 | + AscendC::Sin<float, false>(packed[K_], aL, scratch, K_); | ||
| 188 | + qIn_.FreeTensor(aL); | ||
| 189 | + | ||
| 190 | + // 3) broadcast cosT[0:K]→cosTile[0:T]、sinT[0:K]→sinTile[0:T],常驻 bufSinCosTmp_ | ||
| 191 | + // (一次性 m=T/K 次 K-段复制,不在 hot loop) | ||
| 192 | + AscendC::LocalTensor<float> tile = bufSinCosTmp_.Get<uint8_t>().ReinterpretCast<float>(); | ||
| 193 | + const uint32_t mFull = tileLen_ / K_; | ||
| 194 | + for (uint32_t i = 0; i < mFull; ++i) { | ||
| 195 | + AscendC::DataCopy(tile[i * K_], packed[0], K_); // cosTile period i | ||
| 196 | + AscendC::DataCopy(tile[tileLen_ + i * K_], packed[K_], K_); // sinTile period i | ||
| 197 | + } | ||
| 198 | + } | ||
| 199 | + | ||
| 200 | + // bcastMode=1 单 tile:input only HBM read;cos/sin tile 从 bufSinCosTmp_ 常驻区取(前 n 个即正确 period)。 | ||
| 201 | + // off 必 K 对齐、n 是 K 整数倍(host K-block 分核 + main loop 保证)。 | ||
| 202 | + __aicore__ inline void ProcessTileBcast(uint32_t off, uint32_t n) | ||
| 203 | + { | ||
| 204 | + // CopyIn input[off:off+n] only | ||
| 205 | + AscendC::LocalTensor<float> inL = qIn_.AllocTensor<float>(); | ||
| 206 | + AscendC::DataCopyExtParams cpi{1, n * (uint32_t)sizeof(float), 0, 0, 0}; | ||
| 207 | + AscendC::DataCopyPadExtParams<float> pad{false, 0, 0, 0}; | ||
| 208 | + AscendC::DataCopyPad(inL, inGm_[off], cpi, pad); | ||
| 209 | + qIn_.EnQue(inL); | ||
| 210 | + inL = qIn_.DeQue<float>(); | ||
| 211 | + | ||
| 212 | + // Mul:real = cosTile * input → packed[0:n];imag = sinTile * input → packed[T:T+n] | ||
| 213 | + AscendC::LocalTensor<float> packed = bufPacked_.Get<float>(); | ||
| 214 | + AscendC::LocalTensor<float> tile = bufSinCosTmp_.Get<uint8_t>().ReinterpretCast<float>(); | ||
| 215 | + AscendC::LocalTensor<float> cosTile = tile; // [0:T] | ||
| 216 | + AscendC::LocalTensor<float> sinTile = tile[tileLen_]; // [T:2T] | ||
| 217 | + AscendC::Mul(packed[0], cosTile, inL, n); | ||
| 218 | + AscendC::Mul(packed[tileLen_], sinTile, inL, n); | ||
| 219 | + qIn_.FreeTensor(inL); | ||
| 220 | + | ||
| 221 | + InterleaveAndStore(packed, off, n); | ||
| 222 | + } | ||
| 223 | + | ||
| 224 | + AscendC::GlobalTensor<float> inGm_, anGm_, outGmF_; | ||
| 225 | + AscendC::TQue<AscendC::QuePosition::VECIN, BUFFER_NUM> qIn_; // 合并 abs + ang,buffer 大小 2T | ||
| 226 | + AscendC::TQue<AscendC::QuePosition::VECOUT, BUFFER_NUM> qOut_; | ||
| 227 | + AscendC::TBuf<AscendC::QuePosition::VECCALC> bufPacked_, bufOff_, bufSinCosTmp_; | ||
| 228 | + uint32_t tileLen_{0}, coreLen_{0}, coreStart_{0}; | ||
| 229 | + uint32_t bcastMode_{0}, K_{0}; | ||
| 230 | +}; | ||
| 231 | + | ||
| 232 | +} // namespace NsPolar | ||
| 233 | +#endif // POLAR_H_ | ||
| @@ -0,0 +1,34 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file polar_tiling_data.h | ||
| 13 | + * \brief Polar tiling data struct(host 填写 / kernel 读取共用同一 struct)。 | ||
| 14 | + * 两条路径:bcastMode=0 同 shape elementwise;bcastMode=1 angle inner-broadcast | ||
| 15 | + * (angle 周期 K << input,trailing-dim 重复)—— 详见 op_kernel/polar.h。 | ||
| 16 | + * 非 inner-bcast 的广播(中间轴/input 侧)仍由 aclnn L2 层 BroadcastTo + Contiguous 完成。 | ||
| 17 | + */ | ||
| 18 | +#ifndef POLAR_TILING_DATA_H_ | ||
| 19 | +#define POLAR_TILING_DATA_H_ | ||
| 20 | + | ||
| 21 | +struct PolarTilingData { | ||
| 22 | + uint32_t totalLen; // 输出元素总数 | ||
| 23 | + uint32_t tileLen; // 每 tile 元素数 | ||
| 24 | + uint32_t bigCoreNum; // 大核数量(前 bigCoreNum 个核各多处理 1 元素) | ||
| 25 | + uint32_t bigCoreLen; // 大核分块长度 | ||
| 26 | + uint32_t smallCoreLen; // 小核分块长度 | ||
| 27 | + uint32_t coreNum; // 实际启用核数(偶数) | ||
| 28 | + uint32_t tmpBufferSize; // Sin/Cos 显式 sharedTmpBuffer 字节数(由 Get{Sin,Cos}MaxMinTmpSize 算) | ||
| 29 | + uint32_t inN; // input.numel(bcastMode=1 时 = totalLen) | ||
| 30 | + uint32_t anN; // angle.numel(bcastMode=1 时 = K,周期长度) | ||
| 31 | + uint32_t bcastMode; // 0: same-shape;1: angle inner-broadcast | ||
| 32 | +}; | ||
| 33 | + | ||
| 34 | +#endif // POLAR_TILING_DATA_H_ | ||
| @@ -0,0 +1,30 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file polar_tiling_key.h | ||
| 13 | + * \brief polar tiling key declare(单 kernel 全路径,全形/广播按 TilingData 运行期判断, | ||
| 14 | + * 不设多 tilingkey;此处保留 ops-math 模板参数样板以接入构建框架) | ||
| 15 | + */ | ||
| 16 | +#ifndef POLAR_TILING_KEY_H_ | ||
| 17 | +#define POLAR_TILING_KEY_H_ | ||
| 18 | + | ||
| 19 | +#include "ascendc/host_api/tiling/template_argument.h" | ||
| 20 | + | ||
| 21 | +#define ELEMENTWISE_TPL_SCH_MODE_0 0 | ||
| 22 | +#define ELEMENTWISE_TPL_SCH_MODE_1 1 | ||
| 23 | + | ||
| 24 | +ASCENDC_TPL_ARGS_DECL(Polar, ASCENDC_TPL_UINT_DECL(schMode, 1, ASCENDC_TPL_UI_LIST, ELEMENTWISE_TPL_SCH_MODE_0, | ||
| 25 | + ELEMENTWISE_TPL_SCH_MODE_1)); | ||
| 26 | + | ||
| 27 | +ASCENDC_TPL_SEL(ASCENDC_TPL_ARGS_SEL(ASCENDC_TPL_UINT_SEL(schMode, ASCENDC_TPL_UI_LIST, ELEMENTWISE_TPL_SCH_MODE_0, | ||
| 28 | + ELEMENTWISE_TPL_SCH_MODE_1)), ); | ||
| 29 | + | ||
| 30 | +#endif // POLAR_TILING_KEY_H_ | ||
| @@ -0,0 +1,40 @@ | |||
| 1 | +[ | ||
| 2 | + { | ||
| 3 | + "op": "Polar", | ||
| 4 | + "language": "cpp", | ||
| 5 | + "input_desc": [ | ||
| 6 | + { | ||
| 7 | + "name": "input", | ||
| 8 | + "param_type": "required", | ||
| 9 | + "format": [ | ||
| 10 | + "ND" | ||
| 11 | + ], | ||
| 12 | + "type": [ | ||
| 13 | + "float" | ||
| 14 | + ] | ||
| 15 | + }, | ||
| 16 | + { | ||
| 17 | + "name": "angle", | ||
| 18 | + "param_type": "required", | ||
| 19 | + "format": [ | ||
| 20 | + "ND" | ||
| 21 | + ], | ||
| 22 | + "type": [ | ||
| 23 | + "float" | ||
| 24 | + ] | ||
| 25 | + } | ||
| 26 | + ], | ||
| 27 | + "output_desc": [ | ||
| 28 | + { | ||
| 29 | + "name": "out", | ||
| 30 | + "param_type": "required", | ||
| 31 | + "format": [ | ||
| 32 | + "ND" | ||
| 33 | + ], | ||
| 34 | + "type": [ | ||
| 35 | + "complex64" | ||
| 36 | + ] | ||
| 37 | + } | ||
| 38 | + ] | ||
| 39 | + } | ||
| 40 | +] | ||
| @@ -0,0 +1,42 @@ | |||
| 1 | +# AscendOpTest 官方精度/性能验收(Polar) | ||
| 2 | + | ||
| 3 | +任务要求"精度满足 AscendOpTest 工具默认阈值",须用**官方工具**跑(非我们 pybind 复刻)。本目录为离线准备好的资产。 | ||
| 4 | + | ||
| 5 | +## 资产 | ||
| 6 | + | ||
| 7 | +| 文件 | 作用 | | ||
| 8 | +|---|---| | ||
| 9 | +| `../../../S8/Polar.json` | 算子描述文件(IR/原型,复用,`-i` 传入) | | ||
| 10 | +| `polar_cases.json` | 测试用例(6 例:同 shape 小/16M + 广播低→高/标量/双向 + 高维非对齐),`-c` 传入 | | ||
| 11 | +| `polar_golden.py` | `expect_func` CPU 基准(numpy 广播 polar),被 case json 引用 | | ||
| 12 | + | ||
| 13 | +**关键点**:complex64 在 AscendOpTest `accuracy_config` **无内置默认** → 每个 `output_desc` 已显式写 `"err_threshold":[0.0001,0.0001]`(fp32 分量默认;[绝对偏差,错误率]),否则 run 时 KeyError。 | ||
| 14 | + | ||
| 15 | +## 在 NPU notebook 上运行 | ||
| 16 | + | ||
| 17 | +```bash | ||
| 18 | +# 0) 前置 | ||
| 19 | +pip install ml_dtypes | ||
| 20 | +# 1) 部署自定义 Polar 算子(构建+安装 .run,同 S8/Polar 流程) | ||
| 21 | +cd ~/work/S8/Polar && bash build.sh && ./build_out/*.run --quiet | ||
| 22 | +# 2) 关键:使部署算子生效(README 红字强调) | ||
| 23 | +source $ASCEND_HOME_PATH/opp/vendors/customize/bin/set_env.bash | ||
| 24 | +export LD_LIBRARY_PATH=$ASCEND_HOME_PATH/opp/vendors/customize/op_api/lib:$LD_LIBRARY_PATH | ||
| 25 | +# 3) 取官方工具 | ||
| 26 | +git clone https://gitcode.com/HIT1920/AscendOpTest.git && cd AscendOpTest | ||
| 27 | +# 4) 跑精度(--build 首次/改用例后必加;--op-type custom 自定义算子) | ||
| 28 | +python run_test.py \ | ||
| 29 | + -i /home/ma-user/work/S8/Polar.json \ | ||
| 30 | + -c /home/ma-user/work/case_910b/Polar/aot/polar_cases.json \ | ||
| 31 | + --op-type custom --build | ||
| 32 | +# 5) 跑性能(msprof application 形式) | ||
| 33 | +python run_test.py -i .../Polar.json -c .../polar_cases.json --op-type custom --msprof | ||
| 34 | +``` | ||
| 35 | + | ||
| 36 | +结果在 `result.csv`(每用例 pass/fail)。`expect_func` 路径已按 `/home/ma-user/work/case_910b/Polar/aot/polar_golden.py:polar` 写死——若上传路径不同需同步改 `polar_cases.json`。 | ||
| 37 | + | ||
| 38 | +## 注意 | ||
| 39 | + | ||
| 40 | +- input/angle/out 的 name 与顺序须与 `Polar.json`、OpDef 一致(input, angle, out)——已对齐 | ||
| 41 | +- `Test_same_16M` 用例专门用于让官方工具**独立复核**之前 standalone 测出的 16M 非确定性问题(见项目记忆 ⚠️ 未决风险) | ||
| 42 | +- 角度取 `[-3.14,3.14]`(单周期,干净查正确性);abs 取 `[-10,10]`(含负值,验证负 abs 翻号合法) | ||
| @@ -0,0 +1,86 @@ | |||
| 1 | +[ | ||
| 2 | + { | ||
| 3 | + "case_name": "Test_same_small", | ||
| 4 | + "op_name": "Polar", | ||
| 5 | + "case_path": "", | ||
| 6 | + "expect_func": "/home/ma-user/work/case_910b/Polar/aot/polar_golden.py:polar", | ||
| 7 | + "input_desc": [ | ||
| 8 | + { "name": "input", "format": "ND", "data_type": "float", "param_type": "required", "shape": [2, 6, 10], "data_path": "", "value_range": [-10, 10] }, | ||
| 9 | + { "name": "angle", "format": "ND", "data_type": "float", "param_type": "required", "shape": [2, 6, 10], "data_path": "", "value_range": [-3.14, 3.14] } | ||
| 10 | + ], | ||
| 11 | + "output_desc": [ | ||
| 12 | + { "name": "out", "format": "ND", "data_type": "complex64", "param_type": "required", "shape": [2, 6, 10], "data_path": "", "golden_path": "", "err_threshold": [0.0001, 0.0001] } | ||
| 13 | + ], | ||
| 14 | + "attr_desc": [] | ||
| 15 | + }, | ||
| 16 | + { | ||
| 17 | + "case_name": "Test_same_16M", | ||
| 18 | + "op_name": "Polar", | ||
| 19 | + "case_path": "", | ||
| 20 | + "expect_func": "/home/ma-user/work/case_910b/Polar/aot/polar_golden.py:polar", | ||
| 21 | + "input_desc": [ | ||
| 22 | + { "name": "input", "format": "ND", "data_type": "float", "param_type": "required", "shape": [4096, 4096], "data_path": "", "value_range": [-10, 10] }, | ||
| 23 | + { "name": "angle", "format": "ND", "data_type": "float", "param_type": "required", "shape": [4096, 4096], "data_path": "", "value_range": [-3.14, 3.14] } | ||
| 24 | + ], | ||
| 25 | + "output_desc": [ | ||
| 26 | + { "name": "out", "format": "ND", "data_type": "complex64", "param_type": "required", "shape": [4096, 4096], "data_path": "", "golden_path": "", "err_threshold": [0.0001, 0.0001] } | ||
| 27 | + ], | ||
| 28 | + "attr_desc": [] | ||
| 29 | + }, | ||
| 30 | + { | ||
| 31 | + "case_name": "Test_bcast_lowhigh", | ||
| 32 | + "op_name": "Polar", | ||
| 33 | + "case_path": "", | ||
| 34 | + "expect_func": "/home/ma-user/work/case_910b/Polar/aot/polar_golden.py:polar", | ||
| 35 | + "input_desc": [ | ||
| 36 | + { "name": "input", "format": "ND", "data_type": "float", "param_type": "required", "shape": [4, 1, 8], "data_path": "", "value_range": [-10, 10] }, | ||
| 37 | + { "name": "angle", "format": "ND", "data_type": "float", "param_type": "required", "shape": [4, 5, 8], "data_path": "", "value_range": [-3.14, 3.14] } | ||
| 38 | + ], | ||
| 39 | + "output_desc": [ | ||
| 40 | + { "name": "out", "format": "ND", "data_type": "complex64", "param_type": "required", "shape": [4, 5, 8], "data_path": "", "golden_path": "", "err_threshold": [0.0001, 0.0001] } | ||
| 41 | + ], | ||
| 42 | + "attr_desc": [] | ||
| 43 | + }, | ||
| 44 | + { | ||
| 45 | + "case_name": "Test_bcast_scalar", | ||
| 46 | + "op_name": "Polar", | ||
| 47 | + "case_path": "", | ||
| 48 | + "expect_func": "/home/ma-user/work/case_910b/Polar/aot/polar_golden.py:polar", | ||
| 49 | + "input_desc": [ | ||
| 50 | + { "name": "input", "format": "ND", "data_type": "float", "param_type": "required", "shape": [1], "data_path": "", "value_range": [-10, 10] }, | ||
| 51 | + { "name": "angle", "format": "ND", "data_type": "float", "param_type": "required", "shape": [3, 4, 5], "data_path": "", "value_range": [-3.14, 3.14] } | ||
| 52 | + ], | ||
| 53 | + "output_desc": [ | ||
| 54 | + { "name": "out", "format": "ND", "data_type": "complex64", "param_type": "required", "shape": [3, 4, 5], "data_path": "", "golden_path": "", "err_threshold": [0.0001, 0.0001] } | ||
| 55 | + ], | ||
| 56 | + "attr_desc": [] | ||
| 57 | + }, | ||
| 58 | + { | ||
| 59 | + "case_name": "Test_bcast_2way", | ||
| 60 | + "op_name": "Polar", | ||
| 61 | + "case_path": "", | ||
| 62 | + "expect_func": "/home/ma-user/work/case_910b/Polar/aot/polar_golden.py:polar", | ||
| 63 | + "input_desc": [ | ||
| 64 | + { "name": "input", "format": "ND", "data_type": "float", "param_type": "required", "shape": [8, 1], "data_path": "", "value_range": [-10, 10] }, | ||
| 65 | + { "name": "angle", "format": "ND", "data_type": "float", "param_type": "required", "shape": [1, 7], "data_path": "", "value_range": [-3.14, 3.14] } | ||
| 66 | + ], | ||
| 67 | + "output_desc": [ | ||
| 68 | + { "name": "out", "format": "ND", "data_type": "complex64", "param_type": "required", "shape": [8, 7], "data_path": "", "golden_path": "", "err_threshold": [0.0001, 0.0001] } | ||
| 69 | + ], | ||
| 70 | + "attr_desc": [] | ||
| 71 | + }, | ||
| 72 | + { | ||
| 73 | + "case_name": "Test_highdim_unalign", | ||
| 74 | + "op_name": "Polar", | ||
| 75 | + "case_path": "", | ||
| 76 | + "expect_func": "/home/ma-user/work/case_910b/Polar/aot/polar_golden.py:polar", | ||
| 77 | + "input_desc": [ | ||
| 78 | + { "name": "input", "format": "ND", "data_type": "float", "param_type": "required", "shape": [3, 5, 17, 269], "data_path": "", "value_range": [-10, 10] }, | ||
| 79 | + { "name": "angle", "format": "ND", "data_type": "float", "param_type": "required", "shape": [3, 5, 17, 269], "data_path": "", "value_range": [-3.14, 3.14] } | ||
| 80 | + ], | ||
| 81 | + "output_desc": [ | ||
| 82 | + { "name": "out", "format": "ND", "data_type": "complex64", "param_type": "required", "shape": [3, 5, 17, 269], "data_path": "", "golden_path": "", "err_threshold": [0.0001, 0.0001] } | ||
| 83 | + ], | ||
| 84 | + "attr_desc": [] | ||
| 85 | + } | ||
| 86 | +] | ||
| @@ -0,0 +1,30 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +# -*- coding: utf-8 -*- | ||
| 3 | +# ---------------------------------------------------------------------------- | ||
| 4 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 5 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 6 | +# CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 7 | +# Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 8 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 9 | +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 10 | +# See LICENSE in the root of the software repository for the full text of the License. | ||
| 11 | +# ---------------------------------------------------------------------------- | ||
| 12 | +""" | ||
| 13 | +AscendOpTest expect_func(CPU 基准)—— Polar 算子 | ||
| 14 | +被 polar_cases.json 的 "expect_func":"<path>/polar_golden.py:polar" 引用。 | ||
| 15 | + | ||
| 16 | +约束(AscendOpTest README): | ||
| 17 | +- 参数名/顺序须与算子描述文件(Polar.json)、case 文件 input_desc 一致:input, angle | ||
| 18 | +- 返回 list,元素为 numpy.ndarray;输出 dtype 须为 complex64 | ||
| 19 | +- polar(abs, angle) = abs * (cos(angle) + i*sin(angle)),input 与 angle 按 numpy 广播 | ||
| 20 | +""" | ||
| 21 | +import numpy as np | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +def polar(input, angle): | ||
| 25 | + a = np.asarray(input).astype(np.float32) | ||
| 26 | + th = np.asarray(angle).astype(np.float32) | ||
| 27 | + # numpy 广播(input.dim 与 angle.dim 可不一致,与算子 InferShape 对齐) | ||
| 28 | + a, th = np.broadcast_arrays(a, th) | ||
| 29 | + out = (a * (np.cos(th) + 1j * np.sin(th))).astype(np.complex64) | ||
| 30 | + return [np.ascontiguousarray(out)] | ||
| @@ -0,0 +1,58 @@ | |||
| 1 | +# Polar 测试框架(S8 同款) | ||
| 2 | + | ||
| 3 | +`out = input · (cos(angle) + i·sin(angle))`,input(abs)/angle 为 fp32 可广播,out 为 complex64。 | ||
| 4 | +对齐基准:`cann/ops-math` 的 `math/complex/op_host/op_api/aclnn_polar.cpp`(l0 拼接版)。 | ||
| 5 | + | ||
| 6 | +## 文件构成(与 S8 case_910b/<Op>/ 完全一致) | ||
| 7 | + | ||
| 8 | +| 文件 | 来源 | 说明 | | ||
| 9 | +|---|---|---| | ||
| 10 | +| `common/pytorch_npu_helper.hpp` | **逐字复用 S8** | `EXEC_NPU_CMD` 通用胶水(算子无关,动态 dlopen `aclnnPolar`) | | ||
| 11 | +| `setup.py` / `get_time.py` | **逐字复用 S8** | pybind wheel 构建 / msprof `op_summary*.csv` 取 [20:40] 均值 | | ||
| 12 | +| `extension/custom_op.cpp` | Polar 适配 | `EXEC_NPU_CMD(aclnnPolar, input, angle, result)`,result=complex64、shape=broadcast(input,angle),50 轮供 msprof 取平均 | | ||
| 13 | +| `test_op.py` | Polar 适配 | 8 个 case + 复数 verify(`view_as_real` 拆 real/imag 当 fp32,rtol/atol=1e-4) | | ||
| 14 | +| `run.sh` | Polar 适配 | 重建 wheel → msprof 计时 → get_time → 基线校验;LD 指向 `custom_math` vendor | | ||
| 15 | + | ||
| 16 | +## 前置(NPU 环境,改 kernel 后重做) | ||
| 17 | + | ||
| 18 | +```bash | ||
| 19 | +# 在 ops-math 仓编译并安装自定义 Polar 算子 | ||
| 20 | +bash build.sh --pkg --soc=ascend910b --ops=polar -j16 # A3: --soc=ascend910_93 | ||
| 21 | +./build_out/cann-ops-math-*linux*.run | ||
| 22 | +``` | ||
| 23 | +装到 `${ASCEND_HOME_PATH}/opp/vendors/custom_math/`。 | ||
| 24 | + | ||
| 25 | +## 用法 | ||
| 26 | + | ||
| 27 | +```bash | ||
| 28 | +cd case_910b/Polar | ||
| 29 | +bash run.sh 2 # 跑 case2(广播主战场) | ||
| 30 | +``` | ||
| 31 | +输出 `xxx verify result pass!` + `time_base = ... time_use = ...`。 | ||
| 32 | + | ||
| 33 | +## case 覆盖 | ||
| 34 | + | ||
| 35 | +| case | input shape | angle shape | 考点 | | ||
| 36 | +|---|---|---|---| | ||
| 37 | +| 1 | [2,6,10] | [2,6,10] | 同 shape 基础 | | ||
| 38 | +| 2 | [4,1,8] | [4,5,8] | **广播:低维→高维(新增功能主战场)** | | ||
| 39 | +| 3 | [1] | [3,4,5] | 广播:标量 input | | ||
| 40 | +| 4 | [8,1] | [1,7] | 广播:双向 → [8,7] | | ||
| 41 | +| 5 | [4096,4096] | [4096,4096] | 大 shape(性能) | | ||
| 42 | +| 6 | [3,5,17,269] | 同 | 高维 + inner 非 32B 对齐(1076B/行) | | ||
| 43 | +| 7 | [1] | [1] | 1 元素边界 | | ||
| 44 | +| 8 | [64,1024] | [64,1024] | 大角度(Sin/Cos 范围归约压力) | | ||
| 45 | + | ||
| 46 | +新增 case 直接往 `test_op.py` 的 `case_data` 加键即可(与 S8 习惯一致)。 | ||
| 47 | + | ||
| 48 | +## 与 S8 的差异(仅构建层) | ||
| 49 | + | ||
| 50 | +- S8:每算子自带 `S8/<Op>/build.sh`,`auto_submit.sh` 直接 build。 | ||
| 51 | +- Polar:构建走 **ops-math 仓 `build.sh --pkg --ops=polar`**(仓级),与本测试框架解耦。 | ||
| 52 | +- `auto_submit.sh -o Polar` 仍可复用其 upload/run 阶段;build 阶段需替换为 ops-math 命令(待算子工程落地后再接线)。 | ||
| 53 | + | ||
| 54 | +## 注意 | ||
| 55 | + | ||
| 56 | +- 精度阈值现为 fp32 rtol/atol=1e-4;任务要求"AscendOpTest 默认阈值",待 AscendOpTest 工具实际阈值确认后于 `test_op.py:verify_result` 回填。 | ||
| 57 | +- 性能基线 `run.sh:time_base` 现为哨兵值(仅正确性门禁);硬件实测出 l0 参考实现耗时后回填真实基线(任务要求 ≥ 基线 95%)。 | ||
| 58 | +- 本框架只能在 NPU 环境运行(依赖 `torch_npu`),本地无法自检——与 S8 相同。 | ||
| @@ -0,0 +1,575 @@ | |||
| 1 | +/****************************************************************************** | ||
| 2 | + * Copyright (c) 2022 Huawei Technologies Co., Ltd | ||
| 3 | + * All rights reserved. | ||
| 4 | + * | ||
| 5 | + * Licensed under the BSD 3-Clause License (the "License"); | ||
| 6 | + * you may not use this file except in compliance with the License. | ||
| 7 | + * You may obtain a copy of the License at | ||
| 8 | + * | ||
| 9 | + * https://opensource.org/licenses/BSD-3-Clause | ||
| 10 | + * | ||
| 11 | + * Unless required by applicable law or agreed to in writing, software | ||
| 12 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | + * See the License for the specific language governing permissions and | ||
| 15 | + * limitations under the License. | ||
| 16 | + ******************************************************************************/ | ||
| 17 | + | ||
| 18 | +#ifndef PYTORCH_NPU_HELPER_HPP_ | ||
| 19 | +#define PYTORCH_NPU_HELPER_HPP_ | ||
| 20 | + | ||
| 21 | +#include <ATen/Tensor.h> | ||
| 22 | +#include <acl/acl_base.h> | ||
| 23 | +#include <acl/acl_rt.h> | ||
| 24 | +#include <c10/util/Exception.h> | ||
| 25 | +#include <torch/extension.h> | ||
| 26 | +#include <dlfcn.h> | ||
| 27 | +#include <torch_npu/csrc/framework/utils/CalcuOpUtil.h> | ||
| 28 | +#include <torch_npu/csrc/framework/utils/OpAdapter.h> | ||
| 29 | + | ||
| 30 | +#include <functional> | ||
| 31 | +#include <type_traits> | ||
| 32 | +#include <vector> | ||
| 33 | + | ||
| 34 | +#include "torch_npu/csrc/aten/NPUNativeFunctions.h" | ||
| 35 | +#include "torch_npu/csrc/core/npu/NPUStream.h" | ||
| 36 | +#include "torch_npu/csrc/framework/OpCommand.h" | ||
| 37 | +#include "torch_npu/csrc/framework/interface/EnvVariables.h" | ||
| 38 | +#include "torch_npu/csrc/framework/utils/CalcuOpUtil.h" | ||
| 39 | +#include "torch_npu/csrc/framework/utils/OpPreparation.h" | ||
| 40 | + | ||
| 41 | +#define NPU_NAME_SPACE at_npu::native | ||
| 42 | + | ||
| 43 | +#define __FILENAME__ (strrchr("/" __FILE__, '/') + 1) | ||
| 44 | + | ||
| 45 | +typedef struct aclOpExecutor aclOpExecutor; | ||
| 46 | +typedef struct aclTensor aclTensor; | ||
| 47 | +typedef struct aclScalar aclScalar; | ||
| 48 | +typedef struct aclIntArray aclIntArray; | ||
| 49 | +typedef struct aclFloatArray aclFloatArray; | ||
| 50 | +typedef struct aclBoolArray aclBoolArray; | ||
| 51 | +typedef struct aclTensorList aclTensorList; | ||
| 52 | + | ||
| 53 | +typedef aclTensor* (*_aclCreateTensor)(const int64_t* view_dims, uint64_t view_dims_num, aclDataType data_type, | ||
| 54 | + const int64_t* stride, int64_t offset, aclFormat format, | ||
| 55 | + const int64_t* storage_dims, uint64_t storage_dims_num, void* tensor_data); | ||
| 56 | +typedef aclScalar* (*_aclCreateScalar)(void* value, aclDataType data_type); | ||
| 57 | +typedef aclIntArray* (*_aclCreateIntArray)(const int64_t* value, uint64_t size); | ||
| 58 | +typedef aclFloatArray* (*_aclCreateFloatArray)(const float* value, uint64_t size); | ||
| 59 | +typedef aclBoolArray* (*_aclCreateBoolArray)(const bool* value, uint64_t size); | ||
| 60 | +typedef aclTensorList* (*_aclCreateTensorList)(const aclTensor* const* value, uint64_t size); | ||
| 61 | + | ||
| 62 | +typedef int (*_aclDestroyTensor)(const aclTensor* tensor); | ||
| 63 | +typedef int (*_aclDestroyScalar)(const aclScalar* scalar); | ||
| 64 | +typedef int (*_aclDestroyIntArray)(const aclIntArray* array); | ||
| 65 | +typedef int (*_aclDestroyFloatArray)(const aclFloatArray* array); | ||
| 66 | +typedef int (*_aclDestroyBoolArray)(const aclBoolArray* array); | ||
| 67 | +typedef int (*_aclDestroyTensorList)(const aclTensorList* array); | ||
| 68 | + | ||
| 69 | +constexpr int kHashBufSize = 8192; | ||
| 70 | +constexpr int kHashBufMaxSize = kHashBufSize + 1024; | ||
| 71 | +extern thread_local char g_hashBuf[kHashBufSize]; | ||
| 72 | +extern thread_local int g_hashOffset; | ||
| 73 | + | ||
| 74 | +#define AT_ALL_SCALAR_TYPE_AND_ACL_DATATYPE_PAIR(_) \ | ||
| 75 | + _(at::ScalarType::Byte, ACL_UINT8) \ | ||
| 76 | + _(at::ScalarType::Char, ACL_INT8) \ | ||
| 77 | + _(at::ScalarType::Short, ACL_INT16) \ | ||
| 78 | + _(at::ScalarType::Int, ACL_INT32) \ | ||
| 79 | + _(at::ScalarType::Long, ACL_INT64) \ | ||
| 80 | + _(at::ScalarType::Half, ACL_FLOAT16) \ | ||
| 81 | + _(at::ScalarType::Float, ACL_FLOAT) \ | ||
| 82 | + _(at::ScalarType::Double, ACL_DOUBLE) \ | ||
| 83 | + _(at::ScalarType::ComplexHalf, ACL_DT_UNDEFINED) \ | ||
| 84 | + _(at::ScalarType::ComplexFloat, ACL_COMPLEX64) \ | ||
| 85 | + _(at::ScalarType::ComplexDouble, ACL_COMPLEX128) \ | ||
| 86 | + _(at::ScalarType::Bool, ACL_BOOL) \ | ||
| 87 | + _(at::ScalarType::QInt8, ACL_DT_UNDEFINED) \ | ||
| 88 | + _(at::ScalarType::QUInt8, ACL_DT_UNDEFINED) \ | ||
| 89 | + _(at::ScalarType::QInt32, ACL_DT_UNDEFINED) \ | ||
| 90 | + _(at::ScalarType::BFloat16, ACL_BF16) \ | ||
| 91 | + _(at::ScalarType::QUInt4x2, ACL_DT_UNDEFINED) \ | ||
| 92 | + _(at::ScalarType::QUInt2x4, ACL_DT_UNDEFINED) \ | ||
| 93 | + _(at::ScalarType::Undefined, ACL_DT_UNDEFINED) \ | ||
| 94 | + _(at::ScalarType::NumOptions, ACL_DT_UNDEFINED) | ||
| 95 | + | ||
| 96 | +constexpr aclDataType kATenScalarTypeToAclDataTypeTable[static_cast<int64_t>(at::ScalarType::NumOptions) + 1] = { | ||
| 97 | +#define DEFINE_ENUM(_1, n) n, | ||
| 98 | + AT_ALL_SCALAR_TYPE_AND_ACL_DATATYPE_PAIR(DEFINE_ENUM) | ||
| 99 | +#undef DEFINE_ENUM | ||
| 100 | +}; | ||


README.md 缺少
## 贡献说明章节。根据 CANN 文档交付规范,experimental 目录下新算子必须在 README 中包含贡献说明章节,格式参考:## 贡献说明 | 贡献者 | 贡献方 | 贡献算子 | 贡献时间 | 贡献内容 | | ---- | ---- | ---- | ---- | ---- | | <姓名> | <单位/组织> | <算子名> | <YYYY/MM/DD> | <算子名>算子适配开源仓 |