|
7 | 7 | #pragma once |
8 | 8 |
|
9 | 9 | #include "../mlasi.h" |
10 | | -#include <iostream> |
11 | 10 |
|
12 | 11 | // Fix to ensure compatibility with MSVC build |
13 | 12 | #if defined(_MSC_VER) |
|
25 | 24 | #endif |
26 | 25 |
|
27 | 26 | #if KLEIDIAI_DEBUG_LOGGING ||KLEIDIAI_KERNEL_LOGGING |
| 27 | +#include <iostream> |
28 | 28 | #define KLEIDIAI_LOG(tag, msg) \ |
29 | 29 | do { \ |
30 | 30 | std::cout << "[KLEIDIAI " << tag << "]: " << __FILE__ << " : " << __LINE__ << " : " << msg << std::endl; \ |
@@ -56,6 +56,143 @@ inline const bool UseSME2 = MLAS_CPUIDINFO::GetCPUIDInfo().HasArm_SME2(); |
56 | 56 | inline const bool UseSME = MLAS_CPUIDINFO::GetCPUIDInfo().HasArm_SME(); |
57 | 57 | inline const std::string_view vendor_name = MLAS_CPUIDINFO::GetCPUIDInfo().GetCPUVendor(); |
58 | 58 |
|
| 59 | +// Selects the convolution route for Arm® KleidiAI™ |
| 60 | +enum class ConvRoute { |
| 61 | + NoKleidiAi, // decline the conv, caller runs unchanged |
| 62 | + IGemm, // handle the whole conv via SME IGEMM kernel |
| 63 | + SGemmFallback, // decline IGEMM, but still route the per-segment SGEMM slices through MlasGemm |
| 64 | + // so that the Arm® KleidiAI™ SGEMM backend override can pick them up |
| 65 | +}; |
| 66 | + |
| 67 | +struct ConvRouteSelection { |
| 68 | + ConvRoute route = ConvRoute::NoKleidiAi; |
| 69 | + size_t effective_kernel_h = 0; |
| 70 | + size_t effective_kernel_w = 0; |
| 71 | +}; |
| 72 | + |
| 73 | +// Heuristic default for SME IGEMM selection. Work is estimated as |
| 74 | +// output_m * effective_k * filter_count; larger workloads use the SGEMM-backed |
| 75 | +// fallback to avoid IGEMM packing overhead on large effective-k convolutions. |
| 76 | +inline constexpr size_t ConvIgemmMaxWorkDefault = 1'000'000ULL; |
| 77 | + |
| 78 | +inline bool TryComputeDilatedKernelSize(size_t dilation, size_t kernel, size_t* result) { |
| 79 | + if (dilation == 0 || kernel == 0) return false; |
| 80 | + |
| 81 | + // using formula: dilated_kernel_size = dilation * (kernel - 1) + 1 |
| 82 | + size_t scaled_kernel; |
| 83 | + if (MlasMultiplyOverflowsSizeT(kernel - 1, dilation, &scaled_kernel)) return false; |
| 84 | + |
| 85 | + if (scaled_kernel == SIZE_MAX) return false; |
| 86 | + |
| 87 | + *result = scaled_kernel + 1; |
| 88 | + return true; |
| 89 | +} |
| 90 | + |
| 91 | +inline bool TryComputeConvOutputSize(size_t input, size_t kernel, size_t padding, size_t stride, size_t* result) { |
| 92 | + |
| 93 | + if (stride == 0) return false; |
| 94 | + |
| 95 | + size_t double_padding; |
| 96 | + if (MlasMultiplyOverflowsSizeT(padding, 2, &double_padding)) return false; |
| 97 | + |
| 98 | + if (double_padding > (SIZE_MAX - input)) return false; |
| 99 | + const size_t padded_input = double_padding + input; |
| 100 | + |
| 101 | + if (padded_input < kernel) return false; |
| 102 | + |
| 103 | + // using formula: output_size = ((2*padding + input - kernel) / stride) + 1 |
| 104 | + const size_t output_minus_one = (padded_input - kernel) / stride; |
| 105 | + if (output_minus_one == SIZE_MAX) return false; |
| 106 | + *result = output_minus_one + 1; |
| 107 | + return true; |
| 108 | +} |
| 109 | + |
| 110 | +inline ConvRouteSelection SelectConvRoute(const MLAS_CONV_PARAMETERS* Parameters) { |
| 111 | + if ((Parameters->Dimensions != 2) || |
| 112 | + (Parameters->BatchCount != 1) || |
| 113 | + (Parameters->Beta != 0.f) || |
| 114 | + (Parameters->Padding[0] != Parameters->Padding[1]) || |
| 115 | + (Parameters->Padding[0] != Parameters->Padding[2]) || |
| 116 | + (Parameters->Padding[0] != Parameters->Padding[3])) { |
| 117 | + return ConvRouteSelection{}; |
| 118 | + } |
| 119 | + |
| 120 | + size_t effective_kernel_h; |
| 121 | + size_t effective_kernel_w; |
| 122 | + if (!TryComputeDilatedKernelSize(Parameters->DilationShape[0], Parameters->KernelShape[0], &effective_kernel_h) || |
| 123 | + !TryComputeDilatedKernelSize(Parameters->DilationShape[1], Parameters->KernelShape[1], &effective_kernel_w)) { |
| 124 | + return ConvRouteSelection{}; |
| 125 | + } |
| 126 | + |
| 127 | + size_t output_m; |
| 128 | + size_t output_h_size; |
| 129 | + size_t output_w_size; |
| 130 | + if (!TryComputeConvOutputSize(Parameters->InputShape[0], |
| 131 | + effective_kernel_h, |
| 132 | + Parameters->Padding[0], |
| 133 | + Parameters->StrideShape[0], |
| 134 | + &output_h_size) || |
| 135 | + !TryComputeConvOutputSize(Parameters->InputShape[1], |
| 136 | + effective_kernel_w, |
| 137 | + Parameters->Padding[1], |
| 138 | + Parameters->StrideShape[1], |
| 139 | + &output_w_size) || |
| 140 | + MlasMultiplyOverflowsSizeT(output_h_size, output_w_size, &output_m)) { |
| 141 | + return ConvRouteSelection{}; |
| 142 | + } |
| 143 | + |
| 144 | + if (output_m == 0) { |
| 145 | + return ConvRouteSelection{}; |
| 146 | + } |
| 147 | + |
| 148 | + if (Parameters->KernelShape[0] < 3 || Parameters->KernelShape[1] < 3) { |
| 149 | + return ConvRouteSelection{}; |
| 150 | + } |
| 151 | + |
| 152 | + const auto filter_count = Parameters->FilterCount; |
| 153 | + if (filter_count == 0) { |
| 154 | + return ConvRouteSelection{}; |
| 155 | + } |
| 156 | + |
| 157 | + size_t effective_k; |
| 158 | + if (MlasMultiplyOverflowsSizeT(Parameters->InputChannels, effective_kernel_h, &effective_k) || |
| 159 | + MlasMultiplyOverflowsSizeT(effective_k, effective_kernel_w, &effective_k)) { |
| 160 | + return ConvRouteSelection{}; |
| 161 | + } |
| 162 | + if (effective_k == 0) { |
| 163 | + return ConvRouteSelection{}; |
| 164 | + } |
| 165 | + |
| 166 | + // Currently, the fallback routes assume NCHW layout, so keep valid NHWC convolutions on IGEMM |
| 167 | + if (Parameters->ChannelsLast) { |
| 168 | + return ConvRouteSelection{ConvRoute::IGemm, effective_kernel_h, effective_kernel_w}; |
| 169 | + } |
| 170 | + |
| 171 | + if (filter_count == 1) { |
| 172 | + // Single-output-channel convolutions do not fill the SME IGEMM N tile efficiently, |
| 173 | + // so defer to the default convolution route. |
| 174 | + return ConvRouteSelection{}; |
| 175 | + } |
| 176 | + |
| 177 | + size_t total_work; |
| 178 | + if (MlasMultiplyOverflowsSizeT(output_m, effective_k, &total_work) || |
| 179 | + MlasMultiplyOverflowsSizeT(total_work, filter_count, &total_work)) { |
| 180 | + // Total work calculation overflow means total work is too large, fall back |
| 181 | + return ConvRouteSelection{ConvRoute::SGemmFallback, effective_kernel_h, effective_kernel_w}; |
| 182 | + } |
| 183 | + |
| 184 | + const size_t conv_igemm_max_work = |
| 185 | + Parameters->BackendKernelSelectorConfig != nullptr && |
| 186 | + Parameters->BackendKernelSelectorConfig->kleidiai_conv_igemm_max_work != 0 |
| 187 | + ? Parameters->BackendKernelSelectorConfig->kleidiai_conv_igemm_max_work |
| 188 | + : ConvIgemmMaxWorkDefault; |
| 189 | + if (total_work > conv_igemm_max_work) { |
| 190 | + return ConvRouteSelection{ConvRoute::SGemmFallback, effective_kernel_h, effective_kernel_w}; |
| 191 | + } |
| 192 | + |
| 193 | + return ConvRouteSelection{ConvRoute::IGemm, effective_kernel_h, effective_kernel_w}; |
| 194 | +} |
| 195 | + |
59 | 196 | // Buffer packing routines. |
60 | 197 | // |
61 | 198 | size_t |
@@ -224,38 +361,18 @@ MlasConvSymmetricChannelsLast2DFloatPackW( |
224 | 361 | size_t PackedFilterGroupStride, |
225 | 362 | MLAS_THREADPOOL* ThreadPool |
226 | 363 | ); |
227 | | -} |
228 | | - |
229 | | -/*++ |
230 | | -
|
231 | | -Routine Description: |
232 | | -
|
233 | | - This routine determines if a wraparound will occur when multiplying two size_t variables |
234 | | - Uses __builtin_mul_overflow if available on the current system and if not falls back |
235 | | - to a default implementation to check this wraparound. |
236 | | -
|
237 | | -Arguments: |
238 | | -
|
239 | | - a - Supplies the first number to be muliplied. |
240 | 364 |
|
241 | | - b - Supplies the second number to be muliplied. |
242 | | -
|
243 | | - out - pointer to a size_t which acts as the return value in success cases. |
244 | | -
|
245 | | -Return Value: |
246 | | -
|
247 | | - Returns false if the operation was successful |
248 | | - Returns true if wraparound of size_t was detected |
249 | | -
|
250 | | ---*/ |
251 | | -inline bool mul_overflow_size_t_builtin(size_t a, size_t b, size_t* out) { |
252 | | -#if defined(__has_builtin) |
253 | | -# if __has_builtin(__builtin_mul_overflow) |
254 | | - return __builtin_mul_overflow(a, b, out); |
255 | | -# endif |
256 | | -#endif |
257 | | - // Fallback to manual check if builtin not available |
258 | | - if (b != 0 && a > SIZE_MAX / b) return true; |
259 | | - if (out) *out = a * b; |
260 | | - return false; |
| 365 | +inline MLAS_CONV_SGEMM_ROUTE |
| 366 | +MLASCALL |
| 367 | +MlasConvSGemmRoute(const MLAS_CONV_PARAMETERS* Parameters) { |
| 368 | + if (Parameters->BackendKernelSelectorConfig && |
| 369 | + !Parameters->BackendKernelSelectorConfig->use_kleidiai) { |
| 370 | + return MlasConvSGemmRouteDirect; |
| 371 | + } |
| 372 | + |
| 373 | + return ArmKleidiAI::SelectConvRoute(Parameters).route == ArmKleidiAI::ConvRoute::SGemmFallback |
| 374 | + ? MlasConvSGemmRouteDispatch |
| 375 | + : MlasConvSGemmRouteDirect; |
261 | 376 | } |
| 377 | +} |
| 378 | + |
0 commit comments