Skip to content

Commit 673d574

Browse files
authored
[ET-VK] Enable auto-generated operator correctness tests and benchmark binaries in OSS (#10260)
Summary: ## Context As title. The majority of operator correctness testing for the Vulkan delegate is done via the tests under the `op_tests/` directory, which includes generated operator correctness and benchmark tests. However, these tests are currently only able to be built and run via buck, and are only tested in the Meta internal repo. This PR sets up the CMake build for the operator tests under `op_tests/`, allowing them to be built and run in the Github repo. ## Next Steps * Run these tests in CI This work is in service of making it easier for Open Source Constributors to contribute to the ExecuTorch Vulkan Delegate. Test Plan: ## Test Plan Build and run the test binaries on a Linux machine. ### Setup ``` # Set up ExecuTorch cd ~/executorch ./install_executorch.sh # Build + Install C++ components rm -rf cmake-out && \ cmake . \ -DCMAKE_INSTALL_PREFIX=cmake-out \ -DEXECUTORCH_BUILD_KERNELS_CUSTOM=ON \ -DEXECUTORCH_BUILD_KERNELS_CUSTOM_AOT=ON \ -DEXECUTORCH_BUILD_KERNELS_OPTIMIZED=ON \ -DEXECUTORCH_BUILD_KERNELS_QUANTIZED=ON \ -DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON \ -DEXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR=ON \ -DEXECUTORCH_BUILD_EXTENSION_MODULE=ON \ -DEXECUTORCH_BUILD_EXTENSION_RUNNER_UTIL=ON \ -DEXECUTORCH_BUILD_EXTENSION_TENSOR=ON \ -DEXECUTORCH_BUILD_DEVTOOLS=ON \ -DEXECUTORCH_BUILD_VULKAN=ON \ -DEXECUTORCH_BUILD_XNNPACK=ON \ -DEXECUTORCH_BUILD_TESTS=ON \ -Bcmake-out && \ cmake --build cmake-out -j64 --target install ``` ### Build tests ``` rm -rf cmake-out/backends/vulkan/test/op_tests && \ pp cmake backends/vulkan/test/op_tests \ -DCMAKE_INSTALL_PREFIX=cmake-out \ -DPYTHON_EXECUTABLE=python \ -DTORCH_OPS_YAML_PATH=/home/ssjia/Github/pytorch/aten/src/ATen/native \ -Bcmake-out/backends/vulkan/test/op_tests && \ cmake --build cmake-out/backends/vulkan/test/op_tests -j16 ``` Note: the auto-generated operator correctness tests need the `tags.yaml` and `native_functions.yaml` file from the pytorch repo. Otherwise they won't be built. ### Run tests ``` cmake-out/backends/vulkan/test/op_tests/vulkan_sdpa_test cmake-out/backends/vulkan/test/op_tests/vulkan_rope_test cmake-out/backends/vulkan/test/op_tests/vulkan_linear_weight_int4_test cmake-out/backends/vulkan/test/op_tests/vulkan_operator_correctness_tests --gtest_filter="*add_Tensor*" ```
1 parent 4e112f9 commit 673d574

File tree

7 files changed

+195
-25
lines changed

7 files changed

+195
-25
lines changed

backends/vulkan/cmake/ShaderLibrary.cmake

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,20 @@ include(${EXECUTORCH_ROOT}/tools/cmake/Utils.cmake)
4545

4646
function(gen_vulkan_shader_lib_cpp shaders_path)
4747
set(VULKAN_SHADERGEN_ENV "")
48-
set(VULKAN_SHADERGEN_OUT_PATH ${CMAKE_BINARY_DIR}/${ARGV1})
48+
set(VULKAN_SHADERGEN_OUT_PATH ${CMAKE_BINARY_DIR}/vulkan_compute_shaders)
4949

50-
execute_process(
50+
add_custom_command(
51+
COMMENT "Generating Vulkan Compute Shaders"
52+
OUTPUT ${VULKAN_SHADERGEN_OUT_PATH}/spv.cpp
5153
COMMAND
5254
"${PYTHON_EXECUTABLE}"
5355
${EXECUTORCH_ROOT}/backends/vulkan/runtime/gen_vulkan_spv.py --glsl-path
5456
${shaders_path} --output-path ${VULKAN_SHADERGEN_OUT_PATH}
55-
--glslc-path=${GLSLC_PATH} --tmp-dir-path=${VULKAN_SHADERGEN_OUT_PATH}/shader_cache/
56-
--env ${VULKAN_GEN_ARG_ENV}
57-
RESULT_VARIABLE error_code
57+
--glslc-path=${GLSLC_PATH}
58+
--tmp-dir-path=${VULKAN_SHADERGEN_OUT_PATH}/shader_cache/ --env
59+
${VULKAN_GEN_ARG_ENV}
60+
DEPENDS ${shaders_path}/*
61+
${EXECUTORCH_ROOT}/backends/vulkan/runtime/gen_vulkan_spv.py
5862
)
5963

6064
set(generated_spv_cpp
@@ -86,13 +90,6 @@ macro(vulkan_shader_library shaders_path library_name)
8690
set(VULKAN_SHADERGEN_ENV "")
8791
set(VULKAN_SHADERGEN_OUT_PATH ${CMAKE_BINARY_DIR}/${library_name})
8892

89-
# execute_process( COMMAND "${PYTHON_EXECUTABLE}"
90-
# ${EXECUTORCH_ROOT}/backends/vulkan/runtime/gen_vulkan_spv.py --glsl-path
91-
# ${shaders_path} --output-path ${VULKAN_SHADERGEN_OUT_PATH}
92-
# --glslc-path=${GLSLC_PATH} --tmp-dir-path=${VULKAN_SHADERGEN_OUT_PATH} --env
93-
# ${VULKAN_GEN_ARG_ENV} RESULT_VARIABLE error_code ) set(ENV{PYTHONPATH}
94-
# ${PYTHONPATH})
95-
9693
set(generated_spv_cpp ${VULKAN_SHADERGEN_OUT_PATH}/spv.cpp)
9794

9895
add_library(${library_name} STATIC ${generated_spv_cpp})

backends/vulkan/test/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ if(LIB_VULKAN_BACKEND)
4646
set(VULKAN_THIRD_PARTY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../third-party)
4747

4848
set(GTEST_INCLUDE_PATH
49-
${EXECUTORCH_ROOT}/third-party/googletest/googletest/include set
50-
(PYTORCH_PATH ${EXECUTORCH_ROOT}/third-party/pytorch)
49+
${EXECUTORCH_ROOT}/third-party/googletest/googletest/include
5150
)
5251
set(VULKAN_HEADERS_PATH ${VULKAN_THIRD_PARTY_PATH}/Vulkan-Headers/include)
5352
set(VOLK_PATH ${VULKAN_THIRD_PARTY_PATH}/volk)
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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+
# ### Editing this file ###
8+
#
9+
# This file should be formatted with
10+
# ~~~
11+
# cmake-format -i CMakeLists.txt
12+
# ~~~
13+
# It should also be cmake-lint clean.
14+
#
15+
# The targets in this file will be built if EXECUTORCH_BUILD_VULKAN is ON
16+
17+
cmake_minimum_required(VERSION 3.19)
18+
project(executorch)
19+
20+
find_package(executorch CONFIG REQUIRED COMPONENTS vulkan_backend)
21+
find_package(GTest CONFIG REQUIRED)
22+
23+
if(NOT EXECUTORCH_ROOT)
24+
set(EXECUTORCH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../../..)
25+
endif()
26+
27+
# Include this file to access target_link_options_shared_lib This is required to
28+
# provide access to target_link_options_shared_lib which allows libraries to be
29+
# linked with the --whole-archive flag. This is required for libraries that
30+
# perform dynamic registration via static initialization.
31+
include(${EXECUTORCH_ROOT}/tools/cmake/Utils.cmake)
32+
33+
get_torch_base_path(TORCH_BASE_PATH)
34+
message(STATUS "torch base path: ${TORCH_BASE_PATH}")
35+
36+
# Only build tests if Vulkan was compiled
37+
find_library(LIB_VULKAN_BACKEND vulkan_backend)
38+
find_library(LIB_TORCH torch ${TORCH_BASE_PATH}/lib)
39+
find_library(LIB_TORCH_CPU torch_cpu ${TORCH_BASE_PATH}/lib)
40+
find_library(LIB_C10 c10 ${TORCH_BASE_PATH}/lib)
41+
42+
message(STATUS "Vulkan backend lib ${LIB_VULKAN_BACKEND}")
43+
message(STATUS "Torch ${LIB_TORCH}")
44+
45+
if(NOT PYTHON_EXECUTABLE)
46+
set(PYTHON_EXECUTABLE python3)
47+
endif()
48+
49+
# Third party include paths
50+
51+
set(VULKAN_THIRD_PARTY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../third-party)
52+
53+
set(GTEST_INCLUDE_PATH
54+
${EXECUTORCH_ROOT}/third-party/googletest/googletest/include
55+
)
56+
set(VULKAN_HEADERS_PATH ${VULKAN_THIRD_PARTY_PATH}/Vulkan-Headers/include)
57+
set(VOLK_PATH ${VULKAN_THIRD_PARTY_PATH}/volk)
58+
set(VMA_PATH ${VULKAN_THIRD_PARTY_PATH}/VulkanMemoryAllocator)
59+
60+
set(COMMON_INCLUDES
61+
${EXECUTORCH_ROOT}/..
62+
${VULKAN_HEADERS_PATH}
63+
${VOLK_PATH}
64+
${VMA_PATH}
65+
${GTEST_INCLUDE_PATH}
66+
${TORCH_BASE_PATH}/include
67+
${TORCH_BASE_PATH}/include/torch/csrc/api/include
68+
)
69+
70+
target_link_options_shared_lib(vulkan_backend)
71+
72+
function(vulkan_op_test test_name test_src)
73+
set(extra_deps ${ARGN})
74+
75+
add_executable(${test_name} ${test_src})
76+
target_include_directories(${test_name} PRIVATE ${COMMON_INCLUDES})
77+
target_link_libraries(
78+
${test_name}
79+
PRIVATE GTest::gtest_main
80+
vulkan_backend
81+
executorch
82+
${LIB_TORCH}
83+
${LIB_TORCH_CPU}
84+
${LIB_C10}
85+
${extra_deps}
86+
)
87+
88+
add_test(${test_name} ${test_name})
89+
endfunction()
90+
91+
if(LIB_VULKAN_BACKEND AND LIB_TORCH)
92+
find_library(
93+
CUSTOM_OPS_LIB custom_ops_aot_lib
94+
HINTS ${CMAKE_INSTALL_PREFIX}/executorch/extension/llm/custom_ops
95+
)
96+
if(CUSTOM_OPS_LIB)
97+
vulkan_op_test(
98+
vulkan_sdpa_test ${CMAKE_CURRENT_SOURCE_DIR}/sdpa_test.cpp
99+
${CUSTOM_OPS_LIB}
100+
)
101+
else()
102+
message(
103+
STATUS "Skip building sdpa_test because custom_ops_aot_lib is not found"
104+
)
105+
endif()
106+
vulkan_op_test(
107+
vulkan_rope_test ${CMAKE_CURRENT_SOURCE_DIR}/rotary_embedding_test.cpp
108+
)
109+
vulkan_op_test(
110+
vulkan_linear_weight_int4_test
111+
${CMAKE_CURRENT_SOURCE_DIR}/linear_weight_int4_test.cpp
112+
)
113+
114+
# Only build generated op tests if a path to tags.yaml and
115+
# native_functions.yaml is provided. These files are required for codegen.
116+
if(TORCH_OPS_YAML_PATH)
117+
set(GENERATED_VULKAN_TESTS_CPP_PATH ${CMAKE_CURRENT_BINARY_DIR}/vk_gen_cpp)
118+
119+
# Generated operator correctness tests
120+
121+
set(generated_test_cpp ${GENERATED_VULKAN_TESTS_CPP_PATH}/op_tests.cpp)
122+
123+
add_custom_command(
124+
COMMENT "Generating Vulkan operator correctness tests"
125+
OUTPUT ${generated_test_cpp}
126+
COMMAND
127+
${PYTHON_EXECUTABLE}
128+
${EXECUTORCH_ROOT}/backends/vulkan/test/op_tests/generate_op_correctness_tests.py
129+
-o ${GENERATED_VULKAN_TESTS_CPP_PATH} --tags-path
130+
${TORCH_OPS_YAML_PATH}/tags.yaml --aten-yaml-path
131+
${TORCH_OPS_YAML_PATH}/native_functions.yaml
132+
DEPENDS ${EXECUTORCH_ROOT}/backends/vulkan/test/op_tests/**/*.py
133+
)
134+
135+
vulkan_op_test(vulkan_op_correctness_tests ${generated_test_cpp})
136+
137+
# Generated operator benchmarks (only built in google benchmark is
138+
# installed)
139+
find_package(benchmark CONFIG)
140+
141+
if(benchmark_FOUND)
142+
set(generated_benchmark_cpp
143+
${GENERATED_VULKAN_TESTS_CPP_PATH}/op_benchmarks.cpp
144+
)
145+
146+
add_custom_command(
147+
COMMENT "Generating Vulkan operator benchmarks"
148+
OUTPUT ${generated_benchmark_cpp}
149+
COMMAND
150+
${PYTHON_EXECUTABLE}
151+
${EXECUTORCH_ROOT}/backends/vulkan/test/op_tests/generate_op_benchmarks.py
152+
-o ${GENERATED_VULKAN_TESTS_CPP_PATH} --tags-path
153+
${TORCH_OPS_YAML_PATH}/tags.yaml --aten-yaml-path
154+
${TORCH_OPS_YAML_PATH}/native_functions.yaml
155+
DEPENDS ${EXECUTORCH_ROOT}/backends/vulkan/test/op_tests/**/*.py
156+
)
157+
158+
vulkan_op_test(vulkan_op_benchmarks ${generated_benchmark_cpp})
159+
endif()
160+
else()
161+
message(
162+
STATUS
163+
"Skipping generated operator correctness tests and benchmarks. Please specify TORCH_OPS_YAML_PATH to build these tests."
164+
)
165+
endif()
166+
endif()

backends/vulkan/test/op_tests/generate_op_correctness_tests.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ def process_test_suites(
5858
def generate_cpp(
5959
native_functions_yaml_path: str, tags_path: str, output_dir: str
6060
) -> None:
61+
if not os.path.exists(output_dir):
62+
os.makedirs(output_dir)
63+
6164
output_file = os.path.join(output_dir, "op_tests.cpp")
6265
cpp_generator = VkCorrectnessTestFileGen(output_file)
6366

backends/vulkan/test/op_tests/utils/gen_benchmark_vk.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,15 @@ def generate_benchmark_fixture(self) -> str:
228228
return at::from_blob(values.data(), sizes, at::kFloat).toType(dtype).detach().clone();
229229
}}
230230
231-
at::Tensor make_index_tensor(std::vector<int64_t> indices) {{
231+
at::Tensor make_index_tensor_1d(std::vector<int64_t> indices) {{
232232
at::ScalarType dtype = at::kInt;
233233
std::vector<int64_t> sizes = {{static_cast<int64_t>(indices.size())}};
234234
235235
// Clone as original data will be deallocated upon return.
236236
return at::from_blob(indices.data(), sizes, dtype).detach().clone();
237237
}}
238238
239-
at::Tensor make_index_tensor(std::vector<std::vector<int64_t>> indices) {{
239+
at::Tensor make_index_tensor_2d(std::vector<std::vector<int64_t>> indices) {{
240240
at::ScalarType dtype = at::kInt;
241241
std::vector<int64_t> sizes = {{
242242
static_cast<int64_t>(indices.size()),
@@ -252,7 +252,7 @@ def generate_benchmark_fixture(self) -> str:
252252
return at::from_blob(acc.data(), sizes, dtype).detach().clone();
253253
}}
254254
255-
at::Tensor make_index_tensor(std::vector<std::vector<std::vector<int64_t>>> indices) {{
255+
at::Tensor make_index_tensor_3d(std::vector<std::vector<std::vector<int64_t>>> indices) {{
256256
at::ScalarType dtype = at::kInt;
257257
std::vector<int64_t> sizes = {{
258258
static_cast<int64_t>(indices.size()),

backends/vulkan/test/op_tests/utils/gen_computegraph.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,10 @@ def create_aten_fn_call(self) -> str:
229229

230230
def create_aten_method_call(self) -> str:
231231
# For functions with only Method variant, we fallback to the function
232-
# declared in MethodOperators.h. The method is declared as
233-
# at::_ops::{name}::call(*), and ATEN_FN is a handly macro.
232+
# declared in MethodOperators.h
234233
cpp_sig = gen_static_dispatch_backend_call_signature(self.f_sig, self.f)
235234
exprs = translate_args(self.f_sig, cpp_sig)
236-
func_call = f"ATEN_FN({self.f_sig.name()})({exprs});"
235+
func_call = f"at::_ops::{self.f_sig.name()}::call({exprs});"
237236
return func_call
238237

239238
def create_out_src(self, include_declarations: bool = True) -> str:

backends/vulkan/test/op_tests/utils/gen_correctness_base.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,13 @@ def create_input_data(self, arg: Argument, data: Any) -> str: # noqa: C901
170170

171171
if cpp_type == AT_TENSOR:
172172
if arg.name == "index" or arg.name == "indices":
173-
ret_str += f"make_index_tensor({init_list_str(data)});"
173+
args_str = init_list_str(data)
174+
if args_str[:3] == "{{{":
175+
ret_str += f"make_index_tensor_3d({init_list_str(data)});"
176+
elif args_str[:2] == "{{":
177+
ret_str += f"make_index_tensor_2d({init_list_str(data)});"
178+
else:
179+
ret_str += f"make_index_tensor_1d({init_list_str(data)});"
174180
else:
175181
ret_str += self.call_data_gen_fn(arg, data)
176182
elif cpp_type == OPT_AT_TENSOR:
@@ -278,7 +284,7 @@ def generate_suite_cpp(self) -> str:
278284
float high = 1.0) {{
279285
if (high == 1.0 && low == 0.0)
280286
return at::rand(sizes, at::device(at::kCPU).dtype(dtype));
281-
287+
282288
if (dtype == at::kChar)
283289
return at::randint(high, sizes, at::device(at::kCPU).dtype(dtype));
284290
@@ -307,15 +313,15 @@ def generate_suite_cpp(self) -> str:
307313
return at::from_blob(values.data(), sizes, at::kFloat).toType(dtype).detach().clone();
308314
}}
309315
310-
at::Tensor make_index_tensor(std::vector<int64_t> indices) {{
316+
at::Tensor make_index_tensor_1d(std::vector<int64_t> indices) {{
311317
at::ScalarType dtype = at::kInt;
312318
std::vector<int64_t> sizes = {{static_cast<int64_t>(indices.size())}};
313319
314320
// Clone as original data will be deallocated upon return.
315321
return at::from_blob(indices.data(), sizes, dtype).detach().clone();
316322
}}
317323
318-
at::Tensor make_index_tensor(std::vector<std::vector<int64_t>> indices) {{
324+
at::Tensor make_index_tensor_2d(std::vector<std::vector<int64_t>> indices) {{
319325
at::ScalarType dtype = at::kInt;
320326
std::vector<int64_t> sizes = {{
321327
static_cast<int64_t>(indices.size()),
@@ -331,7 +337,7 @@ def generate_suite_cpp(self) -> str:
331337
return at::from_blob(acc.data(), sizes, dtype).detach().clone();
332338
}}
333339
334-
at::Tensor make_index_tensor(std::vector<std::vector<std::vector<int64_t>>> indices) {{
340+
at::Tensor make_index_tensor_3d(std::vector<std::vector<std::vector<int64_t>>> indices) {{
335341
at::ScalarType dtype = at::kInt;
336342
std::vector<int64_t> sizes = {{
337343
static_cast<int64_t>(indices.size()),

0 commit comments

Comments
 (0)