blob: ba7a8a84a69458de7601b93c6905fedfd9079d70 [file] [log] [blame]
import sys
import yaml
project_root = sys.argv[1]
sys.path.append(project_root + "/third_party/aten/src/ATen")
from code_template import CodeTemplate as CT
try:
# use faster C loader if available
from yaml import CLoader as Loader
except ImportError:
from yaml import Loader
OP_TEMPLATE = CT.from_file(project_root+'/caffe2/contrib/aten/aten_op_template.h')
def write(filename, s):
with open(filename, "w") as f:
f.write(s)
def read(filename):
with open(filename, "r") as f:
return f.read()
decls = yaml.load(read('aten/src/ATen/ATen/Declarations.yaml'), Loader=Loader)
top_env = {
'mappings': [],
'implementations': [],
}
def is_tensor_type(t):
return "Tensor" in t
def value_is_tensor_type(v):
return is_tensor_type(v['dynamic_type'])
# for each aten type, how do we handle a return value of that type?
RETURN_MAP = {
'Tensor': 'assignTo(Output(${offset}),${output});',
'Scalar': 'assignTo(Output(${offset}),*inferred_type, ${output});',
'bool': 'assignToValue<int64_t>(Output(${offset}),${output});',
'int64_t': 'assignToValue<int64_t>(Output(${offset}),${output});',
}
# for each non-Tensor aten argument, how to we read it from caffe2's
# attribute list. Most of these call runtime functions defined in the
# template class.
ARGUMENT_MAP = {
'Scalar': 'at::Scalar ${arg} = readScalarAttribute("${arg}");',
'bool': 'bool ${arg} = readAttribute<int64_t>("${arg}");',
'int': 'int ${arg} = readAttribute<int64_t>("${arg}");',
'int64_t': 'int64_t ${arg} = readAttribute<int64_t>("${arg}");',
'IntList': 'auto ${arg} = readIntList("${arg}");',
}
# filter the list of declarations removing things we cannot support
def supports(o):
# skip all in-place operators for now since aten cannot Resize
# caffe2 memory inside an operator
if o['inplace']:
return False
# _out variants also work in-place on arguments taken as destinations
# we also cannot handle these because aten cannot resize caffe2 Tensors
if "_out" in o['name']:
return False
# skip return types we cannot handle
for ret in o['returns']:
if not value_is_tensor_type(ret) and ret['type'] not in RETURN_MAP:
print("Skipping {} Because of Ret: {} ({})".format(o['name'], ret['type'], ret['dynamic_type']))
return False
# skip arguments we cannot handle
for arg in o['arguments']:
if not value_is_tensor_type(arg) and arg['type'] not in ARGUMENT_MAP:
print("Skipping {} Because of Arg: {} ({}) ".format(o['name'], arg['type'], arg['dynamic_type']))
return False
return True
filtered = [o for o in decls if supports(o)]
# template for each potential operator.
# each operator has an integer 'key' associated with it, and
# a lambda that defines the operator
# non-tensor attributes are created in ${initialization}
# and then saved as arguments to the lambda
# Inputs/Outputs are read inside the lambda
OPTION_TEMPLATE = CT("""\
case ${key}: { // ${name}
${initialization}
run_op = [=] {
${statements}
auto the_result = ${invocation};
${assignments}
return true;
};
} break;
""")
def get_output(o, i):
if len(o['returns']) == 1:
return 'the_result'
else:
return 'std::get<{}>(the_result)'.format(i)
def attribute_names(o):
return sorted([a['name'] for a in o['arguments'] if not value_is_tensor_type(a)])
def self_as_first_argument(arguments):
return ([a for a in arguments if a['name'] == 'self'] +
[a for a in arguments if a['name'] != 'self'])
seen = set()
key = 0
for o in filtered:
# [DESCRIPTORS]
# each option is associated with a descriptor string that is used
# to figure out which version of an op is being used:
# The format is:
# opname-num_inputs-attribute_1-attribute2
# Example:
# lerp-2-weight
# the operator lerp takes 2 arguments and has the attribute weight
attr_names = attribute_names(o)
num_inputs = len(o['arguments']) - len(attr_names)
descriptor = '-'.join([o['name'], str(num_inputs)] + attr_names)
if descriptor in seen:
continue
seen.add(descriptor)
# map from descriptor string to the integer key in the switch statements
# that initializes the operators
top_env['mappings'].append('{{ "{}", {} }},'.format(descriptor, key))
env = {
'name': o['name'],
'statements': [],
'arguments': [],
'assignments': [],
'initialization': [],
'key': str(key),
}
defined_inferred_type = False
if 'Tensor' in o['method_of']:
# make sure 'self' is the first argument. currently Declarations.yaml
# does not always do this. Instead it keeps the argument list the same order
# as the Type method.
o['arguments'] = self_as_first_argument(o['arguments'])
elif 'namespace' not in o['method_of']:
# methods on type like 'ones' or 'zeros' always take a
# string attribute that is translated into the at::Type object
# e.g. "Float" is at::kFloat
assert('Type' in o['method_of'])
defined_inferred_type = True
env['initialization'].append('auto inferred_type = readTypeAttribute("type");')
i = 0
for arg in o['arguments']:
env['arguments'].append(arg['name'])
if value_is_tensor_type(arg):
# load tensor inputs from Caffe2
env['statements'].append("auto {}_ = Input({});".format(arg['name'], i))
i += 1
env['statements'].append(CT(
"auto ${name} = tensorWrapping(${name}_);").substitute(arg))
if arg['dynamic_type'] == 'Tensor' and not defined_inferred_type:
# first tensor input is used to define the output type.
defined_inferred_type = True
env['statements'].append('auto inferred_type = &({}.type());'.format(arg['name']))
else:
init = CT(ARGUMENT_MAP[arg['type']]).substitute(env, arg=arg['name'])
env['initialization'].append(init)
for i, r in enumerate(o['returns']):
t = RETURN_MAP[r['type'] if not value_is_tensor_type(r) else 'Tensor']
assignment = CT(t).substitute(env, offset=i, output=get_output(o, i))
env['assignments'].append(assignment)
if 'Tensor' in o['method_of']:
env['invocation'] = "self.{}({})".format(o['name'], ', '.join(env['arguments'][1:]))
elif 'namespace' in o['method_of']:
env['invocation'] = CT("at::${name}(${arguments})").substitute(env)
else:
assert('Type' in o['method_of'])
env['invocation'] = CT('inferred_type->${name}(${arguments})').substitute(env)
top_env['implementations'].append(OPTION_TEMPLATE.substitute(env))
key += 1
write("aten_op.h", OP_TEMPLATE.substitute(top_env))