mirror of
https://github.com/pytorch/pytorch.git
synced 2025-10-20 21:14:14 +08:00
Summary: Pull Request resolved: https://github.com/pytorch/pytorch/pull/74069 RFC: pytorch/rfcs#40 In #69881 we added the ability to generate codegen unboxing source files. Notice that the generated code to register an operator looks like this: ``` // aten::add.Tensor(Tensor self, Tensor other, *, Scalar alpha=1) -> Tensor OperatorGenerator( TORCH_SELECTIVE_SCHEMA("aten::add.Tensor(Tensor self, Tensor other, *, Scalar alpha=1) -> Tensor"), [](Stack & stack) { RECORD_FUNCTION("add", std::vector<c10::IValue>()); at::unboxing::add_Tensor(stack); }, aliasAnalysisFromSchema() ), ``` However, this means we have to parse the schema and get back arguments with default values in static init time. As written in the RFC, there's a more performant option: providing these arguments with default values using codegen, then we don't have to do expensive regex pattern matching in parsing. Here's how it looks like: ``` // aten::add.Tensor(Tensor self, Tensor other, *, Scalar alpha=1) -> Tensor OperatorGenerator( "aten::add", "Tensor", { c10::Argument("self", nullptr, c10::nullopt, c10::IValue(c10::nullopt)), c10::Argument("other", nullptr, c10::nullopt, c10::IValue(c10::nullopt)), c10::Argument("alpha", nullptr, c10::nullopt, c10::IValue(1)) }, { c10::Argument("") }, [](Stack & stack) { RECORD_FUNCTION("add", std::vector<c10::IValue>()); at::unboxing::add_Tensor(stack); }, aliasAnalysisFromSchema() ), ``` We also added corresponding APIs in `operator.h` to take in the arguments. Test Plan: Rely on CI Reviewed By: kimishpatel Differential Revision: D33077733 fbshipit-source-id: e7f13a2f162c70d4e506b4f64cdbb7afec39f4e6 (cherry picked from commit 08a076935f480d5ab11c75e78d8627a8e2ba7d1a)
208 lines
7.1 KiB
Python
208 lines
7.1 KiB
Python
# Generates RegisterCodegenUnboxedKernels.cpp, UnboxingFunctions.h and UnboxingFunctions.cpp.
|
|
import argparse
|
|
import os
|
|
import pathlib
|
|
from dataclasses import dataclass
|
|
from tools.codegen.api import unboxing
|
|
from tools.codegen.api.translate import translate
|
|
from tools.codegen.api.types import CppSignatureGroup
|
|
from tools.codegen.api.unboxing import convert_arguments
|
|
from tools.codegen.context import method_with_native_function
|
|
from tools.codegen.gen import parse_native_yaml, cpp_string
|
|
from tools.codegen.model import NativeFunction, NativeFunctionsGroup, Variant
|
|
from tools.codegen.utils import Target, FileManager, mapMaybe, make_file_manager
|
|
from typing import Union, Sequence
|
|
from typing_extensions import Literal
|
|
|
|
|
|
# Generates UnboxingFunctions.h & UnboxingFunctions.cpp.
|
|
@dataclass(frozen=True)
|
|
class ComputeUnboxingFunctions:
|
|
target: Union[Literal[Target.DECLARATION], Literal[Target.DEFINITION]]
|
|
|
|
@method_with_native_function
|
|
def __call__(self, f: NativeFunction) -> str:
|
|
|
|
if self.target is Target.DECLARATION:
|
|
# Note [The ATen Codegen Unboxing API]
|
|
# Similar to the ATen Operators API, ATen Codegen Unboxing API lives in the at::unboxing namespace, and
|
|
# will be used by codegen unboxing wrappers (CodegenUnboxingWrappers.cpp).
|
|
# The Wrappers will be registered into torch::jit::OperatorRegistry using RegisterOperators API.
|
|
#
|
|
# Important characteristics about the Codegen Unboxing API:
|
|
# (1) It follows the OperatorRegistry API.
|
|
# This is kind of necessary to avoid overhead.
|
|
# For example: if it followed the C++ API, then all of the faithful C++ factory functions
|
|
# would need to wrap their arguments into TensorOptions only to unwrap them again.
|
|
# (2) Under the hood it calls C++ API.
|
|
return f"""
|
|
// aten::{f.func}
|
|
TORCH_API void {f.func.name.unambiguous_name()}(Stack & stack);
|
|
"""
|
|
else:
|
|
sig_group = CppSignatureGroup.from_native_function(
|
|
f, method=(Variant.method in f.variants)
|
|
)
|
|
sig = sig_group.most_faithful_signature()
|
|
# parse arguments into C++ code
|
|
binding_list, code_list = convert_arguments(f)
|
|
|
|
# for each C++ argument, generate the conversion code
|
|
code_connector = "\n\t"
|
|
arg_connector = ", "
|
|
# function call and push back to stack
|
|
prefix = "self_base." if sig.method else "at::"
|
|
translated_args = translate(binding_list, sig.arguments(), method=sig.method)
|
|
args_str = f"{arg_connector.join(e.expr for e in translated_args)}"
|
|
if len(f.func.returns) == 0:
|
|
ret_str = ""
|
|
push_str = ""
|
|
else:
|
|
ret_str = "auto result_ = "
|
|
push_str = """
|
|
pack(stack, std::move(result_));
|
|
"""
|
|
return f"""
|
|
// aten::{f.func}
|
|
TORCH_API void {f.func.name.unambiguous_name()}(Stack & stack) {{
|
|
{code_connector.join(code_list)}
|
|
|
|
drop(stack, {len(binding_list)});
|
|
|
|
{ret_str}{prefix}{sig.name()}({args_str});
|
|
{push_str}
|
|
}}
|
|
"""
|
|
|
|
|
|
# Generates RegisterCodegenUnboxedKernels.cpp.
|
|
@dataclass(frozen=True)
|
|
class ComputeCodegenUnboxedKernels:
|
|
@method_with_native_function
|
|
def __call__(self, f: NativeFunction) -> str:
|
|
# We unconditionally generate function wrappers,
|
|
sig_group = CppSignatureGroup.from_native_function(
|
|
f, method=False
|
|
)
|
|
|
|
sig = sig_group.most_faithful_signature()
|
|
|
|
# escape double quote in schema, get rid of extra double quotes
|
|
schema = cpp_string(str(sig.func))[1:-1]
|
|
|
|
# arguments
|
|
args = sig.arguments()
|
|
connector = ",\n\t\t"
|
|
args_code = []
|
|
for arg in args:
|
|
if not arg.default:
|
|
arg_cpp = "c10::IValue(c10::nullopt)"
|
|
elif arg.default.startswith('{'):
|
|
arg_cpp = f"c10::IntArrayRef({arg.default})"
|
|
else:
|
|
arg_cpp = f"c10::IValue({arg.default})"
|
|
args_code.append(f"""c10::Argument("{arg.name}", nullptr, c10::nullopt, {arg_cpp})""")
|
|
|
|
returns = f.func.returns
|
|
returns_code = []
|
|
for ret in returns:
|
|
returns_code.append(f"""c10::Argument("{ret.name if ret.name else ""}")""")
|
|
return f"""
|
|
// aten::{schema}
|
|
OperatorGenerator(
|
|
"aten::{f.func.name.name}",
|
|
"{f.func.name.overload_name}",
|
|
{{
|
|
{connector.join(args_code)}
|
|
}},
|
|
{{
|
|
{connector.join(returns_code)}
|
|
}},
|
|
[](Stack & stack) {{
|
|
RECORD_FUNCTION("{sig.name()}", std::vector<c10::IValue>());
|
|
at::unboxing::{unboxing.name(f)}(stack);
|
|
}},
|
|
aliasAnalysisFromSchema()
|
|
),
|
|
"""
|
|
|
|
|
|
def gen_unboxing(
|
|
*,
|
|
native_functions: Sequence[NativeFunction],
|
|
cpu_fm: FileManager,
|
|
) -> None:
|
|
def key_func(fn: Union[NativeFunction, NativeFunctionsGroup]) -> str:
|
|
return fn.root_name
|
|
|
|
cpu_fm.write_sharded(
|
|
"UnboxingFunctions.cpp",
|
|
native_functions,
|
|
key_fn=key_func,
|
|
env_callable=lambda fn: {
|
|
"definitions": [ComputeUnboxingFunctions(Target.DEFINITION)(fn)]
|
|
},
|
|
num_shards=5,
|
|
sharded_keys={"definitions"},
|
|
)
|
|
cpu_fm.write(
|
|
"UnboxingFunctions.h",
|
|
lambda: {
|
|
"declarations": list(
|
|
mapMaybe(ComputeUnboxingFunctions(Target.DECLARATION), native_functions)
|
|
),
|
|
},
|
|
)
|
|
cpu_fm.write_sharded(
|
|
"RegisterCodegenUnboxedKernels.cpp",
|
|
native_functions,
|
|
key_fn=key_func,
|
|
env_callable=lambda fn: {"unboxed_ops": [ComputeCodegenUnboxedKernels()(fn)]},
|
|
num_shards=10,
|
|
sharded_keys={"unboxed_ops"},
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Generate unboxing source files")
|
|
parser.add_argument(
|
|
"-s",
|
|
"--source-path",
|
|
help="path to source directory for ATen",
|
|
default="aten/src/ATen",
|
|
)
|
|
parser.add_argument(
|
|
"-d", "--install_dir", help="output directory", default="build/aten/src/ATen"
|
|
)
|
|
parser.add_argument(
|
|
'-o',
|
|
'--output-dependencies',
|
|
help='output a list of dependencies into the given file and exit')
|
|
parser.add_argument(
|
|
'--dry-run', action='store_true',
|
|
help='run without writing any files (still updates outputs)')
|
|
|
|
options = parser.parse_args()
|
|
|
|
native_yaml_path = os.path.join(options.source_path, "native/native_functions.yaml")
|
|
parsed_yaml = parse_native_yaml(native_yaml_path)
|
|
native_functions, backend_indices = (
|
|
parsed_yaml.native_functions,
|
|
parsed_yaml.backend_indices,
|
|
)
|
|
|
|
cpu_fm = make_file_manager(options=options)
|
|
gen_unboxing(native_functions=native_functions, cpu_fm=cpu_fm)
|
|
|
|
if options.output_dependencies:
|
|
depfile_path = pathlib.Path(options.output_dependencies).resolve()
|
|
depfile_name = depfile_path.name
|
|
depfile_stem = depfile_path.stem
|
|
|
|
path = depfile_path.parent / depfile_name
|
|
cpu_fm.write_outputs(depfile_stem, str(path))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|