Skip to content

Commit 6829de0

Browse files
larryliu0820facebook-github-bot
authored andcommitted
Add unfold_copy.out
Summary: As titled Naive kernel for `unfold_copy.out` Differential Revision: D70597013
1 parent 5dd96c3 commit 6829de0

File tree

7 files changed

+244
-0
lines changed

7 files changed

+244
-0
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#include <c10/util/irange.h>
2+
#include <cstring>
3+
#include <executorch/kernels/portable/cpu/util/copy_ops_util.h>
4+
#include <executorch/runtime/kernel/kernel_includes.h>
5+
#include <executorch/runtime/platform/assert.h>
6+
namespace torch {
7+
namespace executor {
8+
namespace native {
9+
10+
using Tensor = executorch::aten::Tensor;
11+
12+
// unfold_copy(Tensor self, int dimension, int size, int step, *, Tensor(a!) out) -> Tensor(a!)
13+
Tensor unfold_copy_out(
14+
KernelRuntimeContext& ctx,
15+
const Tensor& self,
16+
int64_t dim,
17+
int64_t size,
18+
int64_t step,
19+
Tensor& out) {
20+
(void)ctx;
21+
// Check if dimension is valid
22+
ET_KERNEL_CHECK(
23+
ctx,
24+
check_unfold_copy_args(self, dim, size, step, out),
25+
InvalidArgument,
26+
out);
27+
if (dim < 0) {
28+
dim += nonzero_dim(self);
29+
}
30+
// Calculate output size
31+
Tensor::SizesType expected_output_size[kTensorDimensionLimit];
32+
size_t expected_out_dim = 0;
33+
34+
get_unfold_copy_out_target_size(self, dim, size, step, expected_output_size, &expected_out_dim);
35+
36+
ET_KERNEL_CHECK(
37+
ctx,
38+
resize_tensor(out, {expected_output_size, expected_out_dim}) == Error::Ok,
39+
InvalidArgument,
40+
out);
41+
42+
// Copy data
43+
const size_t leading_dims = getLeadingDims(self, dim);
44+
const size_t trailing_dims = getTrailingDims(self, dim);
45+
ScalarType in_type = self.scalar_type();
46+
ScalarType out_type = out.scalar_type();
47+
48+
ET_SWITCH_REALHBBF16_TYPES(
49+
in_type, ctx, "unfold_copy.out", CTYPE_IN, [&]() {
50+
const CTYPE_IN* input_ptr = self.const_data_ptr<CTYPE_IN>();
51+
ET_SWITCH_REALHBBF16_TYPES(out_type, ctx, "unfold_copy.out", CTYPE_OUT, [&] {
52+
CTYPE_OUT* out_ptr = out.mutable_data_ptr<CTYPE_OUT>();
53+
for (const auto i : c10::irange(leading_dims)) {
54+
const CTYPE_IN* src = input_ptr + i * self.size(dim) * trailing_dims;
55+
for (size_t j = 0; j < out.size(dim); ++j) {
56+
const CTYPE_IN* dim_src = src + j * step * trailing_dims;
57+
for (const auto k: c10::irange(trailing_dims)) {
58+
for (const auto l: c10::irange(size)) {
59+
*out_ptr = convert<CTYPE_OUT, CTYPE_IN>(dim_src[k + l * trailing_dims]);
60+
out_ptr++;
61+
}
62+
}
63+
}
64+
}
65+
});
66+
});
67+
return out;
68+
}
69+
} // namespace native
70+
} // namespace executor
71+
} // namespace torch

kernels/portable/cpu/util/copy_ops_util.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,5 +964,38 @@ void get_diagonal_copy_out_target_size(
964964
out_sizes[in.dim() - 2] = diagonal_size;
965965
}
966966

967+
bool check_unfold_copy_args(const Tensor& self,
968+
int64_t dim,
969+
int64_t size,
970+
int64_t step,
971+
Tensor& out) {
972+
if (dim < 0) {
973+
dim += nonzero_dim(self);
974+
}
975+
ET_LOG_AND_RETURN_IF_FALSE(tensor_has_dim(self, dim));
976+
ET_CHECK_OR_RETURN_FALSE(size >= 0, "size is %" PRId64 " but must be >= 0", size);
977+
ET_CHECK_OR_RETURN_FALSE(size <= self.size(dim), "maximum size for tensor at dimension %" PRId64 " is %" PRId64 " but size is %" PRId64, dim, self.size(dim), size);
978+
ET_CHECK_OR_RETURN_FALSE(step > 0, "step is %" PRId64 " but must be > 0", step);
979+
return true;
980+
}
981+
982+
void get_unfold_copy_out_target_size(const Tensor& self,
983+
int64_t dim,
984+
int64_t size,
985+
int64_t step,
986+
executorch::aten::SizesType* out_sizes,
987+
size_t* out_ndim) {
988+
for (size_t i = 0; i < dim; ++i) {
989+
out_sizes[i] = self.size(i);
990+
}
991+
// At `dim` dimension, we split the tensor into `size` chunks with `step` stride.
992+
out_sizes[dim] = (self.size(dim) - size + step) / step;
993+
for (size_t i = dim + 1; i < self.dim(); ++i) {
994+
out_sizes[i] = self.size(i);
995+
}
996+
out_sizes[self.dim()] = size;
997+
*out_ndim = self.dim() + 1;
998+
}
999+
9671000
} // namespace executor
9681001
} // namespace torch

kernels/portable/cpu/util/copy_ops_util.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,5 +233,20 @@ void get_diagonal_copy_out_target_size(
233233
executorch::aten::SizesType* out_sizes,
234234
size_t* out_ndim);
235235

236+
bool check_unfold_copy_args(
237+
const Tensor& self,
238+
int64_t dim,
239+
int64_t size,
240+
int64_t step,
241+
Tensor& out);
242+
243+
void get_unfold_copy_out_target_size(
244+
const Tensor& self,
245+
int64_t dim,
246+
int64_t size,
247+
int64_t step,
248+
executorch::aten::SizesType* out_sizes,
249+
size_t* out_ndim);
250+
236251
} // namespace executor
237252
} // namespace torch

kernels/portable/functions.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,11 @@
917917
- arg_meta: null
918918
kernel_name: torch::executor::unbind_copy_int_out
919919

920+
- op: unfold_copy.out
921+
kernels:
922+
- arg_meta: null
923+
kernel_name: torch::executor::unfold_copy_out
924+
920925
- op: unsqueeze_copy.out
921926
kernels:
922927
- arg_meta: null

kernels/test/op_unfold_copy_test.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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/test/FunctionHeaderWrapper.h> // Declares the operator
10+
#include <executorch/kernels/test/supported_features.h>
11+
#include <executorch/runtime/core/exec_aten/exec_aten.h>
12+
#include <executorch/runtime/core/exec_aten/testing_util/tensor_factory.h>
13+
#include <executorch/runtime/core/exec_aten/testing_util/tensor_util.h>
14+
#include <executorch/runtime/core/exec_aten/util/tensor_util.h>
15+
16+
#include <executorch/kernels/test/TestUtil.h>
17+
18+
#include <gtest/gtest.h>
19+
20+
using namespace ::testing;
21+
using executorch::aten::ScalarType;
22+
using executorch::aten::Tensor;
23+
using torch::executor::testing::TensorFactory;
24+
25+
class OpUnfoldTest : public OperatorTest {
26+
protected:
27+
Tensor& op_unfold_copy_out(
28+
const Tensor& self,
29+
int64_t dim,
30+
int64_t size,
31+
int64_t step,
32+
Tensor& out) {
33+
return torch::executor::aten::unfold_copy_outf(
34+
context_, self, dim, size, step, out);
35+
}
36+
37+
template <class CTYPE, ScalarType DTYPE>
38+
void test_unfold_copy_dtype() {
39+
TensorFactory<DTYPE> tf;
40+
41+
auto input = tf.make({3, 3}, {1, 2, 3, 4, 5, 6, 7, 8, 9});
42+
auto expected = tf.make({3, 2, 2}, {1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 8, 9});
43+
auto actual_out = tf.zeros_like(expected);
44+
op_unfold_copy_out(input, /*dim=*/1, /*size=*/2, /*step=*/1, actual_out);
45+
EXPECT_TENSOR_CLOSE(actual_out, expected);
46+
}
47+
};
48+
49+
TEST_F(OpUnfoldTest, SmokeTest) {
50+
TensorFactory<ScalarType::Float> tf;
51+
const auto input = tf.make({3, 3}, {1, 2, 3, 4, 5, 6, 7, 8, 9});
52+
const auto expected = tf.make({3, 1, 2}, {1, 2, 4, 5, 7, 8});
53+
auto output = tf.zeros_like(expected);
54+
55+
op_unfold_copy_out(input, /*dim=*/1, /*size=*/2, /*step=*/2, output);
56+
EXPECT_TENSOR_CLOSE(output, expected);
57+
}
58+
59+
TEST_F(OpUnfoldTest, DType) {
60+
#define TEST_ENTRY(ctype, dtype) \
61+
test_unfold_copy_dtype<ctype, ScalarType::dtype>();
62+
ET_FORALL_REALHBF16_TYPES(TEST_ENTRY);
63+
#undef TEST_ENTRY
64+
}
65+
66+
TEST_F(OpUnfoldTest, ZeroDimension) {
67+
TensorFactory<ScalarType::Float> tf;
68+
const auto input = tf.make({3, 3}, {1, 2, 3, 4, 5, 6, 7, 8, 9});
69+
const auto expected = tf.make({2, 3, 2}, {1, 4, 2, 5, 3, 6, 4, 7, 5, 8, 6, 9});
70+
auto output = tf.zeros_like(expected);
71+
72+
op_unfold_copy_out(input, /*dim=*/0, /*size=*/2, /*step=*/1, output);
73+
EXPECT_TENSOR_CLOSE(output, expected);
74+
}
75+
76+
TEST_F(OpUnfoldTest, NegativeDimension) {
77+
TensorFactory<ScalarType::Float> tf;
78+
const auto input = tf.make({3, 3}, {1, 2, 3, 4, 5, 6, 7, 8, 9});
79+
const auto expected = tf.make({3, 1, 2}, {1, 2, 4, 5, 7, 8});
80+
auto output = tf.zeros_like(expected);
81+
82+
op_unfold_copy_out(input, /*dim=*/-1, /*size=*/2, /*step=*/2, output);
83+
EXPECT_TENSOR_CLOSE(output, expected);
84+
}
85+
86+
TEST_F(OpUnfoldTest, LargeStep) {
87+
TensorFactory<ScalarType::Float> tf;
88+
const auto input = tf.make({3, 3}, {1, 2, 3, 4, 5, 6, 7, 8, 9});
89+
const auto expected = tf.make({3, 1, 2}, {1, 2, 4, 5, 7, 8});
90+
auto output = tf.zeros_like(expected);
91+
92+
op_unfold_copy_out(input, /*dim=*/-1, /*size=*/2, /*step=*/5, output);
93+
EXPECT_TENSOR_CLOSE(output, expected);
94+
}
95+
96+
TEST_F(OpUnfoldTest, ZeroSize) {
97+
TensorFactory<ScalarType::Float> tf;
98+
const auto input = tf.make({3, 3}, {1, 2, 3, 4, 5, 6, 7, 8, 9});
99+
const auto expected = tf.make({3, 4, 0}, {});
100+
auto output = tf.zeros_like(expected);
101+
102+
op_unfold_copy_out(input, /*dim=*/1, /*size=*/0, /*step=*/1, output);
103+
EXPECT_TENSOR_CLOSE(output, expected);
104+
}
105+
106+
TEST_F(OpUnfoldTest, NegativeSizeAndNegativeStepDies) {
107+
TensorFactory<ScalarType::Float> tf;
108+
const auto input = tf.make({3, 3}, {1, 2, 3, 4, 5, 6, 7, 8, 9});
109+
auto output = tf.zeros({3, 1, 2});
110+
111+
ET_EXPECT_KERNEL_FAILURE(context_, op_unfold_copy_out(input, /*dim=*/1, /*size=*/-1, /*step=*/1, output));
112+
ET_EXPECT_KERNEL_FAILURE(context_, op_unfold_copy_out(input, /*dim=*/1, /*size=*/1, /*step=*/-1, output));
113+
}

kernels/test/targets.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ def define_common_targets():
324324
_common_op_test("op_tril_test", ["aten", "portable"])
325325
_common_op_test("op_trunc_test", ["aten", "portable"])
326326
_common_op_test("op_unbind_copy_test", ["aten", "portable"])
327+
_common_op_test("op_unfold_copy_test", ["aten", "portable"])
327328
_common_op_test("op_unsqueeze_copy_test", ["aten", "portable"])
328329
_common_op_test("op_upsample_bilinear2d_test", ["aten", "portable"])
329330
_common_op_test("op_upsample_nearest2d_test", ["aten", "portable"])

shim_et/xplat/executorch/kernels/portable/op_registration_util.bzl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,12 @@ ATEN_OPS = (
12231223
"//executorch/kernels/portable/cpu/util:copy_ops_util",
12241224
],
12251225
),
1226+
op_target(
1227+
name = "op_unfold_copy",
1228+
deps = [
1229+
"//executorch/kernels/portable/cpu/util:copy_ops_util",
1230+
],
1231+
),
12261232
op_target(
12271233
name = "op_unsqueeze_copy",
12281234
deps = [

0 commit comments

Comments
 (0)