已合并
optimize the performance of aicpu realdiv operator #2208
ZhaiPeiChao创建于 4月13日
optimize the performance of aicpu realdiv operator #2208
已合并
ZhaiPeiChao创建于 4月13日
3 个文件变更+598-201
Mmath/real_div/op_kernel_aicpu/real_div_aicpu.cpp+382-192
@@ -10,11 +10,11 @@
10 10 
11#include "real_div_aicpu.h"11#include "real_div_aicpu.h"
12 12 
13-#include <cstdint>13+#include <stdint.h>
14+#include <algorithm>
14#include <vector>15#include <vector>
15 16 
16#include "Eigen/Dense"17#include "Eigen/Dense"
17-#include "unsupported/Eigen/CXX11/Tensor"
18#include "cpu_kernel_utils.h"18#include "cpu_kernel_utils.h"
19#include "cpu_types.h"19#include "cpu_types.h"
20#include "kernel_util.h"20#include "kernel_util.h"
@@ -22,213 +22,403 @@
22#include "securec.h"22#include "securec.h"
23#include "status.h"23#include "status.h"
24 24 
25-namespace {
26-const char *const kDIV = "Div";
27-const char *const kRealDiv = "RealDiv";
28-constexpr int32_t kDim0 = 0;
29-constexpr int32_t kDim1 = 1;
30-constexpr int32_t kDim2 = 2;
31-constexpr int32_t kDim3 = 3;
32-constexpr int32_t kDim4 = 4;
33-constexpr int32_t kDim5 = 5;
34-constexpr int32_t kDim6 = 6;
35-constexpr int32_t kDim7 = 7;
36-constexpr int32_t kDim8 = 8;
37-} // namespace
38- 
39namespace aicpu {25namespace aicpu {
40-uint32_t RealDivKernel::RealDivSameTypeCompute(const CpuKernelContext &ctx, DataType data_type) {26+namespace {
41- switch (data_type) {27+const char* const kDIV = "Div";
42- case DT_FLOAT16:28+const char* const kRealDiv = "RealDiv";
43- return RealDivCompute<Eigen::half>(ctx, false);29+constexpr int64_t kParallelDataNum = 2 * 1024;
44- case DT_FLOAT:30+constexpr int64_t kParallelDataNumMid = 16 * 1024;
45- return RealDivCompute<float>(ctx, false);31+constexpr int64_t kParallelDataNumSameShape = 7 * 1024;
46- case DT_DOUBLE:32+constexpr int64_t kParallelDataNumSameShapeMid = 35 * 1024;
47- return RealDivCompute<double>(ctx, false);33+constexpr int32_t kMaxBcastDims = 8;
48- case DT_INT8:34+constexpr uint32_t kReserveCpuNum = 2U;
49- return RealDivCompute<int8_t>(ctx);
50- case DT_INT16:
51- return RealDivCompute<int16_t>(ctx);
52- case DT_INT32:
53- return RealDivCompute<int32_t>(ctx);
54- case DT_INT64:
55- return RealDivCompute<int64_t>(ctx);
56- case DT_UINT8:
57- return RealDivCompute<uint8_t>(ctx);
58- case DT_UINT16:
59- return RealDivCompute<uint16_t>(ctx);
60- case DT_UINT32:
61- return RealDivCompute<uint32_t>(ctx);
62- case DT_UINT64:
63- return RealDivCompute<uint64_t>(ctx);
64- case DT_COMPLEX64:
65- return RealDivCompute<std::complex<float>>(ctx, false);
66- case DT_COMPLEX128:
67- return RealDivCompute<std::complex<double>>(ctx, false);
68- default:
69- KERNEL_LOG_ERROR("[%s] Data type of input is not support, input data type is [%s].",
70- ctx.GetOpType().c_str(), DTypeStr(data_type).c_str());
71- return KERNEL_STATUS_PARAM_INVALID;
72- }
73-}
74 35 
75-template <typename T>36+// BcastDivInfo: pre-computed stride info for stride-based broadcast iteration.
76-bool RealDivKernel::IsInputHasZero(T *input_data, const int64_t num_of_elems) {37+// Replaces per-element GetBroadcastXIndex/YIndex (which does O(ndims) integer
77- std::vector<T> check_data(input_data, input_data + num_of_elems);38+// divisions per element) with an O(1)-amortized carry-propagation scheme.
78- return std::any_of(check_data.begin(), check_data.end(), [](T &cur_data) {39+struct BcastDivInfo {
79- return IsValueEqual<T>(cur_data, T(0));40+ int32_t ndims;
80- });41+ int64_t out_shape[kMaxBcastDims];
81-}42+ int64_t x_strides[kMaxBcastDims]; // effective input-x stride; 0 for broadcast dims
43+ int64_t y_strides[kMaxBcastDims]; // effective input-y stride; 0 for broadcast dims
44+ int64_t out_strides[kMaxBcastDims]; // row-major strides of the output shape
45+ int64_t total_elements;
46+};
82 47 
83-template <typename T>48+// Compute BcastDivInfo from the two input shapes.
84-uint32_t RealDivKernel::DispatchByRank(int32_t rank, BCalcInfo &calc_info, const CpuKernelContext &ctx) {49+// Steps as follows
85- switch (rank) {50+// 1. Pad to equal rank, validate broadcast, compute output shape.
86- case kDim0: {51+// 2. Compute effective strides (natural stride for non-broadcast dims, 0 otherwise).
87- T v0 = *(reinterpret_cast<const T*>(calc_info.input_0->GetData()));52+// 3. Remove output-size-1 dimensions (no iteration needed).
88- T v1 = *(reinterpret_cast<const T*>(calc_info.input_1->GetData()));53+// 4. Collapse adjacent contiguous dimensions to reduce loop depth.
89- T* value_out = reinterpret_cast<T*>(calc_info.output->GetData());54+bool ComputeBcastDivInfo(const std::vector<int64_t>& x_shape, const std::vector<int64_t>& y_shape, BcastDivInfo& info)
90- *(value_out) = v0 / v1;55+{
91- return KERNEL_STATUS_OK;56+ const int32_t x_rank = static_cast<int32_t>(x_shape.size());
57+ const int32_t y_rank = static_cast<int32_t>(y_shape.size());
58+ const int32_t max_rank = std::max(x_rank, y_rank);
59+ if (max_rank == 0 || max_rank > kMaxBcastDims) {
60+ return false;
61+ }
62+ 
63+ // Step 1: Pad shorter shape with leading 1s & validate.
64+ int64_t xp[kMaxBcastDims], yp[kMaxBcastDims], out[kMaxBcastDims];
65+ for (int32_t i = 0; i < max_rank; ++i) {
66+ xp[i] = (i >= max_rank - x_rank) ? x_shape[i - (max_rank - x_rank)] : 1;
67+ yp[i] = (i >= max_rank - y_rank) ? y_shape[i - (max_rank - y_rank)] : 1;
68+ }
69+ for (int32_t i = 0; i < max_rank; ++i) {
70+ if (xp[i] == yp[i]) {
71+ out[i] = xp[i];
72+ } else if (xp[i] == 1) {
73+ out[i] = yp[i];
74+ } else if (yp[i] == 1) {
75+ out[i] = xp[i];
76+ } else {
77+ return false;
92 }78 }
93- case kDim1:79+ }
94- return RealDivCalculateWithAlignedCheck<kDim1, T>(calc_info);80+ 
95- case kDim2:81+ // Step 2: Natural strides from each input's own shape.
96- return RealDivCalculateWithAlignedCheck<kDim2, T>(calc_info);82+ int64_t xn[kMaxBcastDims], yn[kMaxBcastDims];
97- case kDim3:83+ xn[max_rank - 1] = 1;
98- return RealDivCalculateWithAlignedCheck<kDim3, T>(calc_info);84+ yn[max_rank - 1] = 1;
99- case kDim4:85+ for (int32_t d = max_rank - 2; d >= 0; --d) {
100- return RealDivCalculateWithAlignedCheck<kDim4, T>(calc_info);86+ xn[d] = xn[d + 1] * xp[d + 1];
101- case kDim5:87+ yn[d] = yn[d + 1] * yp[d + 1];
102- return RealDivCalculateWithAlignedCheck<kDim5, T>(calc_info);88+ }
103- case kDim6:89+ 
104- return RealDivCalculateWithAlignedCheck<kDim6, T>(calc_info);90+ // Effective stride = natural stride if dim matches output, else 0 (broadcast).
105- case kDim7:91+ int64_t xe[kMaxBcastDims], ye[kMaxBcastDims];
106- return RealDivCalculateWithAlignedCheck<kDim7, T>(calc_info);92+ for (int32_t d = 0; d < max_rank; ++d) {
107- case kDim8:93+ xe[d] = (xp[d] == out[d]) ? xn[d] : 0;
108- return RealDivCalculateWithAlignedCheck<kDim8, T>(calc_info);94+ ye[d] = (yp[d] == out[d]) ? yn[d] : 0;
95+ }
96+ 
97+ // Step 3: Remove output dims of size 1 (no work to iterate).
98+ int64_t to[kMaxBcastDims], tx[kMaxBcastDims], ty[kMaxBcastDims];
99+ int32_t ndims = 0;
100+ for (int32_t d = 0; d < max_rank; ++d) {
101+ if (out[d] != 1) {
102+ to[ndims] = out[d];
103+ tx[ndims] = xe[d];
104+ ty[ndims] = ye[d];
105+ ndims++;
106+ }
107+ }
108+ if (ndims == 0) {
109+ info.ndims = 1;
110+ info.out_shape[0] = 1;
111+ info.x_strides[0] = 1;
112+ info.y_strides[0] = 1;
113+ info.out_strides[0] = 1;
114+ info.total_elements = 1;
115+ return true;
116+ }
117+ 
118+ // Step 4: Collapse contiguous dims.
119+ // Two adjacent dims (prev, cur) can be merged if for BOTH x and y:
120+ // prev_stride == cur_stride * cur_size (or both strides are 0).
121+ info.out_shape[0] = to[0];
122+ info.x_strides[0] = tx[0];
123+ info.y_strides[0] = ty[0];
124+ int32_t collapsed = 1;
125+ 
126+ for (int32_t d = 1; d < ndims; ++d) {
127+ bool x_ok =
128+ (info.x_strides[collapsed - 1] == tx[d] * to[d]) || (info.x_strides[collapsed - 1] == 0 && tx[d] == 0);
129+ bool y_ok =
130+ (info.y_strides[collapsed - 1] == ty[d] * to[d]) || (info.y_strides[collapsed - 1] == 0 && ty[d] == 0);
131+ 
132+ if (x_ok && y_ok) {
133+ info.out_shape[collapsed - 1] *= to[d];
134+ info.x_strides[collapsed - 1] = tx[d];
135+ info.y_strides[collapsed - 1] = ty[d];
136+ } else {
137+ info.out_shape[collapsed] = to[d];
138+ info.x_strides[collapsed] = tx[d];
139+ info.y_strides[collapsed] = ty[d];
140+ collapsed++;
141+ }
142+ }
143+ 
144+ info.ndims = collapsed;
145+ info.out_strides[collapsed - 1] = 1;
146+ for (int32_t d = collapsed - 2; d >= 0; --d) {
147+ info.out_strides[d] = info.out_strides[d + 1] * info.out_shape[d + 1];
148+ }
149+ info.total_elements = info.out_strides[0] * info.out_shape[0];
150+ return true;
151+}
152+ 
153+template <typename T>
154+void SpecialComputeImpl(BcastShapeType type, int64_t start, int64_t end, const T* in0, const T* in1, T* out)
155+{
156+ switch (type) {
157+ case BcastShapeType::SAME_SHAPE:
158+ for (int64_t i = start; i < end; ++i) {
159+ out[i] = in0[i] / in1[i];
160+ }
161+ break;
162+ case BcastShapeType::X_ONE_ELEMENT:
163+ for (int64_t i = start; i < end; ++i) {
164+ out[i] = in0[0] / in1[i];
165+ }
166+ break;
167+ case BcastShapeType::Y_ONE_ELEMENT:
168+ for (int64_t i = start; i < end; ++i) {
169+ out[i] = in0[i] / in1[0];
170+ }
171+ break;
172+ default:
173+ break;
174+ }
175+}
176+ 
177+template <typename T>
178+uint32_t NoBcastComputeImpl(const CpuKernelContext& ctx)
179+{
180+ auto in0 = reinterpret_cast<T*>(ctx.Input(kFirstInputIndex)->GetData());
181+ auto in1 = reinterpret_cast<T*>(ctx.Input(kSecondInputIndex)->GetData());
182+ auto out = reinterpret_cast<T*>(ctx.Output(kFirstOutputIndex)->GetData());
183+ int64_t in0_num = ctx.Input(kFirstInputIndex)->NumElements();
184+ int64_t in1_num = ctx.Input(kSecondInputIndex)->NumElements();
185+ int64_t data_num = ctx.Output(kFirstOutputIndex)->NumElements();
186+ 
187+ BcastShapeType type = (in0_num == in1_num) ? BcastShapeType::SAME_SHAPE :
188+ (in0_num == 1) ? BcastShapeType::X_ONE_ELEMENT :
189+ BcastShapeType::Y_ONE_ELEMENT;
190+ 
191+ if (data_num >= kParallelDataNumSameShape) {
192+ uint32_t min_core_num = 1U;
193+ uint32_t max_core_num = std::max(min_core_num, std::max(CpuKernelUtils::GetCPUNum(ctx), kReserveCpuNum) - kReserveCpuNum);
194+ if (data_num <= kParallelDataNumSameShapeMid) {
195+ max_core_num = std::min(max_core_num, 4U);
196+ }
197+ if (static_cast<int64_t>(max_core_num) > data_num) {
198+ max_core_num = static_cast<uint32_t>(data_num);
199+ }
200+ auto sharder = [&type, &in0, &in1, &out](int64_t start, int64_t end) {
201+ SpecialComputeImpl<T>(type, start, end, in0, in1, out);
202+ };
203+ KERNEL_HANDLE_ERROR(
204+ CpuKernelUtils::ParallelFor(ctx, data_num, data_num / max_core_num, sharder), "RealDiv Compute failed.")
205+ } else {
206+ SpecialComputeImpl<T>(type, 0, data_num, in0, in1, out);
207+ }
208+ return KERNEL_STATUS_OK;
209+}
210+ 
211+// BcastComputeImpl: stride-based broadcast division.
212+// 1. Decompose the shard-start index into multi-dim coordinates (O(ndims)
213+// divisions done ONCE per shard).
214+// 2. Iterate the innermost dimension in a tight loop with constant strides
215+// (zero divisions, compiler auto-vectorisable on ARM NEON).
216+// 3. On innermost-dim boundary, propagate a carry to outer dims (amortised
217+// O(1) additions per element).
218+//
219+// This reduces index-computation cost from ~2*ndims divisions per element
220+// to essentially zero in the hot path.
221+template <typename T>
222+uint32_t BcastComputeImpl(const CpuKernelContext& ctx, const BcastDivInfo& info)
223+{
224+ auto in0 = reinterpret_cast<const T*>(ctx.Input(kFirstInputIndex)->GetData());
225+ auto in1 = reinterpret_cast<const T*>(ctx.Input(kSecondInputIndex)->GetData());
226+ auto out = reinterpret_cast<T*>(ctx.Output(kFirstOutputIndex)->GetData());
227+ const int64_t data_num = info.total_elements;
228+ const int32_t ndims = info.ndims;
229+ const int64_t x_inner = info.x_strides[ndims - 1];
230+ const int64_t y_inner = info.y_strides[ndims - 1];
231+ 
232+ auto sharder = [&in0, &in1, &out, &ndims, &x_inner, &y_inner, &info](int64_t start, int64_t end) {
233+ // Decompose start into multi-dim coordinates & offsets.
234+ int64_t coords[kMaxBcastDims] = {0};
235+ int64_t x_off = 0;
236+ int64_t y_off = 0;
237+ 
238+ if (start > 0) {
239+ int64_t rem = start;
240+ for (int32_t d = 0; d < ndims; ++d) {
241+ coords[d] = rem / info.out_strides[d];
242+ rem -= coords[d] * info.out_strides[d];
243+ x_off += coords[d] * info.x_strides[d];
244+ y_off += coords[d] * info.y_strides[d];
245+ }
246+ }
247+ 
248+ // Main loop: process one innermost-dim strip per iteration.
249+ int64_t idx = start;
250+ while (idx < end) {
251+ const int64_t inner_remain = info.out_shape[ndims - 1] - coords[ndims - 1];
252+ const int64_t chunk = std::min(inner_remain, end - idx);
253+ 
254+ // Inner loop dispatch based on innermost stride pattern.
255+ // The branch is loop-invariant (same path every iteration) so the
256+ // CPU branch predictor handles it perfectly.
257+ if (x_inner == 1 && y_inner == 1) {
258+ // Both inputs contiguous in innermost dim — best case.
259+ // Compiler will auto-vectorise with NEON vdivq on aarch64.
260+ const T* xp = in0 + x_off;
261+ const T* yp = in1 + y_off;
262+ T* op = out + idx;
263+ for (int64_t i = 0; i < chunk; ++i) {
264+ op[i] = xp[i] / yp[i];
265+ }
266+ } else if (y_inner == 0) {
267+ // Y broadcast in innermost dim — hoist scalar load.
268+ const T y_val = in1[y_off];
269+ for (int64_t i = 0; i < chunk; ++i) {
270+ out[idx + i] = in0[x_off + i * x_inner] / y_val;
271+ }
272+ } else if (x_inner == 0) {
273+ // X broadcast in innermost dim — hoist scalar load.
274+ const T x_val = in0[x_off];
275+ for (int64_t i = 0; i < chunk; ++i) {
276+ out[idx + i] = x_val / in1[y_off + i * y_inner];
277+ }
278+ } else {
279+ // General strided case.
280+ for (int64_t i = 0; i < chunk; ++i) {
281+ out[idx + i] = in0[x_off + i * x_inner] / in1[y_off + i * y_inner];
282+ }
283+ }
284+ 
285+ idx += chunk;
286+ x_off += chunk * x_inner;
287+ y_off += chunk * y_inner;
288+ coords[ndims - 1] += chunk;
289+ 
290+ // Carry propagation: reset overflowed dims, increment parent.
291+ for (int32_t d = ndims - 1; d >= 0; --d) {
292+ if (coords[d] < info.out_shape[d])
293+ break;
294+ x_off -= coords[d] * info.x_strides[d];
295+ y_off -= coords[d] * info.y_strides[d];
296+ coords[d] = 0;
297+ if (d > 0) {
298+ coords[d - 1]++;
299+ x_off += info.x_strides[d - 1];
300+ y_off += info.y_strides[d - 1];
301+ }
302+ }
303+ }
304+ };
305+ 
306+ if (data_num >= kParallelDataNum) {
307+ uint32_t min_core_num = 1U;
308+ uint32_t max_core_num = std::max(min_core_num, std::max(CpuKernelUtils::GetCPUNum(ctx), kReserveCpuNum) - kReserveCpuNum);
309+ if (data_num <= kParallelDataNumMid) {
310+ max_core_num = std::min(max_core_num, 4U);
311+ }
312+ if (static_cast<int64_t>(max_core_num) > data_num) {
313+ max_core_num = static_cast<uint32_t>(data_num);
314+ }
315+ int64_t shard_size = data_num / max_core_num;
316+ KERNEL_HANDLE_ERROR(CpuKernelUtils::ParallelFor(ctx, data_num, shard_size, sharder), "RealDiv Compute failed.")
317+ } else {
318+ sharder(0, data_num);
319+ }
320+ return KERNEL_STATUS_OK;
321+}
322+ 
323+} // anonymous namespace
324+ 
325+uint32_t RealDivKernel::RealDivSameTypeCompute(const CpuKernelContext& ctx, DataType data_type)
326+{
327+ switch (data_type) {
328+ case DT_FLOAT16:
329+ return RealDivCompute<Eigen::half>(ctx, false);
330+ case DT_FLOAT:
331+ return RealDivCompute<float>(ctx, false);
332+ case DT_DOUBLE:
333+ return RealDivCompute<double>(ctx, false);
334+ case DT_INT8:
335+ return RealDivCompute<int8_t>(ctx);
336+ case DT_INT16:
337+ return RealDivCompute<int16_t>(ctx);
338+ case DT_INT32:
339+ return RealDivCompute<int32_t>(ctx);
340+ case DT_INT64:
341+ return RealDivCompute<int64_t>(ctx);
342+ case DT_UINT8:
343+ return RealDivCompute<uint8_t>(ctx);
344+ case DT_UINT16:
345+ return RealDivCompute<uint16_t>(ctx);
346+ case DT_UINT32:
347+ return RealDivCompute<uint32_t>(ctx);
348+ case DT_UINT64:
349+ return RealDivCompute<uint64_t>(ctx);
350+ case DT_COMPLEX64:
351+ return RealDivCompute<std::complex<float>>(ctx, false);
352+ case DT_COMPLEX128:
353+ return RealDivCompute<std::complex<double>>(ctx, false);
109 default:354 default:
110 KERNEL_LOG_ERROR(355 KERNEL_LOG_ERROR(
111- "[%s] Rank of output should less than 8 but get [%zu].", ctx.GetOpType().c_str(),356+ "[%s] Data type of input is not support, input data type is [%s].", ctx.GetOpType().c_str(),
112- calc_info.shape_out.size());357+ DTypeStr(data_type).c_str());
113 return KERNEL_STATUS_PARAM_INVALID;358 return KERNEL_STATUS_PARAM_INVALID;
114 }359 }
115}360}
116 361 
117template <typename T>362template <typename T>
118-uint32_t RealDivKernel::RealDivCompute(const CpuKernelContext &ctx, const bool verify_zero) {363+bool RealDivKernel::IsInputHasZero(T* input_data, const int64_t num_of_elems)
119- Tensor *input1 = ctx.Input(kSecondInputIndex);364+{
120- if (verify_zero && IsInputHasZero<T>(static_cast<T *>(input1->GetData()), input1->NumElements())) {365+ for (int64_t i = 0; i < num_of_elems; ++i) {
121- KERNEL_LOG_ERROR("Invalid argument, division by zero.");366+ if (IsValueEqual<T>(input_data[i], T(0))) {
122- return KERNEL_STATUS_PARAM_INVALID;367+ return true;
123- }368+ }
124- BCalcInfo calc_info;369+ }
125- calc_info.input_0 = ctx.Input(kFirstInputIndex);370+ return false;
126- calc_info.input_1 = input1;
127- calc_info.output = ctx.Output(kFirstOutputIndex);
128- KERNEL_CHECK_NULLPTR(calc_info.input_0->GetData(),
129- KERNEL_STATUS_PARAM_INVALID, "[%s] Get input 0 data failed",
130- ctx.GetOpType().c_str())
131- KERNEL_CHECK_NULLPTR(calc_info.input_1->GetData(),
132- KERNEL_STATUS_PARAM_INVALID, "[%s] Get input 1 data failed",
133- ctx.GetOpType().c_str())
134- KERNEL_CHECK_NULLPTR(calc_info.output->GetData(), KERNEL_STATUS_PARAM_INVALID,
135- "[%s] Get output data failed", ctx.GetOpType().c_str())
136- KERNEL_LOG_INFO(
137- "[%s] Input[0] data size is [%lu], input[1] data size is [%lu], "
138- "output data size is [%lu].",
139- ctx.GetOpType().c_str(), calc_info.input_0->GetDataSize(),
140- calc_info.input_1->GetDataSize(), calc_info.output->GetDataSize());
141- // broadcast input
142- Bcast bcast;
143- if (bcast.GenerateBcastInfo(calc_info) != KERNEL_STATUS_OK) {
144- KERNEL_LOG_ERROR("[%s] Generate broadcast info failed.", ctx.GetOpType().c_str());
145- return KERNEL_STATUS_PARAM_INVALID;
146- }
147- bcast.GetBcastVec(calc_info);
148- int32_t rank = static_cast<int32_t>(calc_info.shape_out.size());
149- return DispatchByRank<T>(rank, calc_info, ctx);
150}371}
151 372 
152-template <int32_t RANK, typename T>373+template <typename T>
153-uint32_t RealDivKernel::RealDivCalculateWithAlignedCheck(BCalcInfo &calc_info) {374+uint32_t RealDivKernel::RealDivCompute(const CpuKernelContext& ctx, const bool verify_zero)
154- if (AlignedCheck(calc_info)) {375+{
155- return RealDivCalculate<RANK, T, Eigen::Aligned>(calc_info);376+ Tensor* input0 = ctx.Input(kFirstInputIndex);
156- }377+ Tensor* input1 = ctx.Input(kSecondInputIndex);
157- return RealDivCalculate<RANK, T, Eigen::Unaligned>(calc_info);378+ 
379+ if (verify_zero && IsInputHasZero<T>(static_cast<T*>(input1->GetData()), input1->NumElements())) {
380+ KERNEL_LOG_ERROR("Invalid argument, division by zero.");
381+ return KERNEL_STATUS_PARAM_INVALID;
382+ }
383+ 
384+ auto input0_shape = input0->GetTensorShape()->GetDimSizes();
385+ auto input1_shape = input1->GetTensorShape()->GetDimSizes();
386+ int64_t input0_elements = input0->NumElements();
387+ int64_t input1_elements = input1->NumElements();
388+ 
389+ // Fast path: same shape, or one input is scalar — no broadcast needed.
390+ bool no_bcast = (input0_shape == input1_shape) || (input0_elements == 1) || (input1_elements == 1);
391+ if (no_bcast) {
392+ return NoBcastComputeImpl<T>(ctx);
393+ }
394+ 
395+ // General broadcast path: stride-based iteration.
396+ BcastDivInfo info;
397+ if (!ComputeBcastDivInfo(input0_shape, input1_shape, info)) {
398+ KERNEL_LOG_ERROR("[%s] Broadcast shapes are incompatible.", ctx.GetOpType().c_str());
399+ return KERNEL_STATUS_PARAM_INVALID;
400+ }
401+ return BcastComputeImpl<T>(ctx, info);
158}402}
159 403 
160-bool RealDivKernel::AlignedCheck(const BCalcInfo &calc_info) const {404+uint32_t RealDivKernel::Compute(CpuKernelContext& ctx)
161- return AddrAlignedCheck(calc_info.input_0->GetData()) &&405+{
162- AddrAlignedCheck(calc_info.input_1->GetData()) &&406+ KERNEL_HANDLE_ERROR(NormalCheck(ctx, INPUT_NUM2, 1), "Div check input output params failed.");
163- AddrAlignedCheck(calc_info.output->GetData());407+ Tensor* input0 = ctx.Input(kFirstInputIndex);
164-}408+ Tensor* input1 = ctx.Input(kSecondInputIndex);
409+ if ((input0->GetDataSize() == 0) || (input1->GetDataSize() == 0)) {
410+ KERNEL_LOG_INFO("[%s] Input is empty tensor.", ctx.GetOpType().c_str());
411+ return KERNEL_STATUS_OK;
412+ }
165 413 
166-template <int32_t RANK, typename T, int32_t OPTION>414+ DataType input0_type = input0->GetDataType();
167-uint32_t RealDivKernel::RealDivCalculate(BCalcInfo &calc_info) {415+ DataType input1_type = input1->GetDataType();
168- Eigen::TensorMap<Eigen::Tensor<T, 1>, OPTION> input0(416+ KERNEL_CHECK_FALSE(
169- static_cast<T *>(calc_info.input_0->GetData()),417+ (input0_type == input1_type), KERNEL_STATUS_PARAM_INVALID, "input0 type[%s] is not equal to input1 type[%s]",
170- calc_info.input_0->GetTensorShape()->NumElements());418+ DTypeStr(input0_type).c_str(), DTypeStr(input1_type).c_str());
171- Eigen::TensorMap<Eigen::Tensor<T, 1>, OPTION> input1(419+ return RealDivSameTypeCompute(ctx, input0_type);
172- static_cast<T *>(calc_info.input_1->GetData()),
173- calc_info.input_1->GetTensorShape()->NumElements());
174- Eigen::TensorMap<Eigen::Tensor<T, 1>, OPTION> output(
175- static_cast<T *>(calc_info.output->GetData()),
176- calc_info.output->GetTensorShape()->NumElements());
177- auto input_shape_0 = calc_info.input_0->GetTensorShape()->GetDimSizes();
178- auto input_shape_1 = calc_info.input_1->GetTensorShape()->GetDimSizes();
179- if (input_shape_0.empty()) {
180- T v0 = *(reinterpret_cast<const T *>(calc_info.input_0->GetData()));
181- output = v0 / input1;
182- return KERNEL_STATUS_OK;
183- }
184- 
185- if (input_shape_1.empty()) {
186- T v1 = *(reinterpret_cast<const T *>(calc_info.input_1->GetData()));
187- output = input0 / v1;
188- return KERNEL_STATUS_OK;
189- }
190- 
191- Eigen::DSizes<Eigen::DenseIndex, RANK> reshape_0;
192- Eigen::DSizes<Eigen::DenseIndex, RANK> reshape_1;
193- Eigen::DSizes<Eigen::DenseIndex, RANK> shape_out;
194- Eigen::array<Eigen::DenseIndex, RANK> bcast_0;
195- Eigen::array<Eigen::DenseIndex, RANK> bcast_1;
196- 
197- for (int32_t i = 0; i < RANK; i++) {
198- reshape_0[(RANK - i) - 1] = calc_info.reshape_0[i];
199- reshape_1[(RANK - i) - 1] = calc_info.reshape_1[i];
200- shape_out[(RANK - i) - 1] = calc_info.shape_out[i];
201- bcast_0[(RANK - i) - 1] = calc_info.bcast_0[i];
202- bcast_1[(RANK - i) - 1] = calc_info.bcast_1[i];
203- }
204- if (input_shape_0 == input_shape_1) {
205- output.reshape(shape_out) =
206- input0.reshape(reshape_0) / input1.reshape(reshape_1);
207- }
208- else {
209- output.reshape(shape_out) =
210- input0.reshape(reshape_0).broadcast(bcast_0) / input1.reshape(reshape_1).broadcast(bcast_1);
211- }
212- return KERNEL_STATUS_OK;
213-}
214- 
215-uint32_t RealDivKernel::Compute(CpuKernelContext &ctx) {
216- KERNEL_HANDLE_ERROR(NormalCheck(ctx, INPUT_NUM2, 1), "Div check input output params failed.");
217- Tensor *input0 = ctx.Input(kFirstInputIndex);
218- Tensor *input1 = ctx.Input(kSecondInputIndex);
219- if ((input0->GetDataSize() == 0) || (input1->GetDataSize() == 0)) {
220- KERNEL_LOG_INFO("[%s] Input is empty tensor.", ctx.GetOpType().c_str());
221- return KERNEL_STATUS_OK;
222- }
223-
224- DataType input0_type = input0->GetDataType();
225- DataType input1_type = input1->GetDataType();
226- KERNEL_CHECK_FALSE((
227- input0_type == input1_type), KERNEL_STATUS_PARAM_INVALID, "input0 type[%s] is not equal to input1 type[%s]",
228- DTypeStr(input0_type).c_str(), DTypeStr(input1_type).c_str());
229- return RealDivSameTypeCompute(ctx, input0_type);
230}420}
231 421 
232REGISTER_CPU_KERNEL(kRealDiv, RealDivKernel);422REGISTER_CPU_KERNEL(kRealDiv, RealDivKernel);
233REGISTER_CPU_KERNEL(kDIV, RealDivKernel);423REGISTER_CPU_KERNEL(kDIV, RealDivKernel);
234-} // namespace aicpu424+} // namespace aicpu
Mmath/real_div/op_kernel_aicpu/real_div_aicpu.h+0-7
@@ -26,13 +26,6 @@ class RealDivKernel : public CpuKernel {
26 bool IsInputHasZero(T *input_data, const int64_t num_of_elems);26 bool IsInputHasZero(T *input_data, const int64_t num_of_elems);
27 template <typename T>27 template <typename T>
28 uint32_t RealDivCompute(const CpuKernelContext &ctx, const bool verify_zero = true);28 uint32_t RealDivCompute(const CpuKernelContext &ctx, const bool verify_zero = true);
29- template <int32_t RANK, typename T>
30- uint32_t RealDivCalculateWithAlignedCheck(BCalcInfo &calc_info);
31- bool AlignedCheck(const BCalcInfo &calc_info) const;
32- template <int32_t RANK, typename T, int32_t OPTION>
33- uint32_t RealDivCalculate(BCalcInfo &calc_info);
34- template <typename T>
35- uint32_t DispatchByRank(int32_t rank, BCalcInfo &calc_info, const CpuKernelContext &ctx);
36};29};
37} // namespace aicpu30} // namespace aicpu
38#endif31#endif
Mmath/real_div/tests/ut/op_kernel_aicpu/test_real_div.cpp+216-2
@@ -71,7 +71,7 @@ void CalcExpectFunc(const NodeDef &node_def, T expect_out[]) {
71 CompareResult<base_type>(output.data(), expect_out.data(), data_num[2]); \71 CompareResult<base_type>(output.data(), expect_out.data(), data_num[2]); \
72 }72 }
73 73 
74- #define ADD_ZERO_CASE(base_type, aicpu_type) \74+#define ADD_ZERO_CASE(base_type, aicpu_type) \
75 TEST_F(TEST_REALDIV_UT, TestRealDiv_ZeroInput_##aicpu_type) { \75 TEST_F(TEST_REALDIV_UT, TestRealDiv_ZeroInput_##aicpu_type) { \
76 vector<DataType> data_types = {aicpu_type, aicpu_type, aicpu_type}; \76 vector<DataType> data_types = {aicpu_type, aicpu_type, aicpu_type}; \
77 vector<vector<int64_t>> shapes = {{24}, {24}, {24}}; \77 vector<vector<int64_t>> shapes = {{24}, {24}, {24}}; \
@@ -177,4 +177,218 @@ ADD_ZERO_CASE(int64_t, DT_INT64)
177 177 
178ADD_ZERO_CASE(uint8_t, DT_UINT8)178ADD_ZERO_CASE(uint8_t, DT_UINT8)
179 179 
180-ADD_ZERO_CASE(uint16_t, DT_UINT16)180+ADD_ZERO_CASE(uint16_t, DT_UINT16)
181+ 
182+// ==================== coverage: large same-shape parallel path ====================
183+// data_num >= kParallelDataNumSameShape (7*1024=7168) triggers ParallelFor in NoBcastComputeImpl
184+TEST_F(TEST_REALDIV_UT, TestRealDiv_LargeSameShape_Parallel) {
185+ const int64_t num = 8192;
186+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
187+ vector<vector<int64_t>> shapes = {{num}, {num}, {num}};
188+ std::vector<float> input1(num, 2.0f);
189+ std::vector<float> input2(num, 2.0f);
190+ std::vector<float> output(num, 0.0f);
191+ vector<void *> datas = {input1.data(), input2.data(), output.data()};
192+ CREATE_NODEDEF(shapes, data_types, datas);
193+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
194+ std::vector<float> expect_out(num, 1.0f);
195+ CompareResult<float>(output.data(), expect_out.data(), num);
196+}
197+ 
198+// data_num in [kParallelDataNumSameShape, kParallelDataNumSameShapeMid] → max 4 cores
199+TEST_F(TEST_REALDIV_UT, TestRealDiv_LargeSameShape_MidThreshold) {
200+ const int64_t num = 16384;
201+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
202+ vector<vector<int64_t>> shapes = {{num}, {num}, {num}};
203+ std::vector<float> input1(num, 6.0f);
204+ std::vector<float> input2(num, 3.0f);
205+ std::vector<float> output(num, 0.0f);
206+ vector<void *> datas = {input1.data(), input2.data(), output.data()};
207+ CREATE_NODEDEF(shapes, data_types, datas);
208+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
209+ std::vector<float> expect_out(num, 2.0f);
210+ CompareResult<float>(output.data(), expect_out.data(), num);
211+}
212+ 
213+// ==================== coverage: X_ONE_ELEMENT path (scalar / vector) ====================
214+TEST_F(TEST_REALDIV_UT, TestRealDiv_ScalarDivVector) {
215+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
216+ vector<vector<int64_t>> shapes = {{1}, {4}, {4}};
217+ float input1[1] = {12.0f};
218+ float input2[4] = {1.0f, 2.0f, 3.0f, 4.0f};
219+ float output[4] = {0};
220+ vector<void *> datas = {(void *)input1, (void *)input2, (void *)output};
221+ CREATE_NODEDEF(shapes, data_types, datas);
222+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
223+ float expect_out[4] = {12.0f, 6.0f, 4.0f, 3.0f};
224+ CompareResult<float>(output, expect_out, 4);
225+}
226+ 
227+// ==================== coverage: Y_ONE_ELEMENT path (vector / scalar) ====================
228+TEST_F(TEST_REALDIV_UT, TestRealDiv_VectorDivScalar) {
229+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
230+ vector<vector<int64_t>> shapes = {{4}, {1}, {4}};
231+ float input1[4] = {4.0f, 8.0f, 12.0f, 16.0f};
232+ float input2[1] = {4.0f};
233+ float output[4] = {0};
234+ vector<void *> datas = {(void *)input1, (void *)input2, (void *)output};
235+ CREATE_NODEDEF(shapes, data_types, datas);
236+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
237+ float expect_out[4] = {1.0f, 2.0f, 3.0f, 4.0f};
238+ CompareResult<float>(output, expect_out, 4);
239+}
240+ 
241+// ==================== coverage: broadcast parallel path (>= kParallelDataNum=2048) ====================
242+// Broadcast with large output to trigger ParallelFor in BcastComputeImpl
243+TEST_F(TEST_REALDIV_UT, TestRealDiv_BcastLargeParallel) {
244+ const int64_t rows = 64;
245+ const int64_t cols = 64;
246+ const int64_t out_num = rows * cols;
247+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
248+ vector<vector<int64_t>> shapes = {{rows, cols}, {1, cols}, {rows, cols}};
249+ std::vector<float> input1(out_num, 10.0f);
250+ std::vector<float> input2(cols, 5.0f);
251+ std::vector<float> output(out_num, 0.0f);
252+ vector<void *> datas = {input1.data(), input2.data(), output.data()};
253+ CREATE_NODEDEF(shapes, data_types, datas);
254+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
255+ std::vector<float> expect_out(out_num, 2.0f);
256+ CompareResult<float>(output.data(), expect_out.data(), out_num);
257+}
258+ 
259+// ==================== coverage: broadcast y_inner==0 (Y broadcast on innermost dim) ====================
260+// x shape (3,4), y shape (3,1) → output (3,4), y broadcasts on innermost → y_inner=0
261+TEST_F(TEST_REALDIV_UT, TestRealDiv_BcastYInner0) {
262+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
263+ vector<vector<int64_t>> shapes = {{3, 4}, {3, 1}, {3, 4}};
264+ float input1[12] = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24};
265+ float input2[3] = {2, 2, 2};
266+ float output[12] = {0};
267+ vector<void *> datas = {(void *)input1, (void *)input2, (void *)output};
268+ CREATE_NODEDEF(shapes, data_types, datas);
269+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
270+ float expect_out[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
271+ CompareResult<float>(output, expect_out, 12);
272+}
273+ 
274+// ==================== coverage: broadcast x_inner==0 (X broadcast on innermost dim) ====================
275+// x shape (3,1), y shape (3,4) → output (3,4), x broadcasts on innermost → x_inner=0
276+TEST_F(TEST_REALDIV_UT, TestRealDiv_BcastXInner0) {
277+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
278+ vector<vector<int64_t>> shapes = {{3, 1}, {3, 4}, {3, 4}};
279+ float input1[3] = {12.0f, 24.0f, 36.0f};
280+ float input2[12] = {1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4};
281+ float output[12] = {0};
282+ vector<void *> datas = {(void *)input1, (void *)input2, (void *)output};
283+ CREATE_NODEDEF(shapes, data_types, datas);
284+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
285+ float expect_out[12] = {12, 6, 4, 3, 24, 12, 8, 6, 36, 18, 12, 9};
286+ CompareResult<float>(output, expect_out, 12);
287+}
288+ 
289+// ==================== coverage: broadcast both contiguous innermost (outer dim broadcast) ====================
290+// x (4,3), y (1,3) → output (4,3), x_inner=1, y_inner=1, broadcast only on dim 0
291+TEST_F(TEST_REALDIV_UT, TestRealDiv_BcastBothContiguous) {
292+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
293+ vector<vector<int64_t>> shapes = {{4, 3}, {1, 3}, {4, 3}};
294+ float input1[12] = {3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36};
295+ float input2[3] = {3, 3, 3};
296+ float output[12] = {0};
297+ vector<void *> datas = {(void *)input1, (void *)input2, (void *)output};
298+ CREATE_NODEDEF(shapes, data_types, datas);
299+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
300+ float expect_out[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
301+ CompareResult<float>(output, expect_out, 12);
302+}
303+ 
304+// ==================== coverage: "Div" op type registration ====================
305+TEST_F(TEST_REALDIV_UT, TestDiv_OpType) {
306+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
307+ vector<vector<int64_t>> shapes = {{4}, {4}, {4}};
308+ float input1[4] = {10.0f, 20.0f, 30.0f, 40.0f};
309+ float input2[4] = {2.0f, 4.0f, 5.0f, 8.0f};
310+ float output[4] = {0};
311+ vector<void *> datas = {(void *)input1, (void *)input2, (void *)output};
312+ auto node_def = CpuKernelUtils::CpuKernelUtils::CreateNodeDef();
313+ NodeDefBuilder(node_def.get(), "Div", "Div")
314+ .Input({"x1", data_types[0], shapes[0], datas[0]})
315+ .Input({"x2", data_types[1], shapes[1], datas[1]})
316+ .Output({"y", data_types[2], shapes[2], datas[2]});
317+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
318+ float expect_out[4] = {5.0f, 5.0f, 6.0f, 5.0f};
319+ CompareResult<float>(output, expect_out, 4);
320+}
321+ 
322+// ==================== coverage: type mismatch error ====================
323+TEST_F(TEST_REALDIV_UT, TestRealDiv_TypeMismatch) {
324+ vector<vector<int64_t>> shapes = {{4}, {4}, {4}};
325+ float input1[4] = {1.0f, 2.0f, 3.0f, 4.0f};
326+ int32_t input2[4] = {1, 2, 3, 4};
327+ float output[4] = {0};
328+ vector<void *> datas = {(void *)input1, (void *)input2, (void *)output};
329+ auto node_def = CpuKernelUtils::CpuKernelUtils::CreateNodeDef();
330+ NodeDefBuilder(node_def.get(), "RealDiv", "RealDiv")
331+ .Input({"x1", DT_FLOAT, shapes[0], datas[0]})
332+ .Input({"x2", DT_INT32, shapes[1], datas[1]})
333+ .Output({"y", DT_FLOAT, shapes[2], datas[2]});
334+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_PARAM_INVALID);
335+}
336+ 
337+// ==================== coverage: broadcast with incompatible shapes ====================
338+TEST_F(TEST_REALDIV_UT, TestRealDiv_IncompatibleBroadcast) {
339+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
340+ vector<vector<int64_t>> shapes = {{3}, {5}, {5}};
341+ float input1[3] = {1, 2, 3};
342+ float input2[5] = {1, 2, 3, 4, 5};
343+ float output[5] = {0};
344+ vector<void *> datas = {(void *)input1, (void *)input2, (void *)output};
345+ CREATE_NODEDEF(shapes, data_types, datas);
346+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_PARAM_INVALID);
347+}
348+ 
349+// ==================== coverage: broadcast mid threshold (kParallelDataNumMid=16*1024) ====================
350+TEST_F(TEST_REALDIV_UT, TestRealDiv_BcastMidThreshold) {
351+ const int64_t rows = 128;
352+ const int64_t cols = 128;
353+ const int64_t out_num = rows * cols;
354+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
355+ vector<vector<int64_t>> shapes = {{rows, cols}, {1, cols}, {rows, cols}};
356+ std::vector<float> input1(out_num, 8.0f);
357+ std::vector<float> input2(cols, 4.0f);
358+ std::vector<float> output(out_num, 0.0f);
359+ vector<void *> datas = {input1.data(), input2.data(), output.data()};
360+ CREATE_NODEDEF(shapes, data_types, datas);
361+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
362+ std::vector<float> expect_out(out_num, 2.0f);
363+ CompareResult<float>(output.data(), expect_out.data(), out_num);
364+}
365+ 
366+// ==================== coverage: NoBcast large X_ONE_ELEMENT parallel ====================
367+TEST_F(TEST_REALDIV_UT, TestRealDiv_ScalarDivVector_LargeParallel) {
368+ const int64_t num = 8192;
369+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
370+ vector<vector<int64_t>> shapes = {{1}, {num}, {num}};
371+ float input1[1] = {100.0f};
372+ std::vector<float> input2(num, 10.0f);
373+ std::vector<float> output(num, 0.0f);
374+ vector<void *> datas = {(void *)input1, input2.data(), output.data()};
375+ CREATE_NODEDEF(shapes, data_types, datas);
376+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
377+ std::vector<float> expect_out(num, 10.0f);
378+ CompareResult<float>(output.data(), expect_out.data(), num);
379+}
380+ 
381+// ==================== coverage: NoBcast large Y_ONE_ELEMENT parallel ====================
382+TEST_F(TEST_REALDIV_UT, TestRealDiv_VectorDivScalar_LargeParallel) {
383+ const int64_t num = 8192;
384+ vector<DataType> data_types = {DT_FLOAT, DT_FLOAT, DT_FLOAT};
385+ vector<vector<int64_t>> shapes = {{num}, {1}, {num}};
386+ std::vector<float> input1(num, 50.0f);
387+ float input2[1] = {5.0f};
388+ std::vector<float> output(num, 0.0f);
389+ vector<void *> datas = {input1.data(), (void *)input2, output.data()};
390+ CREATE_NODEDEF(shapes, data_types, datas);
391+ RUN_KERNEL(node_def, HOST, KERNEL_STATUS_OK);
392+ std::vector<float> expect_out(num, 10.0f);
393+ CompareResult<float>(output.data(), expect_out.data(), num);
394+}