Open
[feat][ops] norm_multiply_dropout反向用例/示例与构建接入 #235
[feat][ops] norm_multiply_dropout反向用例/示例与构建接入 #235
Open
penghuiyangAdd on 5 days ago
Total: 7 files changed+943-0
@@ -0,0 +1,3 @@
1+# norm_multiply_dropout 前反向聚合
2+add_subdirectory(norm_multiply_dropout_forward)
3+add_subdirectory(norm_multiply_dropout_backward)
@@ -0,0 +1,27 @@
1+# ============================================================================
2+# norm_multiply_dropout_backward 算子聚合 CMakeLists
3+# ----------------------------------------------------------------------------
4+# 设置 vendor 名,使用 npu_op_package + INSTALL_PATH 将产物
5+# 定向到 opp/<variant>/vendors/<op_name>/ 目录。
6+# ============================================================================
7+ 
8+# vendor 名,同时也是 CANN package 名
9+set(VENDOR_NAME "norm_multiply_dropout_backward")
10+ 
11+# 独立 autogen 路径,避免多个算子共享冲突
12+set(ASCEND_AUTOGEN_PATH "${CMAKE_CURRENT_BINARY_DIR}/autogen")
13+file(MAKE_DIRECTORY "${ASCEND_AUTOGEN_PATH}")
14+ 
15+# 安装路径
16+set(OPP_VENDOR_INSTALL_PREFIX
17+ "${CUSTOM_OPP_INSTALL_PREFIX}/vendors/${VENDOR_NAME}")
18+ 
19+npu_op_package(${VENDOR_NAME}
20+ TYPE RUN
21+ CONFIG
22+ INSTALL_PATH ${OPP_VENDOR_INSTALL_PREFIX}
23+ VENDOR_NAME ${VENDOR_NAME}
24+)
25+ 
26+add_subdirectory(op_host)
27+add_subdirectory(op_kernel)
@@ -0,0 +1,209 @@
1+/**
2+ * @file test_aclnn_norm_multiply_dropout_backward.cpp
3+ *
4+ * Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
5+ *
6+ * aclnnNormMultiplyDropoutBackward 两段式接口调用示例。
7+ * 输入 d_out/x/u/weight/bias/mean/var/mask 与 attrs eps/dropout_ratio,
8+ * 输出 (d_x, d_u, d_weight, d_bias)。
9+ * dropout_ratio > 1e-10 时需传入 uint8 位图掩码:mask 元素数 = x 元素数向上对齐
10+ * 128 再除以 8;本示例仅演示接口调用,mask 使用随机数据填充(仅保证 shape 满足
11+ * 输入要求),实际业务应通过 aclnnDropoutGenMaskV2 或上层框架接口生成有效掩码;
12+ * dropout_ratio <= 1e-10 时不消费 mask,shape 传占位 {1}。
13+ * 950 平台算子不支持 fp32,示例输入使用 ACL_FLOAT16;d_weight/d_bias 输出为 fp32。
14+ */
15+#include <algorithm>
16+#include <cstdlib>
17+#include <iostream>
18+#include <numeric>
19+#include <vector>
20+#include "acl/acl.h"
21+#include "aclnn_norm_multiply_dropout_backward.h"
22+ 
23+#define CHECK_RET(cond, expr) \
24+ do { \
25+ if (!(cond)) { \
26+ expr; \
27+ } \
28+ } while (0)
29+ 
30+#define LOG_PRINT(message, ...) \
31+ do { \
32+ printf(message, ##__VA_ARGS__); \
33+ printf("\n"); \
34+ } while (0)
35+ 
36+// ACL_FLOAT16 位模式占位(0x3800 = fp16 0.5),长度与元素数一致
37+using Float16 = uint16_t;
38+ 
39+int32_t deviceId = 0;
40+aclrtStream stream;
41+ 
42+int Init(int32_t deviceId, aclrtStream* stream)
43+{
44+ // 固定写法,AscendCL初始化
45+ auto ret = aclInit(nullptr);
46+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
47+ ret = aclrtSetDevice(deviceId);
48+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
49+ ret = aclrtCreateStream(stream);
50+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
51+ return 0;
52+}
53+ 
54+template <typename T>
55+int CreateAclTensor(const std::vector<int64_t>& shape, void** deviceAddr, std::vector<T>& hostData,
56+ aclDataType dataType, aclTensor** tensor)
57+{
58+ auto size = shape.empty() ? sizeof(T)
59+ : std::accumulate(shape.begin(), shape.end(), sizeof(T),
60+ [](int64_t a, int64_t b) { return a * b; });
61+ *deviceAddr = nullptr;
62+ auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
63+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
64+ ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE);
65+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret);
66+ 
67+ std::vector<int64_t> strides(shape.size(), 1);
68+ for (int64_t i = shape.size() - 2; i >= 0; i--) {
69+ strides[i] = shape[i + 1] * strides[i + 1];
70+ }
71+ 
72+ *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND,
73+ shape.data(), shape.size(), *deviceAddr);
74+ return 0;
75+}
76+ 
77+int main()
78+{
79+ // batch、col 及 tensor 初始化(dtype/buffer 尺寸需与 op_host 校验一致)
80+ int64_t batch = 4;
81+ int64_t col = 512; // 仅支持 512 或 1024
82+ std::vector<int64_t> xShape = {batch, col};
83+ std::vector<int64_t> weightShape = {col};
84+ std::vector<int64_t> batchShape = {batch};
85+ float eps = 1e-5f;
86+ float dropoutRatio = 0.5f;
87+ // mask 生成按 dropout_ratio 分支:
88+ // dropout_ratio > 1e-10:mask 元素数 = x 元素数(batch*col)向上对齐 128 再除以 8;
89+ // dropout_ratio <= 1e-10:不执行 dropout,mask 不消费,shape 传占位 {1}
90+ int64_t maskNumel = dropoutRatio > 1e-10f ? (batch * col + 127) / 128 * 128 / 8 : 1;
91+ 
92+ int InitRsp = Init(deviceId, &stream);
93+ CHECK_RET(InitRsp == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", InitRsp); return InitRsp);
94+ 
95+ std::vector<Float16> dOutHostData(batch * col, 0x3800);
96+ std::vector<Float16> xHostData(batch * col, 0x3800);
97+ std::vector<Float16> uHostData(batch * col, 0x3800);
98+ std::vector<Float16> weightHostData(col, 0x3800);
99+ std::vector<Float16> biasHostData(col, 0x3800);
100+ std::vector<float> meanHostData(batch, 0.0f);
101+ std::vector<float> varHostData(batch, 1.0f);
102+ std::vector<uint8_t> maskHostData(maskNumel, 0);
103+ if (dropoutRatio > 1e-10f) {
104+ // dropout>0:随机生成掩码数据,仅保证 shape 满足输入要求(真实掩码应调用 aclnnDropoutGenMaskV2 生成)
105+ std::generate(maskHostData.begin(), maskHostData.end(), []() { return static_cast<uint8_t>(rand() & 0xFF); });
106+ }
107+ // dropout<=1e-10:mask 保持 {0} 占位,kernel 不消费
108+ std::vector<Float16> dXHostData(batch * col, 0);
109+ std::vector<Float16> dUHostData(batch * col, 0);
110+ std::vector<float> dWeightHostData(col, 0);
111+ std::vector<float> dBiasHostData(col, 0);
112+ 
113+ void *dOutDeviceAddr = nullptr, *xDeviceAddr = nullptr, *uDeviceAddr = nullptr, *weightDeviceAddr = nullptr;
114+ void *biasDeviceAddr = nullptr, *meanDeviceAddr = nullptr, *varDeviceAddr = nullptr, *maskDeviceAddr = nullptr;
115+ void *dXDeviceAddr = nullptr, *dUDeviceAddr = nullptr, *dWeightDeviceAddr = nullptr, *dBiasDeviceAddr = nullptr;
116+ aclTensor *dOut = nullptr, *x = nullptr, *u = nullptr, *weight = nullptr, *bias = nullptr;
117+ aclTensor *mean = nullptr, *var = nullptr, *mask = nullptr;
118+ aclTensor *dX = nullptr, *dU = nullptr, *dWeight = nullptr, *dBias = nullptr;
119+ 
120+ int ret = 0;
121+ ret = CreateAclTensor(xShape, &dOutDeviceAddr, dOutHostData, ACL_FLOAT16, &dOut);
122+ CHECK_RET(ret == ACL_SUCCESS, return ret);
123+ ret = CreateAclTensor(xShape, &xDeviceAddr, xHostData, ACL_FLOAT16, &x);
124+ CHECK_RET(ret == ACL_SUCCESS, return ret);
125+ ret = CreateAclTensor(xShape, &uDeviceAddr, uHostData, ACL_FLOAT16, &u);
126+ CHECK_RET(ret == ACL_SUCCESS, return ret);
127+ ret = CreateAclTensor(weightShape, &weightDeviceAddr, weightHostData, ACL_FLOAT16, &weight);
128+ CHECK_RET(ret == ACL_SUCCESS, return ret);
129+ ret = CreateAclTensor(weightShape, &biasDeviceAddr, biasHostData, ACL_FLOAT16, &bias);
130+ CHECK_RET(ret == ACL_SUCCESS, return ret);
131+ ret = CreateAclTensor(batchShape, &meanDeviceAddr, meanHostData, ACL_FLOAT, &mean);
132+ CHECK_RET(ret == ACL_SUCCESS, return ret);
133+ ret = CreateAclTensor(batchShape, &varDeviceAddr, varHostData, ACL_FLOAT, &var);
134+ CHECK_RET(ret == ACL_SUCCESS, return ret);
135+ ret = CreateAclTensor({maskNumel}, &maskDeviceAddr, maskHostData, ACL_UINT8, &mask);
136+ CHECK_RET(ret == ACL_SUCCESS, return ret);
137+ ret = CreateAclTensor(xShape, &dXDeviceAddr, dXHostData, ACL_FLOAT16, &dX);
138+ CHECK_RET(ret == ACL_SUCCESS, return ret);
139+ ret = CreateAclTensor(xShape, &dUDeviceAddr, dUHostData, ACL_FLOAT16, &dU);
140+ CHECK_RET(ret == ACL_SUCCESS, return ret);
141+ ret = CreateAclTensor(weightShape, &dWeightDeviceAddr, dWeightHostData, ACL_FLOAT, &dWeight);
142+ CHECK_RET(ret == ACL_SUCCESS, return ret);
143+ ret = CreateAclTensor(weightShape, &dBiasDeviceAddr, dBiasHostData, ACL_FLOAT, &dBias);
144+ CHECK_RET(ret == ACL_SUCCESS, return ret);
145+ 
146+ uint64_t workspaceSize = 0;
147+ aclOpExecutor* executor;
148+ 
149+ // 调用第一段接口(输出顺序 (d_x, d_u, d_weight, d_bias) 与算子注册一致)
150+ ret = aclnnNormMultiplyDropoutBackwardGetWorkspaceSize(dOut, x, u, weight, bias, mean, var, mask, eps, dropoutRatio,
151+ dX, dU, dWeight, dBias, &workspaceSize, &executor);
152+ CHECK_RET(ret == ACL_SUCCESS,
153+ LOG_PRINT("aclnnNormMultiplyDropoutBackwardGetWorkspaceSize failed. ERROR: %d\n", ret);
154+ return ret);
155+ 
156+ void* workspaceAddr = nullptr;
157+ if (workspaceSize > 0) {
158+ ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
159+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret);
160+ }
161+ // 调用第二段接口
162+ ret = aclnnNormMultiplyDropoutBackward(workspaceAddr, workspaceSize, executor, stream);
163+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnNormMultiplyDropoutBackward failed. ERROR: %d\n", ret); return ret);
164+ 
165+ ret = aclrtSynchronizeStream(stream);
166+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);
167+ 
168+ LOG_PRINT("aclnnNormMultiplyDropoutBackward execute success\n");
169+ 
170+ // (可选)把 d_x/d_u 拷回 host 侧查看结果
171+ ret = aclrtMemcpy(dXHostData.data(), dXHostData.size() * sizeof(Float16), dXDeviceAddr,
172+ dXHostData.size() * sizeof(Float16), ACL_MEMCPY_DEVICE_TO_HOST);
173+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy d_x failed. ERROR: %d\n", ret); return ret);
174+ ret = aclrtMemcpy(dUHostData.data(), dUHostData.size() * sizeof(Float16), dUDeviceAddr,
175+ dUHostData.size() * sizeof(Float16), ACL_MEMCPY_DEVICE_TO_HOST);
176+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy d_u failed. ERROR: %d\n", ret); return ret);
177+ 
178+ aclDestroyTensor(dOut);
179+ aclDestroyTensor(x);
180+ aclDestroyTensor(u);
181+ aclDestroyTensor(weight);
182+ aclDestroyTensor(bias);
183+ aclDestroyTensor(mean);
184+ aclDestroyTensor(var);
185+ aclDestroyTensor(mask);
186+ aclDestroyTensor(dX);
187+ aclDestroyTensor(dU);
188+ aclDestroyTensor(dWeight);
189+ aclDestroyTensor(dBias);
190+ aclrtFree(dOutDeviceAddr);
191+ aclrtFree(xDeviceAddr);
192+ aclrtFree(uDeviceAddr);
193+ aclrtFree(weightDeviceAddr);
194+ aclrtFree(biasDeviceAddr);
195+ aclrtFree(meanDeviceAddr);
196+ aclrtFree(varDeviceAddr);
197+ aclrtFree(maskDeviceAddr);
198+ aclrtFree(dXDeviceAddr);
199+ aclrtFree(dUDeviceAddr);
200+ aclrtFree(dWeightDeviceAddr);
201+ aclrtFree(dBiasDeviceAddr);
202+ if (workspaceAddr != nullptr) {
203+ aclrtFree(workspaceAddr);
204+ }
205+ aclrtDestroyStream(stream);
206+ aclrtResetDevice(deviceId);
207+ aclFinalize();
208+ return 0;
209+}
@@ -0,0 +1,46 @@
1+#!/usr/bin/env python3
2+# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
3+#
4+# Licensed under the Apache License, Version 2.0 (the "License");
5+# you may not use this file except in compliance with the License.
6+# You may obtain a copy of the License at
7+#
8+# http://www.apache.org/licenses/LICENSE-2.0
9+#
10+# Unless required by applicable law or agreed to in writing, software
11+# distributed under the License is distributed on an "AS IS" BASIS,
12+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+# See the License for the specific language governing permissions and
14+# limitations under the License.
15+# ==============================================================================
16+"""norm_multiply_dropout 反向 torch 调用示例(autograd 触发)。"""
17+ 
18+import torch
19+ 
20+import ops_rec
21+ 
22+ 
23+def main():
24+ batch, col = 4, 512
25+ dtype = torch.bfloat16
26+ device = "npu"
27+ 
28+ x = (torch.rand([batch, col], device=device, dtype=dtype) - 0.5).requires_grad_(True)
29+ u = (torch.rand([batch, col], device=device, dtype=dtype) - 0.5).requires_grad_(True)
30+ weight = (torch.rand([col], device=device, dtype=dtype) - 0.5).requires_grad_(True)
31+ bias = (torch.rand([col], device=device, dtype=dtype) - 0.5).requires_grad_(True)
32+ 
33+ # 前向 + loss.backward() 自动触发 norm_multiply_dropout_backward;
34+ # 高级接口仅返回 output
35+ output = ops_rec.ascendc.activation.norm_multiply_dropout(x, u, weight, bias, eps=1e-5, dropout_ratio=0.0)
36+ loss = output.float().sum()
37+ loss.backward()
38+ 
39+ print("d_x:", x.grad.shape, x.grad.dtype)
40+ print("d_u:", u.grad.shape, u.grad.dtype)
41+ print("d_weight:", weight.grad.shape, weight.grad.dtype)
42+ print("d_bias:", bias.grad.shape, bias.grad.dtype)
43+ 
44+ 
45+if __name__ == "__main__":
46+ main()
@@ -0,0 +1,4 @@
1+[pytest]
2+markers =
3+ ci: mark a test as a CI test
4+ graph: marks tests as graph mode compilation tests
@@ -0,0 +1,627 @@
1+#!/usr/bin/env python3
2+# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
3+#
4+# Licensed under the Apache License, Version 2.0 (the "License");
5+# you may not use this file except in compliance with the License.
6+# You may obtain a copy of the License at
7+#
8+# http://www.apache.org/licenses/LICENSE-2.0
9+#
10+# Unless required by applicable law or agreed to in writing, software
11+# distributed under the License is distributed on an "AS IS" BASIS,
12+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+# See the License for the specific language governing permissions and
14+# limitations under the License.
15+# ==============================================================================
16+"""norm_multiply_dropout 反向双标杆比对测试(dropout_ratio=0,与迁移前实现一致)。
17+ 
18+三路执行:NPU 融合算子(autograd backward)、NPU 小算子拼接(F.layer_norm +
19+autograd)、CPU 公式 golden(torch 实现,pairwise 求和),按 MARE/MERE/RMSE
20+误差比值做双标杆比对。
21+"""
22+ 
23+import sys
24+import pytest
25+import torch
26+import torch.nn.functional as F
27+from torch import Tensor
28+ 
29+import ops_rec
30+ 
31+MARE_L1 = 5
32+MERE_L1 = 1.5
33+RMSE_L1 = 1.5
34+ 
35+# 增大 cache 避免 dynamic=True 时因重编译超限报错
36+torch._dynamo.config.cache_size_limit = 256
37+ 
38+torch.npu.config.allow_internal_format = False
39+ 
40+EPS = 1e-5
41+ 
42+DIM0_MAX = int(1e6)
43+SUPPORT_DIM1_LIST = [512, 1024]
44+ 
45+ 
46+def _is_ascend_950():
47+ try:
48+ return "Ascend950" in torch.npu.get_device_name()
49+ except Exception:
50+ return False
51+ 
52+ 
53+# 950 平台不支持 fp32(op_host 条件编译校验),其余平台支持
54+SUPPORT_DTYPES = [torch.bfloat16, torch.float16]
55+if not _is_ascend_950():
56+ SUPPORT_DTYPES.append(torch.float32)
57+ 
58+ 
59+def torch_compiled(model, **kwargs):
60+ if sys.version_info < (3, 12, 0):
61+ return torch.compile(model, **kwargs)
62+ return model
63+ 
64+ 
65+# ===== 双标杆比对框架 =====
66+ 
67+ 
68+def compute_mare(actual: Tensor, golden: Tensor):
69+ """计算最大相对误差"""
70+ if actual.shape != golden.shape:
71+ raise ValueError(f"actual shape {actual.shape} != golden shape {golden.shape}")
72+ # 计算相对误差,添加防止溢出的处理
73+ diff = torch.abs(actual - golden)
74+ denominator = torch.abs(golden) + 1e-7
75+ rel_error = torch.where(denominator > 1e-7, diff / denominator, diff)
76+ return rel_error.max().item()
77+ 
78+ 
79+def compute_mere(actual: Tensor, golden: Tensor):
80+ """计算平均相对误差"""
81+ if actual.shape != golden.shape:
82+ raise ValueError(f"actual shape {actual.shape} != golden shape {golden.shape}")
83+ # 计算相对误差,添加防止溢出的处理
84+ diff = torch.abs(actual - golden)
85+ denominator = torch.abs(golden) + 1e-7
86+ rel_error = torch.where(denominator > 1e-7, diff / denominator, diff)
87+ return rel_error.mean().item()
88+ 
89+ 
90+def compute_rmse(actual: Tensor, golden: Tensor):
91+ """计算均方根误差"""
92+ if actual.shape != golden.shape:
93+ raise ValueError(f"actual shape {actual.shape} != golden shape {golden.shape}")
94+ squared_error = (actual - golden).pow(2)
95+ mse = torch.mean(squared_error)
96+ return torch.sqrt(mse).item()
97+ 
98+ 
99+def compare_data_with_double_pole(tensor_msg: str, actual_fused: Tensor, actual_npu: Tensor, golden: Tensor):
100+ """双标杆对比
@@ -0,0 +1,27 @@
1+# ============================================================================
2+# norm_multiply_dropout_forward 算子聚合 CMakeLists
3+# ----------------------------------------------------------------------------
4+# 设置 vendor 名,使用 npu_op_package + INSTALL_PATH 将产物
5+# 定向到 opp/<variant>/vendors/<op_name>/ 目录。
6+# ============================================================================
7+ 
8+# vendor 名,同时也是 CANN package 名
9+set(VENDOR_NAME "norm_multiply_dropout_forward")
10+ 
11+# 独立 autogen 路径,避免多个算子共享冲突
12+set(ASCEND_AUTOGEN_PATH "${CMAKE_CURRENT_BINARY_DIR}/autogen")
13+file(MAKE_DIRECTORY "${ASCEND_AUTOGEN_PATH}")
14+ 
15+# 安装路径
16+set(OPP_VENDOR_INSTALL_PREFIX
17+ "${CUSTOM_OPP_INSTALL_PREFIX}/vendors/${VENDOR_NAME}")
18+ 
19+npu_op_package(${VENDOR_NAME}
20+ TYPE RUN
21+ CONFIG
22+ INSTALL_PATH ${OPP_VENDOR_INSTALL_PREFIX}
23+ VENDOR_NAME ${VENDOR_NAME}
24+)
25+ 
26+add_subdirectory(op_host)
27+add_subdirectory(op_kernel)