Skip to content

Commit e4f6748

Browse files
committed
[ET-VK][ops] Add buffer unfold support
Implement `aten.unfold_copy.default` for Vulkan buffer tensors, including high-rank indexing and dynamic resize support. Add focused coverage for general, high-rank, and SceneX audio shapes. Authored with Codex. Differential Revision: [D114382687](https://our.internmc.facebook.com/intern/diff/D114382687/) ghstack-source-id: 409188517 Pull-Request: pytorch#21521
1 parent 4dab143 commit e4f6748

5 files changed

Lines changed: 239 additions & 0 deletions

File tree

‎backends/vulkan/op_registry.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ def register_copy_op():
226226
exir_ops.edge.aten.tanh.default,
227227
exir_ops.edge.aten.round.default,
228228
exir_ops.edge.aten.leaky_relu.default,
229+
exir_ops.edge.aten.log10.default,
229230
]
230231
)
231232
def register_unaryop_cpp_ops():
@@ -1271,6 +1272,21 @@ def register_alias_copy():
12711272
)
12721273

12731274

1275+
# =============================================================================
1276+
# Unfold.cpp
1277+
# =============================================================================
1278+
1279+
1280+
@update_features(exir_ops.edge.aten.unfold_copy.default)
1281+
def register_unfold_copy():
1282+
return OpFeatures(
1283+
inputs_storage=utils.ANY_BUFFER,
1284+
inputs_dtypes=utils.FP_T,
1285+
supports_resize=True,
1286+
supports_highdim=True,
1287+
)
1288+
1289+
12741290
# =============================================================================
12751291
# Gather.cpp
12761292
# =============================================================================
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
#version 450 core
10+
11+
${define_required_extensions("buffer", DTYPE)}
12+
13+
#define PRECISION ${PRECISION}
14+
#define T ${buffer_scalar_type(DTYPE)}
15+
16+
${define_active_storage_type("buffer")}
17+
18+
layout(std430) buffer;
19+
20+
#include "indexing.glslh"
21+
22+
${layout_declare_tensor(B, "w", "t_out", DTYPE, "buffer")}
23+
${layout_declare_tensor(B, "r", "t_in", DTYPE, "buffer")}
24+
25+
${layout_declare_ubo(B, "BufferMetadata", "out_meta")}
26+
${layout_declare_ubo(B, "BufferMetadata", "in_meta")}
27+
28+
${layout_declare_spec_const(C, "int", "unfold_dim", "0")}
29+
${layout_declare_spec_const(C, "int", "step", "1")}
30+
31+
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
32+
33+
void main() {
34+
const uint out_bufi = gl_GlobalInvocationID.x;
35+
if (out_of_bounds(out_bufi, out_meta)) {
36+
return;
37+
}
38+
39+
const TensorIndex out_tidx = linear_idx_to_tensor_idx(out_meta, out_bufi);
40+
TensorIndex in_tidx;
41+
initialize(in_tidx);
42+
43+
for (int d = 0; d < int_ndim(in_meta); ++d) {
44+
in_tidx.data[div_4(d)][mod_4(d)] = idx_at(out_tidx, d + 1);
45+
}
46+
in_tidx.data[div_4(unfold_dim)][mod_4(unfold_dim)] =
47+
idx_at(out_tidx, unfold_dim + 1) * step + idx_at(out_tidx, 0);
48+
49+
const uint in_bufi = tensor_idx_to_linear_idx(in_meta, in_tidx);
50+
t_out[out_bufi] = t_in[in_bufi];
51+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
unfold_copy_buffer:
8+
parameter_names_with_default_values:
9+
DTYPE: float
10+
generate_variant_forall:
11+
DTYPE:
12+
- VALUE: half
13+
- VALUE: float
14+
shader_variants:
15+
- NAME: unfold_copy_buffer
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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/backends/vulkan/runtime/graph/ops/OperatorRegistry.h>
10+
11+
#include <executorch/backends/vulkan/runtime/graph/ops/impl/Common.h>
12+
#include <executorch/backends/vulkan/runtime/graph/ops/utils/ShaderNameUtils.h>
13+
14+
namespace vkcompute {
15+
16+
int64_t normalize_unfold_dim(const int64_t dim, const int64_t ndim) {
17+
const int64_t normalized_dim = dim < 0 ? dim + ndim : dim;
18+
VK_CHECK_COND(
19+
normalized_dim >= 0 && normalized_dim < ndim,
20+
"unfold_copy: dimension must be in range [-input.dim(), input.dim())");
21+
return normalized_dim;
22+
}
23+
24+
std::vector<int64_t> get_unfold_sizes(
25+
ComputeGraph& graph,
26+
const ValueRef input,
27+
const ValueRef dim_ref,
28+
const ValueRef size_ref,
29+
const ValueRef step_ref) {
30+
const std::vector<int64_t> input_sizes = graph.sizes_of(input);
31+
VK_CHECK_COND(!input_sizes.empty(), "unfold_copy: input must have rank >= 1");
32+
33+
const int64_t dim = normalize_unfold_dim(
34+
graph.extract_scalar<int64_t>(dim_ref), input_sizes.size());
35+
const int64_t size = graph.extract_scalar<int64_t>(size_ref);
36+
const int64_t step = graph.extract_scalar<int64_t>(step_ref);
37+
38+
VK_CHECK_COND(size > 0, "unfold_copy: size must be greater than zero");
39+
VK_CHECK_COND(step > 0, "unfold_copy: step must be greater than zero");
40+
VK_CHECK_COND(
41+
size <= input_sizes.at(dim),
42+
"unfold_copy: size must not exceed selected input dimension");
43+
44+
std::vector<int64_t> output_sizes = input_sizes;
45+
output_sizes.at(dim) = (input_sizes.at(dim) - size) / step + 1;
46+
output_sizes.push_back(size);
47+
return output_sizes;
48+
}
49+
50+
void resize_unfold_copy_node(
51+
ComputeGraph* graph,
52+
const std::vector<ArgGroup>& args,
53+
const std::vector<ValueRef>& resize_args) {
54+
const ValueRef output = args.at(0).refs.at(0);
55+
const ValueRef input = args.at(1).refs.at(0);
56+
graph->virtual_resize(
57+
output,
58+
get_unfold_sizes(
59+
*graph,
60+
input,
61+
resize_args.at(0),
62+
resize_args.at(1),
63+
resize_args.at(2)));
64+
}
65+
66+
void add_unfold_copy_node(
67+
ComputeGraph& graph,
68+
const ValueRef input,
69+
const ValueRef dim_ref,
70+
const ValueRef size_ref,
71+
const ValueRef step_ref,
72+
const ValueRef output) {
73+
const int64_t input_ndim = graph.dim_of(input);
74+
const int64_t dim =
75+
normalize_unfold_dim(graph.extract_scalar<int64_t>(dim_ref), input_ndim);
76+
const int64_t dim_whcn = input_ndim - dim - 1;
77+
const int64_t step = graph.extract_scalar<int64_t>(step_ref);
78+
79+
get_unfold_sizes(graph, input, dim_ref, size_ref, step_ref);
80+
81+
std::string kernel_name = "unfold_copy_buffer";
82+
add_dtype_suffix(kernel_name, graph.dtype_of(output));
83+
84+
graph.execute_nodes().emplace_back(new DynamicDispatchNode(
85+
graph,
86+
VK_KERNEL_FROM_STR(kernel_name),
87+
default_pick_global_wg_size,
88+
default_pick_local_wg_size,
89+
{{output, vkapi::kWrite}, {input, vkapi::kRead}},
90+
{graph.meta_ubo(output), graph.meta_ubo(input)},
91+
{},
92+
{
93+
utils::safe_downcast<int32_t>(dim_whcn),
94+
utils::safe_downcast<int32_t>(step),
95+
},
96+
{dim_ref, size_ref, step_ref},
97+
resize_unfold_copy_node));
98+
}
99+
100+
void unfold_copy(ComputeGraph& graph, const std::vector<ValueRef>& args) {
101+
add_unfold_copy_node(
102+
graph, args.at(0), args.at(1), args.at(2), args.at(3), args.at(4));
103+
}
104+
105+
REGISTER_OPERATORS {
106+
VK_REGISTER_OP(aten.unfold_copy.default, unfold_copy);
107+
}
108+
109+
} // namespace vkcompute

‎backends/vulkan/test/op_tests/cases.py‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,54 @@ def get_unary_ops_inputs():
18341834
return test_suite
18351835

18361836

1837+
@register_test_suite("aten.unfold_copy.default")
1838+
def get_unfold_copy_inputs():
1839+
Test = namedtuple("UnfoldCopy", ["self", "dimension", "size", "step"])
1840+
1841+
test_suite = VkTestSuite(
1842+
[
1843+
Test(self=(11,), dimension=0, size=5, step=2),
1844+
Test(self=(3, 11), dimension=-1, size=5, step=2),
1845+
Test(self=(3, 7, 11), dimension=1, size=3, step=2),
1846+
Test(self=(5, 3, 7, 11), dimension=0, size=3, step=2),
1847+
]
1848+
)
1849+
test_suite.storage_types = ["utils::kBuffer"]
1850+
test_suite.layouts = [
1851+
"utils::kWidthPacked",
1852+
"utils::kChannelsPacked",
1853+
]
1854+
test_suite.data_gen = "make_seq_tensor"
1855+
test_suite.test_name_suffix = "buffer"
1856+
1857+
highdim_test_suite = VkTestSuite(
1858+
[
1859+
Test(
1860+
self=(2, 2, 2, 3, 2, 2, 2),
1861+
dimension=-4,
1862+
size=2,
1863+
step=1,
1864+
),
1865+
]
1866+
)
1867+
highdim_test_suite.storage_types = ["utils::kBuffer"]
1868+
highdim_test_suite.layouts = ["utils::kChannelsPacked"]
1869+
highdim_test_suite.data_gen = "make_seq_tensor"
1870+
highdim_test_suite.test_name_suffix = "highdim_buffer"
1871+
1872+
scenex_test_suite = VkTestSuite(
1873+
[
1874+
Test(self=(30, 15760), dimension=-1, size=400, step=160),
1875+
]
1876+
)
1877+
scenex_test_suite.storage_types = ["utils::kBuffer"]
1878+
scenex_test_suite.layouts = ["utils::kWidthPacked"]
1879+
scenex_test_suite.dtypes = ["at::kFloat"]
1880+
scenex_test_suite.test_name_suffix = "scenex_buffer"
1881+
1882+
return [test_suite, highdim_test_suite, scenex_test_suite]
1883+
1884+
18371885
# separate test suite from unary_ops for learning purposes
18381886
@register_test_suite("aten.tan.default")
18391887
def get_tan_inputs():

0 commit comments

Comments
 (0)