Skip to content

Commit 636daf1

Browse files
add forward portable Conv3d support for contiguous 5D inputs in aten:convolution.out (#19977)
Fixes #11704 - add forward portable Conv3d support for contiguous 5D inputs in `aten::convolution.out` - add direct kernel tests and a portable_lib e2e regression, including nonzero circular padding updated according to comments, added tests that cover float16, bfloat16, int ci failures seems unrelated to my test --------- Co-authored-by: RJ Ascani <rja@meta.com>
1 parent fc73864 commit 636daf1

4 files changed

Lines changed: 406 additions & 47 deletions

File tree

backends/cortex_m/test/ops/test_conv.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,6 @@ def test_dialect_conv2d(test_case, cortex_m_target):
333333

334334
xfails_implementation: dict[str, xfail_type] = {
335335
"conv1d": "Currently not supported.",
336-
"conv3d": "Currently not supported.",
337336
}
338337

339338

kernels/portable/cpu/op_convolution.cpp

Lines changed: 156 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,117 @@ void conv2d_impl(
191191
}
192192
}
193193

194+
template <typename CTYPE, typename LoadFn = CTYPE (*)(const void*)>
195+
void conv3d_impl(
196+
const CTYPE* const in_ptr,
197+
SizesArrayRef in_sizes,
198+
StridesArrayRef in_strides,
199+
const CTYPE* const w_ptr,
200+
SizesArrayRef w_sizes,
201+
StridesArrayRef w_strides,
202+
const std::optional<Tensor>& bias,
203+
const char* const bias_ptr,
204+
LoadFn load_bias,
205+
IntArrayRef stride,
206+
IntArrayRef padding,
207+
IntArrayRef dilation,
208+
const int64_t groups,
209+
CTYPE* const out_ptr,
210+
SizesArrayRef out_sizes,
211+
StridesArrayRef out_strides,
212+
const size_t batch,
213+
const size_t group,
214+
const size_t out_c) {
215+
size_t in_C = in_sizes[1];
216+
size_t out_D = out_sizes[2];
217+
size_t in_D = in_sizes[2];
218+
size_t w_D = w_sizes[2];
219+
220+
size_t out_H = out_sizes[3];
221+
size_t in_H = in_sizes[3];
222+
size_t w_H = w_sizes[3];
223+
224+
size_t out_W = out_sizes[4];
225+
size_t in_W = in_sizes[4];
226+
size_t w_W = w_sizes[4];
227+
228+
size_t in_C_per_group = in_C / groups;
229+
size_t in_c_start = group * in_C_per_group;
230+
231+
executorch::aten::SizesType in_coord[kTensorDimensionLimit];
232+
in_coord[0] = batch;
233+
executorch::aten::SizesType out_coord[kTensorDimensionLimit];
234+
out_coord[0] = batch;
235+
out_coord[1] = out_c;
236+
executorch::aten::SizesType w_coord[kTensorDimensionLimit];
237+
238+
const int64_t stride_z = val_at(stride, 0);
239+
const int64_t padding_z = val_at(padding, 0, /*default_value=*/0);
240+
const int64_t dilation_z = val_at(dilation, 0);
241+
const int64_t stride_y = val_at(stride, 1);
242+
const int64_t padding_y = val_at(padding, 1, /*default_value=*/0);
243+
const int64_t dilation_y = val_at(dilation, 1);
244+
const int64_t stride_x = val_at(stride, 2);
245+
const int64_t padding_x = val_at(padding, 2, /*default_value=*/0);
246+
const int64_t dilation_x = val_at(dilation, 2);
247+
248+
w_coord[0] = out_c;
249+
for (const auto out_z : c10::irange(out_D)) {
250+
out_coord[2] = out_z;
251+
for (const auto out_y : c10::irange(out_H)) {
252+
out_coord[3] = out_y;
253+
for (const auto out_x : c10::irange(out_W)) {
254+
out_coord[4] = out_x;
255+
256+
CTYPE accum = 0.0f;
257+
for (const auto in_c :
258+
c10::irange(in_c_start, in_c_start + in_C_per_group)) {
259+
in_coord[1] = in_c;
260+
w_coord[1] = in_c - in_c_start;
261+
262+
for (const auto w_z : c10::irange(w_D)) {
263+
w_coord[2] = w_z;
264+
ssize_t in_z = stride_z * out_z + dilation_z * w_z - padding_z;
265+
in_coord[2] = in_z;
266+
if (in_z < 0 || in_z >= static_cast<ssize_t>(in_D)) {
267+
continue;
268+
}
269+
270+
for (const auto w_y : c10::irange(w_H)) {
271+
w_coord[3] = w_y;
272+
ssize_t in_y = stride_y * out_y + dilation_y * w_y - padding_y;
273+
in_coord[3] = in_y;
274+
if (in_y < 0 || in_y >= static_cast<ssize_t>(in_H)) {
275+
continue;
276+
}
277+
278+
for (const auto w_x : c10::irange(w_W)) {
279+
w_coord[4] = w_x;
280+
ssize_t in_x = stride_x * out_x + dilation_x * w_x - padding_x;
281+
in_coord[4] = in_x;
282+
if (in_x >= 0 && in_x < static_cast<ssize_t>(in_W)) {
283+
size_t in_idx =
284+
calculate_linear_index(in_coord, in_strides.data(), 5);
285+
size_t w_idx =
286+
calculate_linear_index(w_coord, w_strides.data(), 5);
287+
accum += in_ptr[in_idx] * w_ptr[w_idx];
288+
}
289+
}
290+
}
291+
}
292+
}
293+
294+
if (bias_ptr != nullptr) {
295+
accum += load_bias(&bias_ptr[out_c * bias.value().element_size()]);
296+
}
297+
size_t out_idx =
298+
calculate_linear_index(out_coord, out_strides.data(), 5);
299+
out_ptr[out_idx] = accum;
300+
}
301+
}
302+
}
303+
}
304+
194305
template <typename CTYPE, typename LoadFn = CTYPE (*)(const void*)>
195306
void convolution_wrapper(
196307
const Tensor& in,
@@ -294,6 +405,7 @@ void convolution_wrapper(
294405
size_t out_N = out.size(0);
295406
size_t out_C = out.size(1);
296407
size_t out_C_per_group = out_C / groups;
408+
const bool is_conv3d = in_sizes.size() == 5;
297409

298410
if (transposed) {
299411
// For transposed convolution, we need to initialized the output before we
@@ -318,27 +430,50 @@ void convolution_wrapper(
318430
// Populate all the out channels in the group
319431
for (const auto out_c :
320432
c10::irange(out_c_start, out_c_start + out_C_per_group)) {
321-
conv2d_impl(
322-
in_ptr,
323-
in_sizes,
324-
{in_strides, 4},
325-
w_ptr,
326-
weight_sizes,
327-
{weight_strides, 4},
328-
bias,
329-
bias_ptr,
330-
load_bias,
331-
stride_,
332-
padding_,
333-
dilation_,
334-
groups,
335-
out_ptr,
336-
out_sizes,
337-
{out_strides, 4},
338-
batch,
339-
group,
340-
out_c,
341-
transposed);
433+
if (is_conv3d) {
434+
conv3d_impl(
435+
in_ptr,
436+
in_sizes,
437+
{in_strides, 5},
438+
w_ptr,
439+
weight_sizes,
440+
{weight_strides, 5},
441+
bias,
442+
bias_ptr,
443+
load_bias,
444+
stride_,
445+
padding_,
446+
dilation_,
447+
groups,
448+
out_ptr,
449+
out_sizes,
450+
{out_strides, 5},
451+
batch,
452+
group,
453+
out_c);
454+
} else {
455+
conv2d_impl(
456+
in_ptr,
457+
in_sizes,
458+
{in_strides, 4},
459+
w_ptr,
460+
weight_sizes,
461+
{weight_strides, 4},
462+
bias,
463+
bias_ptr,
464+
load_bias,
465+
stride_,
466+
padding_,
467+
dilation_,
468+
groups,
469+
out_ptr,
470+
out_sizes,
471+
{out_strides, 4},
472+
batch,
473+
group,
474+
out_c,
475+
transposed);
476+
}
342477
}
343478
}
344479
}

kernels/portable/cpu/util/kernel_ops_util.cpp

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ bool param_array_is_valid(
4545
return true;
4646
}
4747

48+
void fill_convolution_kernel_size(
49+
const Tensor& weight,
50+
int64_t* kernel_size,
51+
size_t* kernel_ndim) {
52+
*kernel_ndim = weight.dim() - 2;
53+
for (const auto i : c10::irange(*kernel_ndim)) {
54+
kernel_size[i] = weight.size(i + 2);
55+
}
56+
}
57+
4858
} // namespace
4959

5060
bool int_array_all_ge(IntArrayRef array, int64_t val) {
@@ -381,18 +391,29 @@ bool check_convolution_args(
381391
const Tensor& out) {
382392
ET_LOG_AND_RETURN_IF_FALSE(tensors_have_same_dtype(in, weight, out));
383393

384-
ET_LOG_AND_RETURN_IF_FALSE(tensor_is_default_or_channels_last_dim_order(in));
385-
ET_LOG_AND_RETURN_IF_FALSE(
386-
tensor_is_default_or_channels_last_dim_order(weight));
387-
ET_LOG_AND_RETURN_IF_FALSE(tensor_is_default_or_channels_last_dim_order(out));
388-
389394
ET_CHECK_OR_RETURN_FALSE(
390-
in.dim() == 3 || in.dim() == 4,
391-
"Expect input tensor to be 3-D or 4-D, but got, %zu.",
395+
in.dim() == 3 || in.dim() == 4 || in.dim() == 5,
396+
"Expect input tensor to be 3-D, 4-D or 5-D, but got, %zu.",
392397
static_cast<size_t>(in.dim()));
393398
ET_LOG_AND_RETURN_IF_FALSE(tensor_is_rank(weight, in.dim()));
394399
ET_LOG_AND_RETURN_IF_FALSE(tensor_is_rank(out, in.dim()));
395400

401+
if (in.dim() == 5) {
402+
ET_LOG_AND_RETURN_IF_FALSE(tensor_is_default_dim_order(in));
403+
ET_LOG_AND_RETURN_IF_FALSE(tensor_is_default_dim_order(weight));
404+
ET_LOG_AND_RETURN_IF_FALSE(tensor_is_default_dim_order(out));
405+
ET_CHECK_OR_RETURN_FALSE(
406+
!transposed,
407+
"Transposed 3D convolution is not yet supported on portable.");
408+
} else {
409+
ET_LOG_AND_RETURN_IF_FALSE(
410+
tensor_is_default_or_channels_last_dim_order(in));
411+
ET_LOG_AND_RETURN_IF_FALSE(
412+
tensor_is_default_or_channels_last_dim_order(weight));
413+
ET_LOG_AND_RETURN_IF_FALSE(
414+
tensor_is_default_or_channels_last_dim_order(out));
415+
}
416+
396417
if (bias.has_value()) {
397418
ET_LOG_AND_RETURN_IF_FALSE(tensor_is_rank(bias.value(), 1));
398419
ET_CHECK_OR_RETURN_FALSE(
@@ -404,15 +425,11 @@ bool check_convolution_args(
404425
transposed ? groups * weight.size(1) : weight.size(0));
405426
}
406427

407-
int64_t kernel_size[2];
408-
size_t kernel_ndim = 2;
409-
if (weight.dim() == 3) {
410-
kernel_size[0] = weight.size(2);
411-
kernel_ndim = 1;
412-
} else {
413-
kernel_size[0] = weight.size(2);
414-
kernel_size[1] = weight.size(3);
415-
}
428+
int64_t kernel_size[3];
429+
size_t kernel_ndim = 0;
430+
fill_convolution_kernel_size(weight, kernel_size, &kernel_ndim);
431+
ET_LOG_AND_RETURN_IF_FALSE(
432+
kernel_size_is_valid({kernel_size, kernel_ndim}, kernel_ndim));
416433
ET_LOG_AND_RETURN_IF_FALSE(
417434
stride_is_valid(stride, kernel_ndim, /*allow_empty=*/false));
418435
ET_LOG_AND_RETURN_IF_FALSE(
@@ -482,15 +499,9 @@ void get_convolution_out_target_size(
482499
out_sizes[1] = in.size(1) == 0 ? 0 : groups * weight.size(1);
483500
}
484501

485-
int64_t kernel_size[2];
486-
size_t kernel_ndim = 2;
487-
if (weight.dim() == 3) {
488-
kernel_size[0] = weight.size(2);
489-
kernel_ndim = 1;
490-
} else {
491-
kernel_size[0] = weight.size(2);
492-
kernel_size[1] = weight.size(3);
493-
}
502+
int64_t kernel_size[3];
503+
size_t kernel_ndim = 0;
504+
fill_convolution_kernel_size(weight, kernel_size, &kernel_ndim);
494505
calculate_kernel_output_sizes(
495506
in,
496507
kernel_ndim,

0 commit comments

Comments
 (0)