mirror of
https://github.com/zebrajr/pytorch.git
synced 2025-12-07 12:21:27 +01:00
Summary: Pull Request resolved: https://github.com/pytorch/pytorch/pull/45890 This rewrite is as per my comments at https://github.com/pytorch/pytorch/pull/44087#issuecomment-701664506 I did the rewrite by reverting #44087 and then reimplementing it on top. You may find it easier to review by diffing against master with only #44087 reverted. There are two main ideas. First, we now factor cpp argument processing into two phases operating on three representations of data: 1. `FunctionSchema` - this is the source from native_functions.yaml 2. `Union[Argument, ThisArgument, TensorOptionsArgument]` - this is the arguments after doing some basic semantic analysis to group them (for TensorOptions) or identify the this argument (if this is a method). There is only ever one of these per functions. 3. `Union[CppArgument, CppThisArgument, CppTensorOptionsArgument]` - this is the arguments after we've elaborated them to C++. There may be multiple of these per actual C++ signature. You can think of (2) as common processing, whereas (3) bakes in specific assumptions about whether or not you have a faithful or non-faithful signature. Second, we now have CppSignature and CppSignatureGroup representing the *total* public C++ API signature. So those dataclasses are what know how to render definitions/declarations, and you no longer have to manually type it out in the Functions/TensorMethods codegen. Here is an exhaustive accounting of the changes. tools.codegen.api.types - CppSignature and CppSignatureGroup got moved to tools.codegen.api.types - Add new CppThisArgument and CppTensorOptionsArguments (modeled off of ThisArgument and TensorOptionsArguments) so that we can retain high level semantic structure even after elaborating terms with C++ API information. Once this is done, we can refine CppArgument.argument to no longer contain a ThisArgument (ThisArgument is always translated to CppThisArgument. Note that this doesn't apply to TensorOptionsArguments, as those may be expanded or not expanded, and so you could get a single CppArgument for 'options') - Add no_default() functional mutator to easily remove default arguments from CppArgument and friends - Add an explicit_arguments() method to CppArgument and friends to extract (flat) argument list that must be explicitly written in the signature. This is everything except (Cpp)ThisArgument, and is also convenient when you don't care about the extra structure of CppTensorOptionsArguments tools.codegen.api.cpp - group_arguments is back, and it doesn't send things directly to a CppSignatureGroup; instead, it moves us from representation (1) to (2) (perhaps it should live in model). Here I changed my mind from my PR comment; I discovered it was not necessary to do classification at grouping time, and it was simpler and easier to do it later. - argument got split into argument_not_this/argument/argument_faithful. argument and argument_faithful are obvious enough what they do, and I needed argument_not_this as a more refined version of argument so that I could get the types to work out on TensorOptionsArguments tools.codegen.api.dispatcher - Here we start seeing the payoff. The old version of this code had a "scatter" mode and a "gather" mode. We don't need that anymore: cppargument_exprs is 100% type-directed via the passed in cpp arguments. I am able to write the functions without any reference to use_c10_dispatcher tools.codegen.gen - Instead of having exprs_str and types_str functions, I moved these to live directly on CppSignature, since it seemed pretty logical. - The actual codegen for TensorMethods/Functions is greatly simplified, since (1) all of the heavy lifting is now happening in CppSignature(Group) construction, and (2) I don't need to proxy one way or another, the new dispatcher translation code is able to handle both cases no problem. There is a little faffing about with ordering to reduce the old and new diff which could be removed afterwards. Here are codegen diffs. For use_c10_dispatcher: full: ``` +// aten::_cudnn_init_dropout_state(float dropout, bool train, int dropout_seed, *, ScalarType? dtype=None, Layout? layout=None, Device? device=None, bool? pin_memory=False) -> Tensor Tensor _cudnn_init_dropout_state(double dropout, bool train, int64_t dropout_seed, const TensorOptions & options) { - return _cudnn_init_dropout_state(dropout, train, dropout_seed, optTypeMetaToScalarType(options.dtype_opt()), options.layout_opt(), options.device_opt(), options.pinned_memory_opt()); + static auto op = c10::Dispatcher::singleton() + .findSchemaOrThrow("aten::_cudnn_init_dropout_state", "") + .typed<Tensor (double, bool, int64_t, c10::optional<ScalarType>, c10::optional<Layout>, c10::optional<Device>, c10::optional<bool>)>(); + return op.call(dropout, train, dropout_seed, optTypeMetaToScalarType(options.dtype_opt()), options.layout_opt(), options.device_opt(), options.pinned_memory_opt()); } ``` Otherwise: ``` +// aten::empty_meta(int[] size, *, ScalarType? dtype=None, Layout? layout=None, Device? device=None, bool? pin_memory=None, MemoryFormat? memory_format=None) -> Tensor Tensor empty_meta(IntArrayRef size, c10::optional<ScalarType> dtype, c10::optional<Layout> layout, c10::optional<Device> device, c10::optional<bool> pin_memory, c10::optional<MemoryFormat> memory_format) { - return empty_meta(size, TensorOptions().dtype(dtype).layout(layout).device(device).pinned_memory(pin_memory), memory_format); + static auto op = c10::Dispatcher::singleton() + .findSchemaOrThrow("aten::empty_meta", "") + .typed<Tensor (IntArrayRef, const TensorOptions &, c10::optional<MemoryFormat>)>(); + return op.call(size, TensorOptions().dtype(dtype).layout(layout).device(device).pinned_memory(pin_memory), memory_format); } ``` Things that I probably did not get right: - The Union[Argument, TensorOptionsArguments, ThisArgument] and the Cpp variants are starting to get a little unwieldy. Not sure if this means I should add a supertype (or at the very least an alias); in some cases I do purposely omit one of these from the Union - Code may not necessarily live in the most logical files. There isn't very much rhyme or reason to it. - The fields on CppSignature. They're not very well constrained and it will be better if people don't use them directly. - Disambiguation. We should do this properly in #44087 and we don't need special logic for deleting defaulting for faithful signatures; there is a more general story here. Signed-off-by: Edward Z. Yang <ezyang@fb.com> Test Plan: Imported from OSS Reviewed By: smessmer Differential Revision: D24144035 Pulled By: ezyang fbshipit-source-id: a185f8bf9df8b44ca5718a7a44dac23cefd11c0a
160 lines
6.8 KiB
Python
160 lines
6.8 KiB
Python
from tools.codegen.model import *
|
|
|
|
from tools.codegen.api.types import *
|
|
import tools.codegen.api.cpp as cpp
|
|
import tools.codegen.api.legacy_dispatcher as legacy_dispatcher
|
|
import tools.codegen.local as local
|
|
|
|
import itertools
|
|
from typing import Sequence, Optional
|
|
|
|
# This file describes the translation of JIT schema to the dispatcher
|
|
# API, the *unboxed* calling convention by which invocations through
|
|
# the dispatcher are made. Historically, the dispatcher API matched
|
|
# the C++ API, but with the establishment of the boxed API, we've
|
|
# made changes to the dispatcher API to so that the unboxed API
|
|
# better aligns with the boxed API. The dispatcher API hooks heavily
|
|
# into our template based boxing/unboxing machinery, so changes
|
|
# to this convention will usually need template updates too.
|
|
#
|
|
# Prominent characteristics of the dispatcher API:
|
|
#
|
|
# - 'use_c10_dispatcher: full' controls whether or not we actually
|
|
# use the modern calling convention or not. When use_c10_dispatcher
|
|
# is not enabled, we don't use the template machinery.
|
|
#
|
|
# - dtype, layout, device and pin_memory are represented as separate
|
|
# arguments.
|
|
#
|
|
|
|
def argumenttype_type(t: Type, *, mutable: bool) -> str:
|
|
if local.use_c10_dispatcher().dispatcher_uses_new_style():
|
|
# This is a faux amis. If it makes sense in the future to add
|
|
# more special cases here, or invert things so cpp.argument_type
|
|
# calls this, or just completely inline the function, please do
|
|
# it.
|
|
return cpp.argumenttype_type(t, mutable=mutable)
|
|
else:
|
|
# This is real sharing. If you're modifying this path, ask
|
|
# yourself why you are changing the legacy dispatcher protocol
|
|
# here and not in legacy_dispatcher.
|
|
return legacy_dispatcher.argumenttype_type(t, mutable=mutable)
|
|
|
|
def argument_type(a: Argument) -> str:
|
|
return argumenttype_type(a.type, mutable=a.is_write)
|
|
|
|
def returns_type(rs: Sequence[Return]) -> str:
|
|
# At present, there is no difference. But there could be!
|
|
return cpp.returns_type(rs)
|
|
|
|
def argument(a: Argument) -> DispatcherArgument:
|
|
if local.use_c10_dispatcher().dispatcher_uses_new_style():
|
|
return DispatcherArgument(
|
|
type=argument_type(a),
|
|
name=a.name,
|
|
argument=a,
|
|
)
|
|
else:
|
|
la = legacy_dispatcher.argument(a)
|
|
return DispatcherArgument(
|
|
type=la.type,
|
|
name=la.name,
|
|
argument=la.argument,
|
|
)
|
|
|
|
def name(func: FunctionSchema) -> str:
|
|
return cpp.name(func)
|
|
|
|
def arguments(func: FunctionSchema) -> Sequence[DispatcherArgument]:
|
|
if local.use_c10_dispatcher().dispatcher_uses_new_style():
|
|
return list(map(argument, itertools.chain(func.out_arguments, func.arguments, func.kwarg_only_arguments)))
|
|
else:
|
|
return [
|
|
DispatcherArgument(type=la.type, name=la.name, argument=la.argument)
|
|
for la in legacy_dispatcher.arguments(func)
|
|
]
|
|
|
|
# Given a set of CppArguments in scope, return a sequence of dispatcher
|
|
# expressions that translate the cpp API into dispatcher API
|
|
#
|
|
# WARNING: This is unsound if you pass it CppArgument when you were
|
|
# supposed to pass it CppTensorOptionsArguments, it will directly
|
|
# translate device to device, which will give you the wrong signature
|
|
# for dispatcher. If Argument "knew" that it was part of a
|
|
# TensorOptions that would help us dynamically test for this case
|
|
def cppargument_exprs(
|
|
a: CppArgumentPack,
|
|
*, tensor_options: Optional[CppArgument]
|
|
) -> Sequence[DispatcherExpr]:
|
|
if isinstance(a, CppSingleArgumentPack):
|
|
if isinstance(a.this.argument, TensorOptionsArguments):
|
|
if local.use_c10_dispatcher().dispatcher_uses_new_style():
|
|
# Scatter
|
|
ta = a.this.argument
|
|
name = a.this.name
|
|
return [
|
|
DispatcherExpr(type=argument_type(ta.dtype), expr=f'optTypeMetaToScalarType({name}.dtype_opt())'),
|
|
DispatcherExpr(type=argument_type(ta.layout), expr=f'{name}.layout_opt()'),
|
|
DispatcherExpr(type=argument_type(ta.device), expr=f'{name}.device_opt()'),
|
|
DispatcherExpr(type=argument_type(ta.pin_memory), expr=f'{name}.pinned_memory_opt()'), # weird discrep
|
|
]
|
|
else:
|
|
# No-op
|
|
return [DispatcherExpr(type='const TensorOptions &', expr=a.this.name)]
|
|
elif isinstance(a.this.argument, Argument):
|
|
if a.this.name == 'memory_format' and \
|
|
tensor_options is not None and \
|
|
local.use_c10_dispatcher().dispatcher_uses_new_style():
|
|
return [DispatcherExpr(
|
|
type=argument_type(a.this.argument),
|
|
expr=f'c10::impl::check_tensor_options_and_extract_memory_format({tensor_options.name}, {a.this.name})')
|
|
]
|
|
else:
|
|
return [DispatcherExpr(type=argument_type(a.this.argument), expr=a.this.name)]
|
|
else:
|
|
assert_never(a.this.argument)
|
|
elif isinstance(a, CppTensorOptionsArgumentPack):
|
|
if local.use_c10_dispatcher().dispatcher_uses_new_style():
|
|
# No-op
|
|
return [
|
|
expr
|
|
for sub_a in a.explicit_arguments() # NB: don't really care about explicitness here
|
|
for expr in cppargument_exprs(CppSingleArgumentPack(sub_a), tensor_options=tensor_options)
|
|
]
|
|
else:
|
|
# Gather
|
|
return [DispatcherExpr(
|
|
type='const TensorOptions &',
|
|
expr=f'TensorOptions().dtype({a.dtype.name}).layout({a.layout.name})'
|
|
f'.device({a.device.name}).pinned_memory({a.pin_memory.name})',
|
|
)]
|
|
elif isinstance(a, CppThisArgumentPack):
|
|
return [DispatcherExpr(
|
|
type=a.type,
|
|
expr='const_cast<Tensor&>(*this)',
|
|
)]
|
|
else:
|
|
assert_never(a)
|
|
|
|
def cpparguments_exprs(args: Sequence[CppArgumentPack]) -> Sequence[DispatcherExpr]:
|
|
tensor_options = next(
|
|
(a.this for a in args if isinstance(a, CppSingleArgumentPack) and
|
|
isinstance(a.this.argument, TensorOptionsArguments)),
|
|
None
|
|
)
|
|
return [r for a in args for r in cppargument_exprs(a, tensor_options=tensor_options)]
|
|
|
|
# I don't think this is entirely sound, but it should be reasonably
|
|
# close
|
|
def legacydispatcherarguments_exprs(args: Sequence[LegacyDispatcherArgument]) -> Sequence[DispatcherExpr]:
|
|
return cpparguments_exprs([
|
|
CppSingleArgumentPack(CppArgument(type=a.type, name=a.name, default=None, argument=a.argument))
|
|
for a in args
|
|
])
|
|
|
|
def exprs(args: Sequence[DispatcherArgument]) -> Sequence[DispatcherExpr]:
|
|
return cpparguments_exprs([
|
|
CppSingleArgumentPack(CppArgument(type=a.type, name=a.name, default=None, argument=a.argument))
|
|
for a in args
|
|
])
|