xref: /aosp_15_r20/external/XNNPACK/src/operators/convolution-nchw.c (revision 4bdc94577ba0e567308109d787f7fec7b531ce36)
1 // Copyright 2019 Google LLC
2 //
3 // This source code is licensed under the BSD-style license found in the
4 // LICENSE file in the root directory of this source tree.
5 
6 #include <assert.h>
7 #include <math.h>
8 #include <stdbool.h>
9 #include <stddef.h>
10 #include <stdint.h>
11 #include <stdlib.h>
12 #include <string.h>
13 
14 #include <xnnpack.h>
15 #include <xnnpack/allocator.h>
16 #include <xnnpack/common.h>
17 #include <xnnpack/compute.h>
18 #include <xnnpack/indirection.h>
19 #include <xnnpack/log.h>
20 #include <xnnpack/math.h>
21 #include <xnnpack/operator.h>
22 #include <xnnpack/pack.h>
23 #include <xnnpack/microparams-init.h>
24 #include <xnnpack/params.h>
25 
26 
xnn_create_convolution2d_nchw_f32(uint32_t input_padding_top,uint32_t input_padding_right,uint32_t input_padding_bottom,uint32_t input_padding_left,uint32_t kernel_height,uint32_t kernel_width,uint32_t subsampling_height,uint32_t subsampling_width,uint32_t dilation_height,uint32_t dilation_width,uint32_t groups,size_t group_input_channels,size_t group_output_channels,size_t input_channel_stride,size_t output_channel_stride,const float * kernel,const float * bias,float output_min,float output_max,uint32_t flags,xnn_caches_t caches,xnn_operator_t * convolution_op_out)27 enum xnn_status xnn_create_convolution2d_nchw_f32(
28     uint32_t input_padding_top,
29     uint32_t input_padding_right,
30     uint32_t input_padding_bottom,
31     uint32_t input_padding_left,
32     uint32_t kernel_height,
33     uint32_t kernel_width,
34     uint32_t subsampling_height,
35     uint32_t subsampling_width,
36     uint32_t dilation_height,
37     uint32_t dilation_width,
38     uint32_t groups,
39     size_t group_input_channels,
40     size_t group_output_channels,
41     size_t input_channel_stride,
42     size_t output_channel_stride,
43     const float* kernel,
44     const float* bias,
45     float output_min,
46     float output_max,
47     uint32_t flags,
48     xnn_caches_t caches,
49     xnn_operator_t* convolution_op_out)
50 {
51   xnn_operator_t convolution_op = NULL;
52   enum xnn_status status = xnn_status_uninitialized;
53 
54   if ((xnn_params.init_flags & XNN_INIT_FLAG_XNNPACK) == 0) {
55     xnn_log_error("failed to create %s operator: XNNPACK is not initialized",
56       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
57     goto error;
58   }
59 
60   status = xnn_status_invalid_parameter;
61 
62   if (kernel_width == 0 || kernel_height == 0) {
63     xnn_log_error(
64       "failed to create %s operator with %" PRIu32 "x%" PRIu32 " kernel: kernel dimensions must be non-zero",
65       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32), kernel_width, kernel_height);
66     goto error;
67   }
68 
69   if (subsampling_width == 0 || subsampling_height == 0) {
70     xnn_log_error(
71       "failed to create %s operator with %" PRIu32 "x%" PRIu32 " subsampling: subsampling dimensions must be non-zero",
72       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32), subsampling_width, subsampling_height);
73     goto error;
74   }
75 
76   if (dilation_width == 0 || dilation_height == 0) {
77     xnn_log_error(
78       "failed to create %s operator with %" PRIu32 "x%" PRIu32 " dilation: dilation dimensions must be non-zero",
79       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32), dilation_width, dilation_height);
80     goto error;
81   }
82 
83   if (groups == 0) {
84     xnn_log_error(
85       "failed to create %s operator with %" PRIu32 " groups: number of groups must be non-zero",
86       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32), groups);
87     goto error;
88   }
89 
90   if (group_input_channels == 0) {
91     xnn_log_error(
92       "failed to create %s operator with %zu input channels per group: number of channels must be non-zero",
93       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32), group_input_channels);
94     goto error;
95   }
96 
97   if (group_output_channels == 0) {
98     xnn_log_error(
99       "failed to create %s operator with %zu output channels per group: number of channels must be non-zero",
100       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32), group_output_channels);
101     goto error;
102   }
103 
104   const size_t input_channels = groups * group_input_channels;
105   if (input_channel_stride < input_channels) {
106     xnn_log_error(
107       "failed to create %s operator with input channel stride of %zu: "
108       "stride must be at least as large as the number of input channels (%" PRIu32 "x%zu)",
109       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32),
110       input_channel_stride, groups, group_input_channels);
111     goto error;
112   }
113 
114   const size_t output_channels = groups * group_output_channels;
115   if (output_channel_stride < output_channels) {
116     xnn_log_error(
117       "failed to create %s operator with output channel stride of %zu: "
118       "stride must be at least as large as the number of output channels (%" PRIu32 "x%zu)",
119       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32),
120       output_channel_stride, groups, group_output_channels);
121     goto error;
122   }
123 
124   if (isnan(output_min)) {
125     xnn_log_error(
126       "failed to create %s operator with NaN output lower bound: lower bound must be non-NaN",
127       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
128     goto error;
129   }
130 
131   if (isnan(output_max)) {
132     xnn_log_error(
133       "failed to create %s operator with NaN output upper bound: upper bound must be non-NaN",
134       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
135     goto error;
136   }
137 
138   if (output_min >= output_max) {
139     xnn_log_error(
140       "failed to create %s operator with [%.7g, %.7g] output range: lower bound must be below upper bound",
141       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32), output_min, output_max);
142     goto error;
143   }
144 
145   if ((flags & XNN_FLAG_DEPTHWISE_CONVOLUTION) != 0 && group_input_channels != 1) {
146     xnn_log_error(
147       "failed to create depthwise %s operator with %zu input channels per group: "
148       "depthwise convolution must have exactly 1 input channel per group",
149       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32), group_input_channels);
150     goto error;
151   }
152 
153   status = xnn_status_unsupported_parameter;
154 
155   enum xnn_ukernel_type ukernel_type;
156   struct dwconv2d_chw_parameters* dwconv2d_parameters = NULL;
157   // Supported cases:
158   // + 1x1 convolution (no groups)
159   // + 3x3 stride-2 with 3 input channels and NHWC input layout
160   // + 3x3 stride-2 depthwise convolution with horizontal padding 1 & no vertical padding
161   // + 3x3 stride-1 depthwise convolution with horizontal padding 1 & no vertical padding
162   // + 5x5 stride-2 depthwise convolution with horizontal padding 2 & no vertical padding
163   // + 5x5 stride-1 depthwise convolution with horizontal padding 2 & no vertical padding
164   const bool any_padding = (input_padding_left | input_padding_top | input_padding_right | input_padding_bottom) != 0;
165   const bool is_1x1 = kernel_width == 1 && kernel_height == 1 && subsampling_height == 1 && subsampling_width == 1;
166   const bool is_3x3 = kernel_width == 3 && kernel_height == 3 && dilation_height == 1 && dilation_width == 1;
167   const bool is_5x5 = kernel_width == 5 && kernel_height == 5 && dilation_height == 1 && dilation_width == 1;
168   const bool nhwc_input = (flags & XNN_FLAG_INPUT_NHWC) != 0;
169   if (is_1x1 && !any_padding && !nhwc_input && groups == 1) {
170     ukernel_type = xnn_ukernel_type_spmm;
171   } else if (is_3x3 && subsampling_height == 2 && subsampling_width == 2 &&
172     input_padding_top == 1 && input_padding_left == 1 && input_padding_bottom == 1 && input_padding_right == 1 &&
173     nhwc_input && groups == 1)
174   {
175     ukernel_type = xnn_ukernel_type_conv2d_hwc2chw;
176   } else if (is_3x3 && subsampling_height == 1 && subsampling_width == 1 &&
177     input_padding_top == 1 && input_padding_left == 1 && input_padding_bottom == 1 && input_padding_right == 1 &&
178     !nhwc_input && group_input_channels == 1 && group_output_channels == 1)
179   {
180     ukernel_type = xnn_ukernel_type_dwconv;
181     dwconv2d_parameters = &xnn_params.f32.dwconv2d_chw_3x3;
182   } else if (is_3x3 && subsampling_height == 2 && subsampling_width == 2 &&
183     (input_padding_top == 0 || input_padding_top == 1) && input_padding_left == 1 && input_padding_bottom == 1 && input_padding_right == 1 &&
184     !nhwc_input && group_input_channels == 1 && group_output_channels == 1)
185   {
186     ukernel_type = xnn_ukernel_type_dwconv;
187     dwconv2d_parameters = &xnn_params.f32.dwconv2d_chw_3x3s2;
188   } else if (is_5x5 && subsampling_height == 1 && subsampling_width == 1 &&
189     input_padding_top == 2 && input_padding_left == 2 && input_padding_bottom == 2 && input_padding_right == 2 &&
190     !nhwc_input && group_input_channels == 1 && group_output_channels == 1)
191   {
192     ukernel_type = xnn_ukernel_type_dwconv;
193     dwconv2d_parameters = &xnn_params.f32.dwconv2d_chw_5x5;
194   } else if (is_5x5 && subsampling_height == 2 && subsampling_width == 2 &&
195     (input_padding_top == 1 || input_padding_top == 2) && input_padding_left == 2 && input_padding_bottom == 2 && input_padding_right == 2 &&
196     !nhwc_input && group_input_channels == 1 && group_output_channels == 1)
197   {
198     ukernel_type = xnn_ukernel_type_dwconv;
199     dwconv2d_parameters = &xnn_params.f32.dwconv2d_chw_5x5s2;
200   } else {
201     xnn_log_error(
202       "failed to create %s operator with %" PRIu32 "x%" PRIu32 " kernel, %"PRIu32 "x%" PRIu32 " subsampling, %"PRIu32 "x%" PRIu32 " dilation"
203       ", %" PRIu32 "+%" PRIu32 "x%" PRIu32 "+%" PRIu32" padding, %" PRIu32 "x%zu input channels, and %" PRIu32 "x%zu output channels: "
204       "only selected convolution parameters are supported",
205       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32),
206       kernel_width, kernel_height, subsampling_width, subsampling_height, dilation_width, dilation_height,
207       input_padding_top, input_padding_left, input_padding_bottom, input_padding_right,
208       groups, group_input_channels, groups, group_output_channels);
209     goto error;
210   }
211 
212   status = xnn_status_out_of_memory;
213 
214   convolution_op = xnn_allocate_zero_simd_memory(sizeof(struct xnn_operator));
215   if (convolution_op == NULL) {
216     xnn_log_error(
217       "failed to allocate %zu bytes for %s operator descriptor",
218       sizeof(struct xnn_operator), xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
219     goto error;
220   }
221 
222   if (caches != NULL && ukernel_type != xnn_ukernel_type_spmm) {
223     convolution_op->weights_cache = caches->weights_cache;
224   }
225 
226   switch (ukernel_type) {
227     case xnn_ukernel_type_spmm:
228     {
229       assert(kernel_height == 1);
230       assert(kernel_width == 1);
231       assert(groups == 1);
232 
233       size_t num_nonzeroes = 0;
234       size_t num_nonzero_blocks2 = 0;
235       size_t num_nonzero_blocks4 = 0;
236       for (size_t oc = 0; oc < round_down_po2(group_output_channels, 4); oc += 4) {
237         for (size_t ic = 0; ic < group_input_channels; ic++) {
238           const size_t row0_nonzero = (size_t) (kernel[oc * group_input_channels + ic] != 0.0f);
239           const size_t row1_nonzero = (size_t) (kernel[(oc + 1) * group_input_channels + ic] != 0.0f);
240           const size_t row2_nonzero = (size_t) (kernel[(oc + 2) * group_input_channels + ic] != 0.0f);
241           const size_t row3_nonzero = (size_t) (kernel[(oc + 3) * group_input_channels + ic] != 0.0f);
242           num_nonzeroes += row0_nonzero + row1_nonzero + row2_nonzero + row3_nonzero;
243           num_nonzero_blocks2 += (row0_nonzero | row1_nonzero) + (row2_nonzero | row3_nonzero);
244           num_nonzero_blocks4 += (row0_nonzero | row1_nonzero | row2_nonzero | row3_nonzero);
245         }
246       }
247       const size_t num_block4_nonzeroes = num_nonzeroes;
248       for (size_t oc = round_down_po2(group_output_channels, 4); oc < round_down_po2(group_output_channels, 2); oc += 2) {
249         for (size_t ic = 0; ic < group_input_channels; ic++) {
250           const size_t row0_nonzero = (size_t) (kernel[oc * group_input_channels + ic] != 0.0f);
251           const size_t row1_nonzero = (size_t) (kernel[(oc + 1) * group_input_channels + ic] != 0.0f);
252           num_nonzeroes += row0_nonzero + row1_nonzero;
253           num_nonzero_blocks2 += (row0_nonzero | row1_nonzero);
254         }
255       }
256       const size_t num_block2_nonzeroes = num_nonzeroes;
257       for (size_t oc = round_down_po2(group_output_channels, 2); oc < group_output_channels; oc++) {
258         for (size_t ic = 0; ic < group_input_channels; ic++) {
259           num_nonzeroes += (size_t) (kernel[oc * group_input_channels + ic] != 0.0f);
260         }
261       }
262       size_t output_channels_block_size = 1;
263       size_t num_output_channel_blocks = group_output_channels;
264       size_t num_nonzero_values = num_nonzeroes;
265       size_t num_nonzero_blocks = num_nonzeroes;
266       const struct spmm_parameters* spmm_parameters = &xnn_params.f32.spmm;
267       if (num_block4_nonzeroes * 5 >= num_nonzero_blocks4 * 18 && xnn_params.f32.spmm4.ukernel != NULL) {
268         // 4-channel blocks have 90%+ non-zeroes
269 
270         output_channels_block_size = 4;
271         num_output_channel_blocks = num_output_channel_blocks / 4 + num_output_channel_blocks % 4;
272         spmm_parameters = &xnn_params.f32.spmm4;
273         // Non-zeroes which don't fit into whole 4-channel blocks, processed one-by-one
274         const size_t num_remaining_nonzeroes = num_nonzeroes - num_block4_nonzeroes;
275         num_nonzero_values = num_nonzero_blocks4 * 4 + num_remaining_nonzeroes;
276         num_nonzero_blocks = num_nonzero_blocks4 + num_remaining_nonzeroes;
277       } else if (num_block2_nonzeroes * 5 >= num_nonzero_blocks2 * 9 && xnn_params.f32.spmm2.ukernel != NULL) {
278         // 2-channel blocks have 90%+ non-zeroes
279 
280         output_channels_block_size = 2;
281         num_output_channel_blocks = num_output_channel_blocks / 2 + num_output_channel_blocks % 2;
282         spmm_parameters = &xnn_params.f32.spmm2;
283         // Non-zeroes which don't fit into whole 2-channel blocks, processed one-by-one
284         const size_t num_remaining_nonzeroes = num_nonzeroes - num_block2_nonzeroes;
285         num_nonzero_values = num_nonzero_blocks2 * 2 + num_remaining_nonzeroes;
286         num_nonzero_blocks = num_nonzero_blocks2 + num_remaining_nonzeroes;
287       }
288 
289       // Sparse representation of weights consists of four components:
290       // 1. An array of float values storing non-zero kernel elements, and all (group_output_channels) bias elements.
291       //    All elements within non-zero block are assumed to be non-zero.
292       // 2. An array of int32_t values storing increment for input pointer after each processed tile. This array is
293       //    derived from scaled difference in array 2 using parameters to setup function.
294       // 3. An array of uint32_t values storing the number of non-zero kernel elements per each output channel.
295       // 4. An array of int32_t values storing scaled [by sizeof(input element)] difference between input channels
296       //    corresponding to successive non-zero blocks.
297       const size_t packed_weights_size = num_output_channel_blocks * sizeof(uint32_t) +
298         (num_nonzero_blocks * 2) * sizeof(int32_t) + (num_nonzero_values + group_output_channels) * sizeof(float);
299 
300       convolution_op->packed_weights.pointer = xnn_allocate_simd_memory(packed_weights_size);
301       if (convolution_op->packed_weights.pointer == NULL) {
302         xnn_log_error(
303           "failed to allocate %zu bytes for %s operator packed weights",
304           packed_weights_size, xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
305         goto error;
306       }
307       convolution_op->num_nonzero_values = num_nonzero_values;
308       convolution_op->num_nonzero_blocks = num_nonzero_blocks;
309       convolution_op->num_output_channel_blocks = num_output_channel_blocks;
310 
311       float* nonzero_values = convolution_op->packed_weights.pointer;
312       int32_t* input_increments = (int32_t*) (nonzero_values + num_nonzero_values + group_output_channels);
313       uint32_t* output_channel_nonzeros = (uint32_t*) (input_increments + num_nonzero_blocks);
314       int32_t* input_channel_diffs = (int32_t*) (output_channel_nonzeros + num_output_channel_blocks);
315       memset(output_channel_nonzeros, 0, num_output_channel_blocks * sizeof(uint32_t));
316 
317       status = xnn_status_unsupported_parameter;
318 
319       size_t first_ic = 0, last_ic = 0;
320       bool first_nonzero = true;
321       for (size_t ocb = 0; ocb < round_down_po2(group_output_channels, output_channels_block_size); ocb += output_channels_block_size) {
322         if XNN_LIKELY(bias != NULL) {
323           for (size_t oco = 0; oco < output_channels_block_size; oco++) {
324             *nonzero_values++ = bias[ocb + oco];
325           }
326         } else {
327           for (size_t oco = 0; oco < output_channels_block_size; oco++) {
328             *nonzero_values++ = 0.0f;
329           }
330         }
331         for (size_t ic = 0; ic < group_input_channels; ic++) {
332           bool is_nonzero_block = false;
333           for (size_t oco = 0; oco < output_channels_block_size; oco++) {
334             is_nonzero_block |= (kernel[(ocb + oco) * group_input_channels + ic] != 0.0f);
335           }
336           if (is_nonzero_block) {
337             for (size_t oco = 0; oco < output_channels_block_size; oco++) {
338               *nonzero_values++ = kernel[(ocb + oco) * group_input_channels + ic];
339             }
340             if (first_nonzero) {
341               first_ic = ic;
342             } else {
343               const int64_t diff = (int64_t) ((uint64_t) ic - (uint64_t) last_ic) * (int64_t) sizeof(float);
344               if (diff != (int64_t) (int32_t) diff) {
345                 xnn_log_error("failed to convert kernel to sparse representation: "
346                   "scaled difference in input channels exceeds int32_t range");
347                 goto error;
348               }
349               *input_channel_diffs++ = (int32_t) diff;
350             }
351             first_nonzero = false;
352             last_ic = ic;
353             *output_channel_nonzeros += 1;
354           }
355         }
356         output_channel_nonzeros += 1;
357       }
358       for (size_t oc = round_down_po2(group_output_channels, output_channels_block_size); oc < group_output_channels; oc++) {
359         if XNN_LIKELY(bias != NULL) {
360           *nonzero_values++ = bias[oc];
361         } else {
362           *nonzero_values++ = 0.0f;
363         }
364         for (size_t ic = 0; ic < group_input_channels; ic++) {
365           const float weight = kernel[oc * group_input_channels + ic];
366           if (weight != 0.0f) {
367             *nonzero_values++ = weight;
368             if (first_nonzero) {
369               first_ic = ic;
370             } else {
371               const int64_t diff = (int64_t) ((uint64_t) ic - (uint64_t) last_ic) * (int64_t) sizeof(float);
372               if (diff != (int64_t) (int32_t) diff) {
373                 xnn_log_error("failed to convert kernel to sparse representation: "
374                   "scaled difference in input channels exceeds int32_t range");
375                 goto error;
376               }
377               *input_channel_diffs++ = (int32_t) diff;
378             }
379             first_nonzero = false;
380             last_ic = ic;
381             *output_channel_nonzeros += 1;
382           }
383         }
384         output_channel_nonzeros += 1;
385       }
386       // If there are any non-zero elements, we have to return to the initial input channel.
387       if (!first_nonzero) {
388         const int64_t diff = (int64_t) ((uint64_t) first_ic - (uint64_t) last_ic) * (int64_t) sizeof(float);
389         if (diff != (int64_t) (int32_t) diff) {
390           xnn_log_error("failed to convert kernel to sparse representation: "
391             "scaled difference in input channels exceeds int32_t range");
392           goto error;
393         }
394         *input_channel_diffs++ = (int32_t) diff;
395       }
396       convolution_op->first_input_channel = first_ic;
397 
398       convolution_op->ukernel.spmm = (struct xnn_ukernel_spmm) {
399         .function = spmm_parameters->ukernel,
400         .mr = spmm_parameters->mr,
401       };
402 
403       break;
404     }
405     case xnn_ukernel_type_conv2d_hwc2chw:
406     {
407       assert(groups == 1);
408 
409       const size_t packed_group_output_channels =
410         round_up(group_output_channels, xnn_params.f32.conv_hwc2chw_3x3c3s2.output_channel_tile);
411       const size_t packed_weights_size = groups * packed_group_output_channels *
412         (group_input_channels * kernel_height * kernel_width + 1 /* bias */) * sizeof(float);
413       size_t aligned_total_weights_size = round_up_po2(packed_weights_size, XNN_ALLOCATION_ALIGNMENT);
414       void* weights_ptr = xnn_get_pointer_to_write_weights(
415           convolution_op, aligned_total_weights_size, 0);
416       if (weights_ptr == NULL) {
417         xnn_log_error("failed to reserve or allocate %zu bytes for %s operator conv2d_hwc2chw packed weights",
418                       aligned_total_weights_size,
419                       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
420         goto error;
421       }
422 
423       xnn_pack_f32_dconv_oki_w(
424         group_output_channels,
425         group_input_channels,
426         xnn_params.f32.conv_hwc2chw_3x3c3s2.output_channel_tile,
427         kernel_height, kernel_width,
428         kernel, bias, weights_ptr, NULL);
429 
430       if (use_weights_cache(convolution_op)) {
431         convolution_op->packed_weights.offset = xnn_get_or_insert_weights_cache(
432             convolution_op->weights_cache, weights_ptr, aligned_total_weights_size);
433       }
434 
435       convolution_op->ukernel.conv2d = (struct xnn_ukernel_conv2d) {
436         .hwc2chw_function = xnn_params.f32.conv_hwc2chw_3x3c3s2.ukernel_with_symm_padding,
437         .output_height_tile = xnn_params.f32.conv_hwc2chw_3x3c3s2.output_height_tile,
438         .output_channel_tile = xnn_params.f32.conv_hwc2chw_3x3c3s2.output_channel_tile,
439       };
440 
441       break;
442     }
443     case xnn_ukernel_type_dwconv:
444     {
445       assert(dwconv2d_parameters != NULL);
446       assert(group_input_channels == 1);
447       assert(group_output_channels == 1);
448 
449       const size_t packed_weights_size = groups * (kernel_height * kernel_width + 1 /* bias */) * sizeof(float);
450       size_t aligned_total_weights_size = round_up_po2(packed_weights_size, XNN_ALLOCATION_ALIGNMENT);
451       void* weights_ptr = xnn_get_pointer_to_write_weights(
452           convolution_op, aligned_total_weights_size, 0);
453       if (weights_ptr == NULL) {
454         xnn_log_error("failed to reserve or allocate %zu bytes for %s operator dwconv packed weights",
455                       aligned_total_weights_size,
456                       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
457         goto error;
458       }
459 
460       if (flags & XNN_FLAG_DEPTHWISE_CONVOLUTION) {
461         xnn_pack_f32_chw_dwconv_hwg_w(
462           kernel_height * kernel_width, groups,
463           kernel, bias, weights_ptr, NULL);
464       } else {
465         xnn_pack_f32_chw_dwconv_ghw_w(
466           kernel_height * kernel_width, groups,
467           kernel, bias, weights_ptr, NULL);
468       }
469 
470       if (use_weights_cache(convolution_op)) {
471         convolution_op->packed_weights.offset = xnn_get_or_insert_weights_cache(
472             convolution_op->weights_cache, weights_ptr, aligned_total_weights_size);
473       }
474 
475       convolution_op->ukernel.dwconv2d = (struct xnn_ukernel_dwconv2d) {
476         .chw_function = dwconv2d_parameters->ukernel,
477         .output_width_tile = dwconv2d_parameters->output_width_tile,
478       };
479 
480       break;
481     }
482     default:
483       XNN_UNREACHABLE;
484   }
485 
486   convolution_op->padding_top = input_padding_top;
487   convolution_op->padding_right = input_padding_right;
488   convolution_op->padding_bottom = input_padding_bottom;
489   convolution_op->padding_left = input_padding_left;
490 
491   convolution_op->kernel_height = kernel_height;
492   convolution_op->kernel_width = kernel_width;
493   convolution_op->stride_height = subsampling_height;
494   convolution_op->stride_width = subsampling_width;
495   convolution_op->dilation_height = dilation_height;
496   convolution_op->dilation_width = dilation_width;
497   convolution_op->groups = groups;
498   convolution_op->group_input_channels = group_input_channels;
499   convolution_op->group_output_channels = group_output_channels;
500   convolution_op->input_pixel_stride = input_channel_stride;
501   convolution_op->output_pixel_stride = output_channel_stride;
502 
503   if (ukernel_type == xnn_ukernel_type_dwconv) {
504     xnn_init_f32_chw_params(&convolution_op->params.f32_chw, 0, output_min, output_max);
505   } else {
506     xnn_init_f32_minmax_params(&convolution_op->params.f32_minmax, output_min, output_max);
507   }
508 
509   convolution_op->type = xnn_operator_type_convolution_nchw_f32;
510   convolution_op->ukernel.type = ukernel_type;
511   convolution_op->flags = flags;
512 
513   convolution_op->state = xnn_run_state_invalid;
514 
515   *convolution_op_out = convolution_op;
516   return xnn_status_success;
517 
518 error:
519   xnn_delete_operator(convolution_op);
520   return status;
521 }
522 
setup_convolution2d_nchw(xnn_operator_t convolution_op,size_t batch_size,size_t input_height,size_t input_width,const void * input,void * output,uint32_t log2_input_element_size,uint32_t log2_filter_element_size,uint32_t bias_element_size,uint32_t log2_output_element_size,const void * params,void * chw_params,size_t num_threads)523 static enum xnn_status setup_convolution2d_nchw(
524   xnn_operator_t convolution_op,
525   size_t batch_size,
526   size_t input_height,
527   size_t input_width,
528   const void* input,
529   void* output,
530   uint32_t log2_input_element_size,
531   uint32_t log2_filter_element_size,
532   uint32_t bias_element_size,
533   uint32_t log2_output_element_size,
534   const void* params,
535   void* chw_params,
536   size_t num_threads)
537 {
538   convolution_op->state = xnn_run_state_invalid;
539 
540   if ((xnn_params.init_flags & XNN_INIT_FLAG_XNNPACK) == 0) {
541     xnn_log_error("failed to setup %s operator: XNNPACK is not initialized",
542       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
543     return xnn_status_uninitialized;
544   }
545 
546   if (input_width == 0 || input_height == 0) {
547     xnn_log_error(
548       "failed to setup %s operator with %zux%zu input: input dimensions must be non-zero",
549       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32), input_width, input_height);
550     return xnn_status_invalid_parameter;
551   }
552 
553   if (batch_size == 0) {
554     convolution_op->state = xnn_run_state_skip;
555     return xnn_status_success;
556   }
557 
558   if (convolution_op->weights_cache != NULL && !xnn_weights_cache_is_finalized(convolution_op->weights_cache)) {
559     xnn_log_error("failed to setup %s operator: weights cache is not finalized",
560       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
561     return xnn_status_invalid_state;
562   }
563 
564   convolution_op->batch_size = batch_size;
565   convolution_op->input_height = input_height;
566   convolution_op->input_width = input_width;
567   convolution_op->input = input;
568   convolution_op->output = output;
569 
570   const size_t output_height = xnn_compute_convolution_output_dimension(
571       convolution_op->padding_top + input_height + convolution_op->padding_bottom,
572       convolution_op->kernel_height,
573       convolution_op->dilation_height,
574       convolution_op->stride_height);
575   const size_t output_width = xnn_compute_convolution_output_dimension(
576       convolution_op->padding_left + input_width + convolution_op->padding_right,
577       convolution_op->kernel_width,
578       convolution_op->dilation_width,
579       convolution_op->stride_width);
580 
581   const size_t input_batch_stride = (input_height * input_width * convolution_op->input_pixel_stride) << log2_input_element_size;
582   const size_t output_batch_stride = (output_height * output_width * convolution_op->output_pixel_stride) << log2_output_element_size;
583   switch (convolution_op->ukernel.type) {
584     case xnn_ukernel_type_spmm:
585     {
586       const size_t num_nonzero_values = convolution_op->num_nonzero_values;
587       const size_t num_nonzero_blocks = convolution_op->num_nonzero_blocks;
588       const size_t num_output_channel_blocks = convolution_op->num_output_channel_blocks;
589 
590       convolution_op->num_nonzero_values = num_nonzero_values;
591       convolution_op->num_nonzero_blocks = num_nonzero_blocks;
592       convolution_op->num_output_channel_blocks = num_output_channel_blocks;
593 
594       float* nonzero_values = packed_weights(convolution_op);
595       int32_t* input_increments = (int32_t*) (nonzero_values + num_nonzero_values + convolution_op->group_output_channels);
596       uint32_t* output_channel_nonzeros = (uint32_t*) (input_increments + num_nonzero_blocks);
597       int32_t* input_channel_diffs = (int32_t*) (output_channel_nonzeros + num_output_channel_blocks);
598 
599       const size_t input_size = input_height * input_width;
600       for (size_t i = 0; i < num_nonzero_blocks; i++) {
601         const int32_t diff = input_channel_diffs[i];
602         const int64_t increment = (int64_t) diff * input_size;
603         if ((int64_t) (int32_t) increment != increment) {
604           xnn_log_error(
605             "failed to setup %s operator with sparse kernel representation: input increment exceeds int32_t range",
606             xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
607           return xnn_status_unsupported_parameter;
608         }
609         input_increments[i] = (int32_t) increment;
610       }
611 
612       convolution_op->context.spmm = (struct spmm_context) {
613           .n = convolution_op->group_output_channels,
614           .scaled_m = input_size * sizeof(float),
615           .input = (const void*) ((uintptr_t) input + (convolution_op->first_input_channel * input_size * sizeof(float))),
616           .nonzero_weights = nonzero_values,
617           .input_increments = input_increments,
618           .output_channel_nonzeros = output_channel_nonzeros,
619           .output = output,
620           .batched_input_stride = input_batch_stride,
621           .batched_output_stride = output_batch_stride,
622           .ukernel = convolution_op->ukernel.spmm.function,
623       };
624       memcpy(&convolution_op->context.spmm.params, params, sizeof(convolution_op->context.spmm.params));
625 
626       const size_t mr = convolution_op->ukernel.spmm.mr;
627       #if XNN_TEST_MODE
628         const size_t mc = mr;
629       #else
630         size_t mc = input_size;
631         if (num_threads > 1) {
632           const size_t target_tiles_per_thread = 5;
633           const size_t max_mc = divide_round_up(input_size, num_threads * target_tiles_per_thread);
634           if (max_mc < mc) {
635             mc = min(mc, divide_round_up(mc, max_mc * mr) * mr);
636           }
637         }
638       #endif
639       convolution_op->compute.type = xnn_parallelization_type_2d_tile_1d;
640       convolution_op->compute.task_2d_tile_1d = (pthreadpool_task_2d_tile_1d_t) xnn_compute_spmm;
641       convolution_op->compute.range[0] = batch_size;
642       convolution_op->compute.range[1] = input_size * sizeof(float);
643       convolution_op->compute.tile[0] = mc * sizeof(float);
644       convolution_op->state = xnn_run_state_ready;
645 
646       return xnn_status_success;
647     }
648     case xnn_ukernel_type_conv2d_hwc2chw:
649     {
650       const size_t zero_size = (input_width * convolution_op->group_input_channels << log2_input_element_size) + XNN_EXTRA_BYTES;
651       void* zero_buffer = xnn_reallocate_memory(convolution_op->zero_buffer, zero_size);
652       if (zero_buffer == NULL) {
653         xnn_log_error(
654           "failed to allocate %zu bytes for %s operator zero padding",
655           sizeof(struct xnn_operator), xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
656         return xnn_status_out_of_memory;
657       }
658       memset(zero_buffer, 0, zero_size);
659       convolution_op->zero_buffer = zero_buffer;
660 
661       convolution_op->context.conv2d = (struct conv2d_context) {
662         .input_height = input_height,
663         .input_width = input_width,
664         .input = input,
665         .input_batch_stride = input_batch_stride,
666         .zero = zero_buffer,
667         .packed_weights = packed_weights(convolution_op),
668         .output = output,
669         .output_batch_stride = output_batch_stride,
670         .input_padding_top = convolution_op->padding_top,
671         .output_channels = convolution_op->group_output_channels,
672         .output_height_stride = output_width << log2_output_element_size,
673         .output_channel_stride = output_height * output_width << log2_output_element_size,
674         .hwc2chw_ukernel = convolution_op->ukernel.conv2d.hwc2chw_function,
675       };
676       memcpy(&convolution_op->context.conv2d.params, params, sizeof(convolution_op->context.conv2d.params));
677 
678       const size_t output_height_tile = convolution_op->ukernel.conv2d.output_height_tile;
679       #if XNN_TEST_MODE
680         size_t output_height_slice = output_height_tile;
681       #else
682         size_t output_height_slice = output_height;
683         if (num_threads > 1) {
684           const size_t target_tiles_per_thread = 5;
685           const size_t max_output_height_slice = divide_round_up(output_height, num_threads * target_tiles_per_thread);
686           if (max_output_height_slice < output_height_slice) {
687             output_height_slice = min(output_height_slice,
688               divide_round_up(output_height_slice, max_output_height_slice * output_height_tile) * output_height_tile);
689           }
690         }
691       #endif
692       convolution_op->compute.type = xnn_parallelization_type_2d_tile_1d;
693       convolution_op->compute.task_2d_tile_1d = (pthreadpool_task_2d_tile_1d_t) xnn_compute_conv2d_hwc2chw;
694       convolution_op->compute.range[0] = batch_size;
695       convolution_op->compute.range[1] = output_height;
696       convolution_op->compute.tile[0] = output_height_slice;
697       convolution_op->state = xnn_run_state_ready;
698 
699       return xnn_status_success;
700     }
701     case xnn_ukernel_type_dwconv:
702     {
703       const size_t zero_size = (input_width << log2_input_element_size) + 2 * XNN_EXTRA_BYTES;
704       void* zero_buffer = xnn_reallocate_memory(convolution_op->zero_buffer, zero_size);
705       if (zero_buffer == NULL) {
706         xnn_log_error(
707           "failed to allocate %zu bytes for %s operator zero padding",
708           sizeof(struct xnn_operator), xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32));
709         return xnn_status_out_of_memory;
710       }
711       memset(zero_buffer, 0, zero_size);
712       convolution_op->zero_buffer = zero_buffer;
713 
714       xnn_update_f32_chw_params((union xnn_f32_chw_params*) chw_params, (uint32_t) input_width);
715       convolution_op->context.dwconv2d = (struct dwconv2d_context) {
716         .input_height = input_height,
717         .input_width = input_width << log2_input_element_size,
718         .input = input,
719         .zero = zero_buffer,
720         .input_padding_top = convolution_op->padding_top,
721         .input_channel_stride = input_height * input_width << log2_input_element_size,
722         .input_batch_stride = input_batch_stride,
723         .packed_weights = packed_weights(convolution_op),
724         .weights_channel_stride = bias_element_size +
725           (convolution_op->kernel_height * convolution_op->kernel_width << log2_filter_element_size),
726         .output = output,
727         .output_channel_stride = output_height * output_width << log2_output_element_size,
728         .output_batch_stride = output_batch_stride,
729         .chw_ukernel = convolution_op->ukernel.dwconv2d.chw_function,
730       };
731       memcpy(&convolution_op->context.dwconv2d.params, chw_params, sizeof(convolution_op->context.dwconv2d.params));
732 
733       convolution_op->compute.type = xnn_parallelization_type_2d;
734       convolution_op->compute.task_2d = (pthreadpool_task_2d_t) xnn_compute_dwconv2d_chw;
735       convolution_op->compute.range[0] = batch_size;
736       convolution_op->compute.range[1] = convolution_op->groups;
737       convolution_op->state = xnn_run_state_ready;
738 
739       return xnn_status_success;
740     }
741     default:
742       XNN_UNREACHABLE;
743   }
744 }
745 
xnn_setup_convolution2d_nchw_f32(xnn_operator_t convolution_op,size_t batch_size,size_t input_height,size_t input_width,const float * input,float * output,pthreadpool_t threadpool)746 enum xnn_status xnn_setup_convolution2d_nchw_f32(
747     xnn_operator_t convolution_op,
748     size_t batch_size,
749     size_t input_height,
750     size_t input_width,
751     const float* input,
752     float* output,
753     pthreadpool_t threadpool)
754 {
755   if (convolution_op->type != xnn_operator_type_convolution_nchw_f32) {
756     xnn_log_error("failed to setup operator: operator type mismatch (expected %s, got %s)",
757       xnn_operator_type_to_string(xnn_operator_type_convolution_nchw_f32),
758       xnn_operator_type_to_string(convolution_op->type));
759     return xnn_status_invalid_parameter;
760   }
761 
762   return setup_convolution2d_nchw(
763     convolution_op,
764     batch_size, input_height, input_width,
765     input, output,
766     2 /* log2(sizeof(input element)) = log2(sizeof(float)) */,
767     2 /* log2(sizeof(filter element)) = log2(sizeof(float)) */,
768     sizeof(float) /* sizeof(bias element) */,
769     2 /* log2(sizeof(output element)) = log2(sizeof(float)) */,
770     &convolution_op->params.f32_minmax,
771     &convolution_op->params.f32_chw,
772     pthreadpool_get_threads_count(threadpool));
773 }
774