Skip to content

Commit 9d45191

Browse files
committed
Add _clone_dim_order runtime test
1 parent a4f98ac commit 9d45191

File tree

3 files changed

+381
-0
lines changed

3 files changed

+381
-0
lines changed

kernels/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ add_custom_target(
108108
set(all_test_sources
109109
"BinaryLogicalOpTest.cpp"
110110
"op__to_dim_order_copy_test.cpp"
111+
"op__clone_dim_order_test.cpp"
111112
"op_abs_test.cpp"
112113
"op_acos_test.cpp"
113114
"op_acosh_test.cpp"
Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
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 <cstdint>
10+
#include <map>
11+
#include <typeindex>
12+
#include <variant>
13+
14+
#include <executorch/kernels/test/FunctionHeaderWrapper.h> // Declares the operator.
15+
#include <executorch/kernels/test/TestUtil.h>
16+
#include <executorch/kernels/test/supported_features.h>
17+
#include <executorch/runtime/core/exec_aten/exec_aten.h>
18+
#include <executorch/runtime/core/exec_aten/testing_util/tensor_factory.h>
19+
#include <executorch/runtime/core/exec_aten/testing_util/tensor_util.h>
20+
21+
#include <gtest/gtest.h>
22+
23+
using namespace ::testing;
24+
using executorch::aten::ArrayRef;
25+
using executorch::aten::ScalarType;
26+
using executorch::aten::Tensor;
27+
using std::optional;
28+
using torch::executor::testing::TensorFactory;
29+
30+
class OpDimOrderCloneTest : public OperatorTest {
31+
protected:
32+
Tensor& op__clone_dim_order_out(
33+
const Tensor& self,
34+
bool non_blocking,
35+
std::optional<ArrayRef<int64_t>> dim_order,
36+
Tensor& out) {
37+
return torch::executor::dim_order_ops::_clone_dim_order_outf(
38+
context_, self, non_blocking, dim_order, out);
39+
}
40+
41+
template <typename INPUT_CTYPE, typename OUTPUT_CTYPE>
42+
std::vector<OUTPUT_CTYPE> vector_type_cast(std::vector<INPUT_CTYPE> input) {
43+
std::vector<OUTPUT_CTYPE> output(input.size());
44+
std::transform(
45+
input.begin(), input.end(), output.begin(), [](INPUT_CTYPE x) {
46+
return static_cast<OUTPUT_CTYPE>(x);
47+
});
48+
return output;
49+
}
50+
51+
template <typename INPUT_CTYPE, typename OUTPUT_CTYPE>
52+
struct ToTestCase {
53+
const std::vector<int32_t> sizes;
54+
const std::vector<INPUT_CTYPE> data_in;
55+
const std::vector<OUTPUT_CTYPE> data_out;
56+
};
57+
58+
template <typename CTYPE, ScalarType DTYPE>
59+
void test_runner_clone(std::vector<ToTestCase<double, double>> test_cases) {
60+
TensorFactory<DTYPE> tf_in;
61+
TensorFactory<DTYPE> tf_out;
62+
63+
for (const auto& test_case : test_cases) {
64+
auto data_in = vector_type_cast<double, CTYPE>(test_case.data_in);
65+
66+
Tensor input = tf_in.make(test_case.sizes, data_in);
67+
Tensor output = tf_out.zeros_like(input);
68+
69+
std::vector<int64_t> dim_order_vec;
70+
for (int64_t i = 0; i < input.dim(); i++) {
71+
dim_order_vec.push_back(i);
72+
}
73+
ArrayRef<int64_t> dim_order(dim_order_vec.data(), dim_order_vec.size());
74+
75+
Tensor ret = op__clone_dim_order_out(
76+
/*self=*/input,
77+
/*non_blocking=*/false,
78+
dim_order,
79+
output);
80+
81+
Tensor expected = tf_out.make(test_case.sizes, data_in);
82+
83+
// Verifies that the returned and output tensor from _clone_dim_order both
84+
// match the original input (expected).
85+
EXPECT_TENSOR_EQ(ret, output);
86+
EXPECT_TENSOR_EQ(ret, expected);
87+
}
88+
}
89+
90+
/* %python
91+
import torch
92+
torch.manual_seed(0)
93+
x = torch.rand(2, 3)
94+
res = x.clone(memory_format = torch.preserve_format)
95+
op = "op__clone_dim_order_out"
96+
opt_setup_params = """
97+
bool non_blocking = false;
98+
optional<MemoryFormat> memory_format;
99+
"""
100+
opt_extra_params = "non_blocking, memory_format,"
101+
out_args = "out_shape, dynamism"
102+
dtype = "ScalarType::Float"
103+
check = "EXPECT_TENSOR_EQ" */
104+
105+
// Helper for testing dynamic shape outputs.
106+
void test_dynamic_shape(
107+
const std::vector<int32_t>& out_shape,
108+
enum torch::executor::TensorShapeDynamism dynamism) {
109+
/* %python
110+
%rewrite(unary_op) */
111+
112+
TensorFactory<ScalarType::Float> tf;
113+
114+
Tensor x = tf.make(
115+
{2, 3},
116+
{0.49625658988952637,
117+
0.7682217955589294,
118+
0.08847743272781372,
119+
0.13203048706054688,
120+
0.30742281675338745,
121+
0.6340786814689636});
122+
Tensor expected = tf.make(
123+
{2, 3},
124+
{0.49625658988952637,
125+
0.7682217955589294,
126+
0.08847743272781372,
127+
0.13203048706054688,
128+
0.30742281675338745,
129+
0.6340786814689636});
130+
131+
bool non_blocking = false;
132+
133+
Tensor out = tf.zeros(out_shape, dynamism);
134+
135+
std::vector<int64_t> dim_order_vec;
136+
for (int64_t i = 0; i < x.dim(); i++) {
137+
dim_order_vec.push_back(i);
138+
}
139+
ArrayRef<int64_t> dim_order(dim_order_vec.data(), dim_order_vec.size());
140+
141+
Tensor ret = op__clone_dim_order_out(
142+
/*self=*/x, non_blocking, dim_order, out);
143+
144+
EXPECT_TENSOR_EQ(out, expected);
145+
EXPECT_TENSOR_EQ(ret, expected);
146+
}
147+
};
148+
149+
// Clones tensors of all real dtypes.
150+
TEST_F(OpDimOrderCloneTest, AllDtypesSupported) {
151+
std::vector<ToTestCase<double, double>> test_cases = {
152+
{
153+
/*sizes=*/{2, 4},
154+
/*data_in=*/{2.11, 3.2, 2.3, 4.0, 1.1, 5.2, 1.1, 6.3},
155+
/*data_out=*/{}, // data_out shouldn't be used in test_runner_clone
156+
},
157+
{
158+
/*sizes=*/{3, 4, 0, 5},
159+
/*data_in=*/{},
160+
/*data_out=*/{},
161+
},
162+
{
163+
/*sizes=*/{},
164+
/*data_in=*/{10.0},
165+
/*data_out=*/{}, // data_out shouldn't be used in test_runner_clone
166+
},
167+
};
168+
169+
#define TEST_KERNEL(CTYPE, DTYPE) \
170+
test_runner_clone<CTYPE, ScalarType::DTYPE>(test_cases);
171+
172+
ET_FORALL_REAL_TYPES(TEST_KERNEL);
173+
174+
#undef TEST_KERNEL
175+
}
176+
177+
// Cloning with mismatched input and output tensor shapes should fail.
178+
TEST_F(OpDimOrderCloneTest, MismatchedSizesDie) {
179+
if (torch::executor::testing::SupportedFeatures::get()->is_aten) {
180+
GTEST_SKIP() << "Skipping: ATen kernel supports mismatched sizes.";
181+
}
182+
TensorFactory<ScalarType::Int> tf;
183+
Tensor input = tf.make(/*sizes=*/{3, 1, 1, 2}, /*data=*/{1, 2, 3, 4, 5, 6});
184+
Tensor out = tf.zeros({3, 2, 1, 1});
185+
std::vector<int64_t> dim_order_vec;
186+
for (int64_t i = 0; i < input.dim(); i++) {
187+
dim_order_vec.push_back(i);
188+
}
189+
ArrayRef<int64_t> dim_order(dim_order_vec.data(), dim_order_vec.size());
190+
191+
ET_EXPECT_KERNEL_FAILURE(
192+
context_,
193+
op__clone_dim_order_out(
194+
/*self=*/input,
195+
/*non_blocking=*/false,
196+
dim_order,
197+
out));
198+
}
199+
200+
// Cloning with a non-contiguous memory format should fail.
201+
TEST_F(OpDimOrderCloneTest, MismatchedMemoryFormatDies) {
202+
if (torch::executor::testing::SupportedFeatures::get()->is_aten) {
203+
GTEST_SKIP()
204+
<< "Skipping: ATen kernel supports non-contiguous memory formats.";
205+
}
206+
TensorFactory<ScalarType::Float> tf_in;
207+
TensorFactory<ScalarType::Float> tf_out;
208+
Tensor input =
209+
tf_in.make(/*sizes=*/{3, 1, 1, 2}, /*data=*/{1, 2, 3, 4, 5, 6});
210+
Tensor out = tf_out.zeros({3, 1, 1, 2});
211+
212+
std::vector<int64_t> dim_order_vec;
213+
for (int64_t i = 0; i < input.dim(); i++) {
214+
dim_order_vec.push_back(i);
215+
}
216+
217+
// Mutate dim_order_vec to create an illegal dim_order.
218+
dim_order_vec[1] = 3;
219+
dim_order_vec[3] = 1;
220+
ArrayRef<int64_t> dim_order(dim_order_vec.data(), dim_order_vec.size());
221+
222+
ET_EXPECT_KERNEL_FAILURE(
223+
context_,
224+
op__clone_dim_order_out(
225+
/*self=*/input,
226+
/*non_blocking=*/false,
227+
dim_order,
228+
out));
229+
}
230+
231+
// Cloning with non‑blocking=true should fail because portable kernels only
232+
// support blocking.
233+
TEST_F(OpDimOrderCloneTest, MismatchedBlockingDie) {
234+
if (torch::executor::testing::SupportedFeatures::get()->is_aten) {
235+
GTEST_SKIP()
236+
<< "Skipping: ATen kernel supports non-blocking data transfer.";
237+
}
238+
TensorFactory<ScalarType::Int> tf;
239+
Tensor input = tf.make(/*sizes=*/{3, 1, 1, 2}, /*data=*/{1, 2, 3, 4, 5, 6});
240+
Tensor out = tf.zeros(/*sizes=*/{3, 1, 1, 2});
241+
242+
std::vector<int64_t> dim_order_vec;
243+
for (int64_t i = 0; i < input.dim(); i++) {
244+
dim_order_vec.push_back(i);
245+
}
246+
ArrayRef<int64_t> dim_order(dim_order_vec.data(), dim_order_vec.size());
247+
248+
ET_EXPECT_KERNEL_FAILURE(
249+
context_,
250+
op__clone_dim_order_out(
251+
/*self=*/input,
252+
/*non_blocking=*/true,
253+
dim_order,
254+
out));
255+
}
256+
257+
TEST_F(OpDimOrderCloneTest, DynamicShapeUpperBoundSameAsExpected) {
258+
test_dynamic_shape(
259+
{2, 3}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
260+
}
261+
262+
TEST_F(OpDimOrderCloneTest, DynamicShapeUpperBoundLargerThanExpected) {
263+
test_dynamic_shape(
264+
{10, 10}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
265+
}
266+
267+
TEST_F(OpDimOrderCloneTest, DynamicShapeUnbound) {
268+
if (!torch::executor::testing::SupportedFeatures::get()->output_resize) {
269+
GTEST_SKIP() << "Dynamic shape unbound not supported.";
270+
}
271+
test_dynamic_shape(
272+
{1, 1}, torch::executor::TensorShapeDynamism::DYNAMIC_UNBOUND);
273+
}
274+
275+
TEST_F(OpDimOrderCloneTest, ContiguousToChannelsLast) {
276+
TensorFactory<ScalarType::Float> tf;
277+
278+
Tensor x = tf.make_with_dimorder(
279+
{3, 5, 2, 2},
280+
{0.2432, 0.5248, 0.5361, 0.8513, 0.8184, 0.8206, 0.7357, 0.9655, 0.6138,
281+
0.1112, 0.2799, 0.1079, 0.9680, 0.2548, 0.0393, 0.6002, 0.2257, 0.8766,
282+
0.2715, 0.1595, 0.2029, 0.7026, 0.6982, 0.8529, 0.4405, 0.6560, 0.9217,
283+
0.6372, 0.2446, 0.6590, 0.3866, 0.7185, 0.4439, 0.5346, 0.3179, 0.4492,
284+
0.3491, 0.6970, 0.8456, 0.2516, 0.2345, 0.2924, 0.7695, 0.0911, 0.8530,
285+
0.8560, 0.6909, 0.7719, 0.8923, 0.5546, 0.6978, 0.8151, 0.3007, 0.3961,
286+
0.8416, 0.4296, 0.7203, 0.8963, 0.3597, 0.5552});
287+
288+
Tensor out = tf.full_channels_last({3, 5, 2, 2}, 0.0);
289+
Tensor expected = tf.make_with_dimorder(
290+
{3, 5, 2, 2},
291+
{0.2432, 0.8184, 0.6138, 0.9680, 0.2257, 0.5248, 0.8206, 0.1112, 0.2548,
292+
0.8766, 0.5361, 0.7357, 0.2799, 0.0393, 0.2715, 0.8513, 0.9655, 0.1079,
293+
0.6002, 0.1595, 0.2029, 0.4405, 0.2446, 0.4439, 0.3491, 0.7026, 0.6560,
294+
0.6590, 0.5346, 0.6970, 0.6982, 0.9217, 0.3866, 0.3179, 0.8456, 0.8529,
295+
0.6372, 0.7185, 0.4492, 0.2516, 0.2345, 0.8530, 0.8923, 0.3007, 0.7203,
296+
0.2924, 0.8560, 0.5546, 0.3961, 0.8963, 0.7695, 0.6909, 0.6978, 0.8416,
297+
0.3597, 0.0911, 0.7719, 0.8151, 0.4296, 0.5552},
298+
/*dim_order=*/{0, 2, 3, 1});
299+
300+
std::vector<int64_t> dim_order_vec = {0, 2, 3, 1};
301+
executorch::aten::ArrayRef<int64_t> dim_order(
302+
dim_order_vec.data(), dim_order_vec.size());
303+
Tensor ret = op__clone_dim_order_out(
304+
/*self*/ x, /*non_blocking*/ false, /*dim_order*/ dim_order, out);
305+
306+
EXPECT_TENSOR_EQ(out, expected);
307+
EXPECT_TENSOR_EQ(ret, expected);
308+
}
309+
310+
TEST_F(OpDimOrderCloneTest, ChannelsLastToContiguous) {
311+
TensorFactory<ScalarType::Float> tf;
312+
313+
Tensor out = tf.full({3, 5, 2, 2}, 0.0);
314+
Tensor x = tf.make_with_dimorder(
315+
{3, 5, 2, 2},
316+
{0.2432, 0.8184, 0.6138, 0.9680, 0.2257, 0.5248, 0.8206, 0.1112, 0.2548,
317+
0.8766, 0.5361, 0.7357, 0.2799, 0.0393, 0.2715, 0.8513, 0.9655, 0.1079,
318+
0.6002, 0.1595, 0.2029, 0.4405, 0.2446, 0.4439, 0.3491, 0.7026, 0.6560,
319+
0.6590, 0.5346, 0.6970, 0.6982, 0.9217, 0.3866, 0.3179, 0.8456, 0.8529,
320+
0.6372, 0.7185, 0.4492, 0.2516, 0.2345, 0.8530, 0.8923, 0.3007, 0.7203,
321+
0.2924, 0.8560, 0.5546, 0.3961, 0.8963, 0.7695, 0.6909, 0.6978, 0.8416,
322+
0.3597, 0.0911, 0.7719, 0.8151, 0.4296, 0.5552},
323+
/*dim_order=*/{0, 2, 3, 1});
324+
325+
Tensor expected = tf.make_with_dimorder(
326+
{3, 5, 2, 2},
327+
{0.2432, 0.5248, 0.5361, 0.8513, 0.8184, 0.8206, 0.7357, 0.9655, 0.6138,
328+
0.1112, 0.2799, 0.1079, 0.9680, 0.2548, 0.0393, 0.6002, 0.2257, 0.8766,
329+
0.2715, 0.1595, 0.2029, 0.7026, 0.6982, 0.8529, 0.4405, 0.6560, 0.9217,
330+
0.6372, 0.2446, 0.6590, 0.3866, 0.7185, 0.4439, 0.5346, 0.3179, 0.4492,
331+
0.3491, 0.6970, 0.8456, 0.2516, 0.2345, 0.2924, 0.7695, 0.0911, 0.8530,
332+
0.8560, 0.6909, 0.7719, 0.8923, 0.5546, 0.6978, 0.8151, 0.3007, 0.3961,
333+
0.8416, 0.4296, 0.7203, 0.8963, 0.3597, 0.5552});
334+
335+
std::vector<int64_t> dim_order_vec = {0, 1, 2, 3};
336+
executorch::aten::ArrayRef<int64_t> dim_order(
337+
dim_order_vec.data(), dim_order_vec.size());
338+
Tensor ret = op__clone_dim_order_out(
339+
/*self*/ x, /*non_blocking*/ false, /*dim_order*/ dim_order, out);
340+
341+
EXPECT_TENSOR_EQ(out, expected);
342+
EXPECT_TENSOR_EQ(ret, expected);
343+
}
344+
345+
TEST_F(OpDimOrderCloneTest, PreserveChannelsLast) {
346+
TensorFactory<ScalarType::Float> tf;
347+
348+
Tensor out = tf.full_channels_last({3, 5, 2, 2}, 0.0);
349+
Tensor x = tf.make_with_dimorder(
350+
{3, 5, 2, 2},
351+
{0.2432, 0.8184, 0.6138, 0.9680, 0.2257, 0.5248, 0.8206, 0.1112, 0.2548,
352+
0.8766, 0.5361, 0.7357, 0.2799, 0.0393, 0.2715, 0.8513, 0.9655, 0.1079,
353+
0.6002, 0.1595, 0.2029, 0.4405, 0.2446, 0.4439, 0.3491, 0.7026, 0.6560,
354+
0.6590, 0.5346, 0.6970, 0.6982, 0.9217, 0.3866, 0.3179, 0.8456, 0.8529,
355+
0.6372, 0.7185, 0.4492, 0.2516, 0.2345, 0.8530, 0.8923, 0.3007, 0.7203,
356+
0.2924, 0.8560, 0.5546, 0.3961, 0.8963, 0.7695, 0.6909, 0.6978, 0.8416,
357+
0.3597, 0.0911, 0.7719, 0.8151, 0.4296, 0.5552},
358+
/*dim_order=*/{0, 2, 3, 1});
359+
360+
Tensor expected = tf.make_with_dimorder(
361+
{3, 5, 2, 2},
362+
{0.2432, 0.8184, 0.6138, 0.9680, 0.2257, 0.5248, 0.8206, 0.1112, 0.2548,
363+
0.8766, 0.5361, 0.7357, 0.2799, 0.0393, 0.2715, 0.8513, 0.9655, 0.1079,
364+
0.6002, 0.1595, 0.2029, 0.4405, 0.2446, 0.4439, 0.3491, 0.7026, 0.6560,
365+
0.6590, 0.5346, 0.6970, 0.6982, 0.9217, 0.3866, 0.3179, 0.8456, 0.8529,
366+
0.6372, 0.7185, 0.4492, 0.2516, 0.2345, 0.8530, 0.8923, 0.3007, 0.7203,
367+
0.2924, 0.8560, 0.5546, 0.3961, 0.8963, 0.7695, 0.6909, 0.6978, 0.8416,
368+
0.3597, 0.0911, 0.7719, 0.8151, 0.4296, 0.5552},
369+
/*dim_order=*/{0, 2, 3, 1});
370+
371+
Tensor ret = op__clone_dim_order_out(
372+
/*self*/ x,
373+
/*non_blocking*/ false,
374+
/*dim_order*/ executorch::aten::nullopt,
375+
out);
376+
377+
EXPECT_TENSOR_EQ(out, expected);
378+
EXPECT_TENSOR_EQ(ret, expected);
379+
}

kernels/test/targets.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ def define_common_targets():
177177

178178
_common_op_test("op__to_dim_order_copy_test", ["aten", "portable"])
179179
_common_op_test("op__empty_dim_order_test", ["aten", "portable"])
180+
_common_op_test("op__clone_dim_order_test", ["portable"])
180181
_common_op_test("op_abs_test", ["aten", "portable"])
181182
_common_op_test("op_acos_test", ["aten", "portable"])
182183
_common_op_test("op_acosh_test", ["aten", "portable"])

0 commit comments

Comments
 (0)