已合并
optimize the performance of aicpu realdiv operator #2208
ZhaiPeiChao创建于 4月13日
optimize the performance of aicpu realdiv operator #2208
已合并
共 3 个文件变更+598-201
| @@ -10,11 +10,11 @@ | |||
| 10 | 10 | ||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | -#include <cstdint> | 13 | +#include <stdint.h> |
| 14 | + | ||
| 14 | 15 | ||
| 15 | 16 | ||
| 16 | 17 | ||
| 17 | - | ||
| 18 | 18 | ||
| 19 | 19 | ||
| 20 | 20 | ||
| @@ -22,213 +22,403 @@ | |||
| 22 | 22 | ||
| 23 | 23 | ||
| 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 | - | ||
| 39 | namespace aicpu { | 25 | namespace 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 | ||
| 117 | template <typename T> | 362 | template <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 | ||
| 232 | REGISTER_CPU_KERNEL(kRealDiv, RealDivKernel); | 422 | REGISTER_CPU_KERNEL(kRealDiv, RealDivKernel); |
| 233 | REGISTER_CPU_KERNEL(kDIV, RealDivKernel); | 423 | REGISTER_CPU_KERNEL(kDIV, RealDivKernel); |
| 234 | -} // namespace aicpu | 424 | +} // namespace aicpu |
| @@ -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 aicpu | 30 | } // namespace aicpu |
| 38 | 31 | ||
| @@ -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 | ||
| 178 | ADD_ZERO_CASE(uint8_t, DT_UINT8) | 178 | ADD_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 | +} | ||