Skip to content

Commit d3e58b0

Browse files
authored
Add portable upsample_bilinear2d kernel
Differential Revision: D65756150 Pull Request resolved: #6923
1 parent 953640f commit d3e58b0

File tree

12 files changed

+1146
-0
lines changed

12 files changed

+1146
-0
lines changed

kernels/aten/functions.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,8 @@
405405

406406
- op: unsqueeze_copy.out
407407

408+
- op: upsample_bilinear2d.vec_out
409+
408410
- op: upsample_nearest2d.out
409411

410412
- op: upsample_nearest2d.vec_out
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include <executorch/kernels/portable/cpu/util/upsample_util.h>
10+
#include <executorch/runtime/kernel/kernel_includes.h>
11+
12+
namespace torch {
13+
namespace executor {
14+
namespace native {
15+
16+
using exec_aten::ArrayRef;
17+
using exec_aten::optional;
18+
using exec_aten::SizesType;
19+
20+
namespace {
21+
template <typename CTYPE>
22+
void upsample_bilinear2d_kernel_impl(
23+
const Tensor& in,
24+
bool align_corners,
25+
const float scale_h,
26+
const float scale_w,
27+
Tensor& out) {
28+
const auto in_data = in.const_data_ptr<CTYPE>();
29+
auto out_data = out.mutable_data_ptr<CTYPE>();
30+
31+
auto in_plane = in_data;
32+
for (auto n = 0; n < out.size(0); n++) {
33+
for (auto c = 0; c < out.size(1); c++) {
34+
for (auto h = 0; h < out.size(2); h++) {
35+
// Compute source index and weights.
36+
int64_t in_h1, in_h2;
37+
float weight_h, inv_weight_h;
38+
39+
compute_source_index_and_lambda(
40+
in_h1,
41+
in_h2,
42+
weight_h,
43+
inv_weight_h,
44+
scale_h,
45+
h,
46+
in.sizes()[2],
47+
out.sizes()[2],
48+
align_corners);
49+
50+
for (auto w = 0; w < out.size(3); w++) {
51+
int64_t in_w1, in_w2;
52+
float weight_w, inv_weight_w;
53+
54+
compute_source_index_and_lambda(
55+
in_w1,
56+
in_w2,
57+
weight_w,
58+
inv_weight_w,
59+
scale_w,
60+
w,
61+
in.sizes()[3],
62+
out.sizes()[3],
63+
align_corners);
64+
65+
const auto top_left =
66+
in_plane[in_h1 * in.strides()[2] + in_w1 * in.strides()[3]];
67+
const auto top_right =
68+
in_plane[in_h1 * in.strides()[2] + in_w2 * in.strides()[3]];
69+
const auto bottom_left =
70+
in_plane[in_h2 * in.strides()[2] + in_w1 * in.strides()[3]];
71+
const auto bottom_right =
72+
in_plane[in_h2 * in.strides()[2] + in_w2 * in.strides()[3]];
73+
74+
const auto top = top_left * weight_w + top_right * inv_weight_w;
75+
const auto bottom =
76+
bottom_left * weight_w + bottom_right * inv_weight_w;
77+
const auto val = top * weight_h + bottom * inv_weight_h;
78+
79+
*out_data = val;
80+
out_data++;
81+
}
82+
}
83+
84+
in_plane += in.strides()[1];
85+
}
86+
}
87+
}
88+
} // namespace
89+
90+
// Signatures are auto-generated, so disable pass-by-value lint.
91+
// NOLINTBEGIN(facebook-hte-ConstantArgumentPassByValue,
92+
// facebook-hte-ParameterMightThrowOnCopy)
93+
Tensor& upsample_bilinear2d_vec_out(
94+
KernelRuntimeContext& ctx,
95+
const Tensor& in,
96+
const exec_aten::OptionalArrayRef<int64_t> output_size,
97+
bool align_corners,
98+
const exec_aten::OptionalArrayRef<double> scale_factors,
99+
Tensor& out) {
100+
// Preconditions (checked in check_..._args):
101+
// In and out tensors have same dtype.
102+
// In and out tensors are rank 4 and have same dim[0] and dim[1].
103+
// In and out tensors are default dim order (NCHW).
104+
ET_KERNEL_CHECK(
105+
ctx,
106+
check_upsample_bilinear2d_args(
107+
in, output_size, align_corners, scale_factors, out),
108+
InvalidArgument,
109+
out);
110+
111+
double scale_h, scale_w;
112+
113+
ET_KERNEL_CHECK_MSG(
114+
ctx,
115+
resize_upsample_2d(
116+
in, output_size, scale_factors, scale_h, scale_w, out) == Error::Ok,
117+
InvalidArgument,
118+
out,
119+
"Failed to resize output tensor");
120+
121+
const auto kernel_scale_h = area_pixel_compute_scale<double>(
122+
in.sizes()[2], out.sizes()[2], align_corners, scale_h);
123+
const auto kernel_scale_w = area_pixel_compute_scale<double>(
124+
in.sizes()[3], out.sizes()[3], align_corners, scale_w);
125+
126+
ET_SWITCH_REAL_TYPES(
127+
in.scalar_type(), ctx, "upsample_bilinear2d.out", CTYPE, [&]() {
128+
upsample_bilinear2d_kernel_impl<CTYPE>(
129+
in, align_corners, kernel_scale_h, kernel_scale_w, out);
130+
});
131+
132+
return out;
133+
}
134+
// NOLINTEND(facebook-hte-ConstantArgumentPassByValue,
135+
// facebook-hte-ParameterMightThrowOnCopy)
136+
137+
} // namespace native
138+
} // namespace executor
139+
} // namespace torch

kernels/portable/cpu/util/targets.bzl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def define_common_targets():
3131
"//executorch/kernels/portable/cpu/util:advanced_index_util",
3232
"//executorch/kernels/portable/cpu/util:slice_util",
3333
"//executorch/kernels/portable/cpu/util:elementwise_util",
34+
"//executorch/kernels/portable/cpu/util:upsample_util",
3435
],
3536
visibility = ["//executorch/...", "@EXECUTORCH_CLIENTS"],
3637
)
@@ -266,6 +267,16 @@ def define_common_targets():
266267
visibility = ["//executorch/kernels/portable/cpu/..."],
267268
)
268269

270+
runtime.cxx_library(
271+
name = "upsample_util",
272+
srcs = ["upsample_util.cpp"],
273+
exported_headers = ["upsample_util.h"],
274+
deps = [
275+
"//executorch/runtime/kernel:kernel_includes",
276+
],
277+
visibility = ["//executorch/kernels/portable/cpu/..."],
278+
)
279+
269280
# Utility functions that can be used by operators that perform reduction
270281
for aten_mode in [True, False]:
271282
suffix = "_aten" if aten_mode else ""
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include <executorch/kernels/portable/cpu/util/upsample_util.h>
10+
#include <executorch/runtime/core/exec_aten/util/tensor_util.h>
11+
12+
namespace torch {
13+
namespace executor {
14+
15+
bool check_upsample_2d_common_args(
16+
const Tensor& in,
17+
const exec_aten::OptionalArrayRef<int64_t>& output_size,
18+
const exec_aten::OptionalArrayRef<double>& scale_factors,
19+
Tensor& out) {
20+
ET_LOG_AND_RETURN_IF_FALSE(tensors_have_same_dtype(in, out));
21+
ET_LOG_AND_RETURN_IF_FALSE(in.dim() == 4);
22+
ET_LOG_AND_RETURN_IF_FALSE(out.dim() == 4);
23+
ET_LOG_AND_RETURN_IF_FALSE(tensor_is_default_dim_order(in));
24+
ET_LOG_AND_RETURN_IF_FALSE(tensor_is_default_dim_order(out));
25+
ET_LOG_AND_RETURN_IF_FALSE(
26+
output_size.has_value() ^ scale_factors.has_value());
27+
if (scale_factors.has_value()) {
28+
ET_LOG_AND_RETURN_IF_FALSE(scale_factors.value().size() == 2);
29+
ET_LOG_AND_RETURN_IF_FALSE(scale_factors.value()[0] > 0);
30+
ET_LOG_AND_RETURN_IF_FALSE(scale_factors.value()[1] > 0);
31+
} else if (output_size.has_value()) {
32+
ET_LOG_AND_RETURN_IF_FALSE(output_size.value().size() == 2);
33+
ET_LOG_AND_RETURN_IF_FALSE(output_size.value()[0] > 0);
34+
ET_LOG_AND_RETURN_IF_FALSE(output_size.value()[1] > 0);
35+
}
36+
37+
return true;
38+
}
39+
40+
bool check_upsample_bilinear2d_args(
41+
const Tensor& in,
42+
const exec_aten::OptionalArrayRef<int64_t>& output_size,
43+
ET_UNUSED const bool align_corners,
44+
const exec_aten::OptionalArrayRef<double>& scale_factors,
45+
Tensor& out) {
46+
return check_upsample_2d_common_args(in, output_size, scale_factors, out);
47+
}
48+
49+
Error resize_upsample_2d(
50+
const Tensor& in,
51+
const exec_aten::OptionalArrayRef<int64_t>& output_size,
52+
const exec_aten::OptionalArrayRef<double>& scale_factors,
53+
double& scale_h_out,
54+
double& scale_w_out,
55+
Tensor& out) {
56+
// Either output_size or scale_factors are provided, not both. This
57+
// is checked in check_..._args.
58+
// Scales are transformed according to align_corners.
59+
std::array<Tensor::SizesType, kTensorDimensionLimit> target_size;
60+
61+
const auto dim = in.dim();
62+
std::copy(in.sizes().cbegin(), in.sizes().cend(), target_size.begin());
63+
64+
if (scale_factors.has_value()) {
65+
scale_h_out = scale_factors.value()[0];
66+
scale_w_out = scale_factors.value()[1];
67+
68+
target_size[dim - 2] =
69+
static_cast<Tensor::SizesType>(in.sizes()[dim - 2] * scale_h_out);
70+
target_size[dim - 1] =
71+
static_cast<Tensor::SizesType>(in.sizes()[dim - 1] * scale_w_out);
72+
} else if (output_size.has_value()) {
73+
scale_h_out =
74+
static_cast<double>(output_size.value()[0]) / in.sizes()[dim - 2];
75+
scale_w_out =
76+
static_cast<double>(output_size.value()[1]) / in.sizes()[dim - 1];
77+
78+
target_size[dim - 2] = output_size.value()[0];
79+
target_size[dim - 1] = output_size.value()[1];
80+
} else {
81+
ET_LOG(Error, "Invalid output_size or scale_factors");
82+
return Error::InvalidArgument;
83+
}
84+
85+
ET_CHECK_OR_RETURN_ERROR(
86+
target_size[dim - 2] > 0 && target_size[dim - 1] > 0,
87+
InvalidArgument,
88+
"Upsampled output size must be non-empty, but was %ld x %ld.",
89+
static_cast<long>(target_size[dim - 2]),
90+
static_cast<long>(target_size[dim - 1]));
91+
92+
return resize_tensor(out, {target_size.data(), static_cast<size_t>(dim)});
93+
}
94+
95+
} // namespace executor
96+
} // namespace torch

0 commit comments

Comments
 (0)