blob: 79599eb42db2247b6653bfb4eb2f3d25cd222aad [file] [log] [blame]
import re
from copy import deepcopy
from function_wrapper import TYPE_FORMAL_GENERIC
import common_with_cwrap
type_map = {
'floating_point': [
'Float',
'Double',
'Half',
'BFloat16',
],
'integral': [
'Byte',
'Char',
'Short',
'Int',
'Long',
'Bool',
],
'quantized': [
'QInt8',
'QUInt8',
'QInt32',
]
}
all_types = type_map['floating_point'] + type_map['integral'] + type_map['quantized']
type_map['all'] = all_types
all_backends = ['CPU', 'CUDA', 'SparseCPU', 'SparseCUDA', 'MkldnnCPU', 'QuantizedCPU']
default_backends = ['CPU', 'CUDA']
def process_types_and_backends(option):
# if specific pairs were not listed, then enumerate them
# based on the backend and type attributes
# if backend or type is not defined, it is assumed to be all of them
if 'backend_types' not in option:
backends = option.get('backends', default_backends)
if isinstance(option.get('type_method_definition_dispatch'), dict):
backends = option.get('type_method_definition_dispatch').keys()
backends = set(backends)
backend_types = {}
for backend in backends:
if backend == 'QuantizedCPU':
backend_types[backend] = type_map['quantized']
else:
backend_types[backend] = option.get('types', all_types)
else:
backend_types = option['backend_types']
# expand type alias (integral, floating_point, all)
def expand(types):
ret = []
for t in types:
if t in type_map:
ret.extend(type_map[t])
else:
assert(t in all_types)
ret.append(t)
return ret
for backend in backend_types.keys():
assert(backend in all_backends)
backend_types[backend] = set(expand(backend_types[backend]))
# special case remove Half for cpu unless it is explicitly enabled
if not option.get('cpu_half', False):
if 'CPU' in backend_types:
backend_types['CPU'].discard('Half')
# special case remove BFloat16 for cpu unless it is explicitly enabled
if not option.get('cpu_bfloat16', False):
if 'CPU' in backend_types:
backend_types['CPU'].discard('BFloat16')
# TODO: remove this hack once support for a bfloat16 tensor for CUDA is enabled
if 'CUDA' in backend_types:
backend_types['CUDA'].discard('BFloat16')
# special cases remove bool for cpu and cuda unless it is explicitly enabled
if not option.get('cpu_bool', False):
if 'CPU' in backend_types:
backend_types['CPU'].discard('Bool')
if not option.get('cuda_bool', False):
if 'CUDA' in backend_types:
backend_types['CUDA'].discard('Bool')
# sort the result for easy reading
for backend in backend_types.keys():
backend_types[backend] = sorted([type for type in backend_types[backend]])
option['backend_types'] = backend_types
def exclude(declaration):
return 'only_register' in declaration or declaration.get('name') == 'ndimension'
def add_variants(option):
option.setdefault('variants', ['method'])
# if we have 'output' arguments, generate a variant where
# we mark oututs as allocate = True, and where the method variant
# is disabled...
def handle_outputs_taken_as_arguments(options):
new_options = []
def is_nullable(arg):
return (arg['type'] in {'THIntegerTensor*', 'THTensor*'} and
arg.get('default', '') in {None, 'NULL', 'nullptr'})
def should_generate_out_variant(option):
if 'function' in option['variants'] and option['mode'] != 'native':
# don't generate _out variants for in-place functions
return re.search('(^__i|[^_]_$)', option['api_name']) is None
return False
for option in options:
for arg in option['arguments']:
# mark arguments which can be null
if is_nullable(arg):
arg['is_nullable'] = True
if any('output' in arg for arg in option['arguments']):
allocate_option = deepcopy(option)
# the allocating option needs to be marked
for arg in allocate_option['arguments']:
if 'output' in arg:
arg['allocate'] = True
# the original option, which takes arguments for the results,
# is no longer a method, and has _out added to indicte it takes
# output arguments
if should_generate_out_variant(option):
if 'method' in option['variants']:
option['variants'].remove('method')
option['api_name'] += '_out'
new_options.append(option)
new_options.append(allocate_option)
else:
new_options.append(option)
return new_options
def sanitize_return(option):
ret = option['return']
m = re.match(r'argument (\d+(,\d+)*)', ret)
if m is not None:
arguments = [int(x) for x in m.group(1).split(',')]
option['return'] = {'kind': 'arguments', 'arguments': arguments}
elif ret == 'self':
option['return'] = {'kind': 'arguments', 'arguments': []}
for i, x in enumerate(option['arguments']):
if x['name'] == 'self':
option['return']['arguments'].append(i)
break
else:
option['return'] = {'kind': 'type', 'type': option['return']}
def set_mode(option):
option['mode'] = option.get('mode', 'TH')
# To enable 0-dim support in TH operations
# we find all places where a single Scalar replaced with a Tensor
# as an argument is still a valid function
# we then mark the tensor variant with a key zero_dim_dispatch_when_scalar: name
# where 'name' is the name of the argument that should be a scalar
# during dispatch, if that argument is marked internally as holding a scalar
# then the method will dispatch to that function.
def discover_zero_dim_tensor_operations(declaration):
def exclude(arg):
return arg.get('ignore_check')
def signature(option, i=None, value=None):
elements = [TYPE_FORMAL_GENERIC.get(arg['type'], arg['type'])
if i is None or j != i else value
for j, arg in enumerate(option['arguments'])
if not exclude(arg)]
return '#'.join(elements)
signature_to_option = {signature(option): option
for option in declaration['options']}
for option in declaration['options']:
for i, arg in enumerate(option['arguments']):
if arg['type'] == 'real':
signature_of_tensor_version = signature(option, i, 'Tensor &')
if signature_of_tensor_version in signature_to_option:
tensor_version = \
signature_to_option[signature_of_tensor_version]
names = [arg['name'] for arg in tensor_version['arguments']
if not exclude(arg)]
tensor_version['zero_dim_dispatch_when_scalar'] = names[i]
# print("FOUND "+str(i) )
# print("Scalar Version ===== ")
# print(yaml.dump(option))
# print("Tensor Version ===== ")
# print(yaml.dump(tensor_version))
# print("SHARED "+names[i])
def discover_sparse_tensor_operations(declaration):
def exclude(arg):
return arg.get('ignore_check')
def signature(option, i=None, value=None):
elements = [TYPE_FORMAL_GENERIC.get(arg['type'], arg['type'])
if i is None or j != i else value
for j, arg in enumerate(option['arguments'])
if not exclude(arg)]
return '#'.join(elements)
def is_extended_method(option):
if 'method' in option['variants']:
return False
else:
return True
def run(declarations):
declarations = [d for d in declarations if not exclude(d)]
non_extended_methods = set()
for declaration in declarations:
common_with_cwrap.set_declaration_defaults(declaration)
declaration['options'] = [deepcopy(o) for o in declaration['options']]
declaration['options'] = common_with_cwrap.filter_unique_options(
declaration['options'],
allow_kwarg=False,
type_to_signature=TYPE_FORMAL_GENERIC,
remove_self=True)
common_with_cwrap.sort_by_number_of_options(declaration)
discover_zero_dim_tensor_operations(declaration)
discover_sparse_tensor_operations(declaration)
for option in declaration['options']:
set_mode(option)
if option['mode'] != 'native':
sanitize_return(option)
process_types_and_backends(option)
add_variants(option)
if not is_extended_method(option):
non_extended_methods.add(option['api_name'])
declaration['options'] = handle_outputs_taken_as_arguments(
declaration['options'])
# We (very unfortunately) have overloaded virtual methods. Because
# of C++'s rules, we cannot move one overload without doing some
# extra work to make sure that overload in a superclass and an
# overload in a subclass resolve together. I've chosen to resolve
# this problem simply by moving ALL overloads of a method which
# occurs in Tensor to Type. This is why we have to first compute
# which methods *names* go on type, and then move ALL overloads
# of this name to Type.
for declaration in declarations:
for option in declaration['options']:
option['extended_method'] = option['api_name'] not in non_extended_methods
return declarations