Skip to content

Commit acdb002

Browse files
committed
Add in-process entry points to gen_all_oplist and codegen.gen
gen_oplist, gen_selected_prim_ops and gen_max_kernel_num each have a function their main() calls, but gen_all_oplist and codegen.gen do all their work in main(), so a Python caller has to start a subprocess or fake a command line to use them. Their work moves into gen_all_oplist.gen_all_oplist() and codegen.gen.generate(), and main() keeps only the command-line handling: argument parsing, the @-prefixed list file, the BUCK checks and gen_all_oplist's single-yaml mode, which still leaves debug_info alone. generate() takes main()'s flags as keyword arguments (outputs is --generate; the unused --static-dispatch-backend is left out) and builds its FileManager directly instead of through make_file_manager(options). Its tags.yaml and native_functions.yaml default to the copies packaged with torchgen, the ones tools/cmake/Codegen.cmake passes, so callers need not know where torchgen keeps them; --tags-path and --aten-yaml-path default the same way. The command lines behave as before: a selective build of two models plus an op list makes byte-identical sources with the old and new tools. The ESP-IDF component's gen_kernels.py calls both functions in the next change. Authored with Claude Code.
1 parent b2d0f19 commit acdb002

4 files changed

Lines changed: 306 additions & 157 deletions

File tree

‎codegen/gen.py‎

Lines changed: 119 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from pathlib import Path
1313
from typing import Any, Callable, TextIO, TYPE_CHECKING
1414

15+
import torchgen
1516
import yaml
1617

1718
try:
@@ -69,13 +70,7 @@
6970
OperatorName,
7071
Variant,
7172
)
72-
from torchgen.utils import (
73-
context,
74-
FileManager,
75-
make_file_manager,
76-
mapMaybe,
77-
NamespaceHelper,
78-
)
73+
from torchgen.utils import context, FileManager, mapMaybe, NamespaceHelper
7974

8075

8176
if TYPE_CHECKING:
@@ -871,11 +866,110 @@ def function_filter(f: NativeFunction) -> bool:
871866
return combined_yaml, custom_ops_parsed_yaml
872867

873868

869+
def generate(
870+
*,
871+
source_path: str,
872+
install_dir: str,
873+
tags_path: str | None = None,
874+
aten_yaml_path: str | None = None,
875+
functions_yaml_path: str | None = None,
876+
custom_ops_yaml_path: str | None = None,
877+
op_selection_yaml_path: str | None = None,
878+
op_registration_whitelist: list[str] | None = None,
879+
use_aten_lib: bool = False,
880+
manual_registration: bool = False,
881+
outputs: Sequence[str] = ("headers", "sources"),
882+
add_exception_boundary: bool = False,
883+
rocm: bool = False,
884+
dry_run: bool = False,
885+
output_dependencies: str | None = None,
886+
) -> None:
887+
"""Generates the operator headers and registration sources into install_dir.
888+
889+
The arguments match main()'s command-line flags; source_path is the
890+
directory holding the templates/ subdirectory, and outputs is --generate.
891+
tags_path and aten_yaml_path default to the copies of tags.yaml and
892+
native_functions.yaml packaged with torchgen. torchgen caches each parsed
893+
yaml by path for the life of the process, so don't rewrite aten_yaml_path
894+
or custom_ops_yaml_path in place between calls.
895+
"""
896+
packaged = os.path.join(os.path.dirname(torchgen.__file__), "packaged/ATen/native")
897+
tags_path = tags_path or os.path.join(packaged, "tags.yaml")
898+
aten_yaml_path = aten_yaml_path or os.path.join(packaged, "native_functions.yaml")
899+
900+
selector = get_custom_build_selector(
901+
op_registration_whitelist,
902+
op_selection_yaml_path,
903+
)
904+
905+
parsed_yaml, custom_ops_parsed_yaml = parse_yaml_files(
906+
aten_yaml_path=aten_yaml_path,
907+
tags_yaml_path=tags_path,
908+
native_yaml_path=functions_yaml_path,
909+
custom_ops_yaml_path=custom_ops_yaml_path,
910+
selector=selector,
911+
use_aten_lib=use_aten_lib,
912+
)
913+
native_functions, kernel_index = (
914+
parsed_yaml.native_functions,
915+
parsed_yaml.kernel_index,
916+
)
917+
custom_ops_native_functions = (
918+
custom_ops_parsed_yaml.native_functions if custom_ops_parsed_yaml else []
919+
)
920+
921+
cpu_fm = FileManager(
922+
install_dir=install_dir,
923+
template_dir=os.path.join(source_path, "templates"),
924+
dry_run=dry_run,
925+
)
926+
927+
if "headers" in outputs:
928+
# generate CustomOpsNativeFunctions.h when custom_ops.yaml is present, to match the build system.
929+
gen_headers(
930+
native_functions=native_functions,
931+
gen_custom_ops_header=bool(custom_ops_yaml_path),
932+
custom_ops_native_functions=custom_ops_native_functions,
933+
selector=selector,
934+
kernel_index=kernel_index,
935+
cpu_fm=cpu_fm,
936+
use_aten_lib=use_aten_lib,
937+
)
938+
939+
if "sources" in outputs:
940+
gen_unboxing(
941+
native_functions=native_functions,
942+
cpu_fm=cpu_fm,
943+
selector=selector,
944+
use_aten_lib=use_aten_lib,
945+
kernel_index=kernel_index,
946+
manual_registration=manual_registration,
947+
add_exception_boundary=add_exception_boundary,
948+
)
949+
if custom_ops_native_functions:
950+
gen_custom_ops(
951+
native_functions=custom_ops_native_functions,
952+
selector=selector,
953+
kernel_index=kernel_index,
954+
cpu_fm=cpu_fm,
955+
rocm=rocm,
956+
)
957+
958+
if output_dependencies:
959+
depfile_path = Path(output_dependencies).resolve()
960+
depfile_name = depfile_path.name
961+
depfile_stem = depfile_path.stem
962+
963+
for fm, prefix in [
964+
(cpu_fm, ""),
965+
]:
966+
varname = prefix + depfile_stem
967+
path = depfile_path.parent / (prefix + depfile_name)
968+
fm.write_outputs(varname, str(path))
969+
970+
874971
def main() -> None:
875972
parser = argparse.ArgumentParser(description="Generate operator source files")
876-
# Although we don't refer to --source-path directly, make_file_manager()
877-
# expects it to point to a directory that contains a templates/ subdirectory
878-
# containing the file templates.
879973
parser.add_argument(
880974
"-s",
881975
"--source-path",
@@ -898,9 +992,8 @@ def main() -> None:
898992
parser.add_argument(
899993
"--aten-yaml-path",
900994
"--aten_yaml_path",
901-
help="path to native_functions.yaml file.",
995+
help="path to native_functions.yaml file (default: torchgen's packaged copy).",
902996
)
903-
# Note that make_file_manager() also looks at --install-dir.
904997
parser.add_argument(
905998
"-d",
906999
"--install-dir",
@@ -913,8 +1006,6 @@ def main() -> None:
9131006
"--output-dependencies",
9141007
help="output a list of dependencies into the given file and exit",
9151008
)
916-
# Although we don't refer to --dry-run directly, make_file_manager() looks
917-
# for it.
9181009
parser.add_argument(
9191010
"--dry-run",
9201011
action="store_true",
@@ -945,7 +1036,7 @@ def main() -> None:
9451036
)
9461037
parser.add_argument(
9471038
"--tags-path",
948-
help="Path to tags.yaml. Required by yaml parsing in codegen system.",
1039+
help="Path to tags.yaml (default: torchgen's packaged copy).",
9491040
)
9501041
parser.add_argument(
9511042
"--rocm",
@@ -982,73 +1073,23 @@ def main() -> None:
9821073
"convert exceptions to clean failures.",
9831074
)
9841075
options = parser.parse_args()
985-
assert options.tags_path, "tags.yaml is required by codegen yaml parsing."
986-
987-
selector = get_custom_build_selector(
988-
options.op_registration_whitelist,
989-
options.op_selection_yaml_path,
990-
)
991-
992-
parsed_yaml, custom_ops_parsed_yaml = parse_yaml_files(
1076+
generate(
1077+
source_path=options.source_path,
1078+
install_dir=options.install_dir,
1079+
tags_path=options.tags_path,
9931080
aten_yaml_path=options.aten_yaml_path,
994-
tags_yaml_path=options.tags_path,
995-
native_yaml_path=options.functions_yaml_path,
1081+
functions_yaml_path=options.functions_yaml_path,
9961082
custom_ops_yaml_path=options.custom_ops_yaml_path,
997-
selector=selector,
1083+
op_selection_yaml_path=options.op_selection_yaml_path,
1084+
op_registration_whitelist=options.op_registration_whitelist,
9981085
use_aten_lib=options.use_aten_lib,
1086+
manual_registration=options.manual_registration,
1087+
outputs=options.generate,
1088+
add_exception_boundary=options.add_exception_boundary,
1089+
rocm=options.rocm,
1090+
dry_run=options.dry_run,
1091+
output_dependencies=options.output_dependencies,
9991092
)
1000-
native_functions, kernel_index = (
1001-
parsed_yaml.native_functions,
1002-
parsed_yaml.kernel_index,
1003-
)
1004-
custom_ops_native_functions = (
1005-
custom_ops_parsed_yaml.native_functions if custom_ops_parsed_yaml else []
1006-
)
1007-
1008-
cpu_fm = make_file_manager(options=options)
1009-
1010-
if "headers" in options.generate:
1011-
# generate CustomOpsNativeFunctions.h when custom_ops.yaml is present, to match the build system.
1012-
gen_headers(
1013-
native_functions=native_functions,
1014-
gen_custom_ops_header=options.custom_ops_yaml_path,
1015-
custom_ops_native_functions=custom_ops_native_functions,
1016-
selector=selector,
1017-
kernel_index=kernel_index,
1018-
cpu_fm=cpu_fm,
1019-
use_aten_lib=options.use_aten_lib,
1020-
)
1021-
1022-
if "sources" in options.generate:
1023-
gen_unboxing(
1024-
native_functions=native_functions,
1025-
cpu_fm=cpu_fm,
1026-
selector=selector,
1027-
use_aten_lib=options.use_aten_lib,
1028-
kernel_index=kernel_index,
1029-
manual_registration=options.manual_registration,
1030-
add_exception_boundary=options.add_exception_boundary,
1031-
)
1032-
if custom_ops_native_functions:
1033-
gen_custom_ops(
1034-
native_functions=custom_ops_native_functions,
1035-
selector=selector,
1036-
kernel_index=kernel_index,
1037-
cpu_fm=cpu_fm,
1038-
rocm=options.rocm,
1039-
)
1040-
1041-
if options.output_dependencies:
1042-
depfile_path = Path(options.output_dependencies).resolve()
1043-
depfile_name = depfile_path.name
1044-
depfile_stem = depfile_path.stem
1045-
1046-
for fm, prefix in [
1047-
(cpu_fm, ""),
1048-
]:
1049-
varname = prefix + depfile_stem
1050-
path = depfile_path.parent / (prefix + depfile_name)
1051-
fm.write_outputs(varname, str(path))
10521093

10531094

10541095
if __name__ == "__main__":

‎codegen/test/test_executorch_gen.py‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
ComputeCodegenUnboxedKernels,
2222
gen_functions_declarations,
2323
gen_unboxing,
24+
generate,
2425
parse_yaml_files,
2526
translate_native_yaml,
2627
)
@@ -231,6 +232,57 @@ def test_parse_yaml_files(self) -> None:
231232

232233
self.assertTrue(len(expected_kernel_entry) == 0)
233234

235+
def _generate_add_registration(self, **yaml_paths: str) -> str:
236+
os.mkdir(os.path.join(self.temp_dir, "templates"))
237+
with open(
238+
os.path.join(
239+
self.temp_dir, "templates", "RegisterCodegenUnboxedKernels.cpp"
240+
),
241+
"w",
242+
) as f:
243+
f.write("${unboxed_kernels}\n")
244+
selection_yaml_path = os.path.join(self.temp_dir, "selected_operators.yaml")
245+
with open(selection_yaml_path, "w") as f:
246+
f.write(
247+
"""
248+
et_kernel_metadata:
249+
aten::add.out:
250+
- default
251+
operators:
252+
aten::add.out:
253+
include_all_overloads: false
254+
is_root_operator: true
255+
is_used_for_training: true
256+
"""
257+
)
258+
install_dir = os.path.join(self.temp_dir, "out")
259+
260+
generate(
261+
source_path=self.temp_dir,
262+
install_dir=install_dir,
263+
functions_yaml_path=self.ops_yaml_path,
264+
op_selection_yaml_path=selection_yaml_path,
265+
outputs=["sources"],
266+
**yaml_paths,
267+
)
268+
269+
with open(
270+
os.path.join(install_dir, "RegisterCodegenUnboxedKernelsEverything.cpp")
271+
) as f:
272+
return f.read()
273+
274+
def test_generate_registers_only_selected_ops(self) -> None:
275+
registration = self._generate_add_registration(
276+
tags_path=self.tags_yaml_path, aten_yaml_path=self.aten_yaml_path
277+
)
278+
self.assertIn("add_out_kernel", registration)
279+
self.assertNotIn("mul_out_kernel", registration)
280+
281+
def test_generate_defaults_to_torchgen_packaged_yaml(self) -> None:
282+
registration = self._generate_add_registration()
283+
self.assertIn("add_out_kernel", registration)
284+
self.assertNotIn("mul_out_kernel", registration)
285+
234286
def tearDown(self) -> None:
235287
import shutil
236288

0 commit comments

Comments
 (0)