mirror of
https://github.com/zebrajr/pytorch.git
synced 2025-12-06 12:20:52 +01:00
Summary: Partially fixes: https://github.com/pytorch/pytorch/issues/394 Implementation detail: Codegen is modified to generate codes that looks like below: ```C++ static PyObject * THPVariable_svd(PyObject* self_, PyObject* args, PyObject* kwargs) { HANDLE_TH_ERRORS static PythonArgParser parser({ "svd(Tensor input, bool some=True, bool compute_uv=True, *, TensorList[3] out=None)", }, /*traceable=*/true); ParsedArgs<6> parsed_args; auto r = parser.parse(args, kwargs, parsed_args); static PyStructSequence_Field fields0[] = { {"U", ""}, {"S", ""}, {"V", ""}, {nullptr} }; static PyStructSequence_Desc desc0 = { "torch.return_types.svd_out", nullptr, fields0, 3 }; static PyTypeObject type0; static bool namedtuple_type_initialized0 = false; if (!namedtuple_type_initialized0) { PyStructSequence_InitType(&type0, &desc0); namedtuple_type_initialized0 = true; } static PyStructSequence_Field fields1[] = { {"U", ""}, {"S", ""}, {"V", ""}, {nullptr} }; static PyStructSequence_Desc desc1 = { "torch.return_types.svd", nullptr, fields1, 3 }; static PyTypeObject type1; static bool namedtuple_type_initialized1 = false; if (!namedtuple_type_initialized1) { PyStructSequence_InitType(&type1, &desc1); namedtuple_type_initialized1 = true; } if (r.idx == 0) { if (r.isNone(3)) { return wrap(&type1, dispatch_svd(r.tensor(0), r.toBool(1), r.toBool(2))); } else { auto results = r.tensorlist_n<3>(3); return wrap(&type0, dispatch_svd(r.tensor(0), r.toBool(1), r.toBool(2), results[0], results[1], results[2])); } } Py_RETURN_NONE; END_HANDLE_TH_ERRORS } ``` Types are defined as static member of `THPVariable_${op_name}` functions, and initialized at the first time the function is called. When parsing function prototypes in `native_functions.yaml`, the parser will set the specified name as `field_name` when see things like `-> (Tensor t1, ...)`. These field names will be the field names of namedtuple. The class of namedtuples will be named `torch.return_types.${op_name}`. In some python 2, `PyStructSequence` is not a subtype of tuple, so we have to create some functions to check if an object is a tuple or namedtuple for compatibility issue. Operators in `native_functions.yaml` are changed such that only `max` and `svd` are generated as namedtuple. Tests are added for these two operators to see if the return value works as expected. Docs for these two ops are also updated to explicitly mention the return value is a namedtuple. More ops will be added in later PRs. There is some issue with Windows build of linker unable to resolve `PyStructSequence_UnnamedField`, and some workaround is added to deal with this case. Pull Request resolved: https://github.com/pytorch/pytorch/pull/15429 Differential Revision: D13709678 Pulled By: ezyang fbshipit-source-id: 23a511c9436977098afc49374e9a748b6e30bccf
153 lines
4.1 KiB
Python
153 lines
4.1 KiB
Python
# Copyright (c) 2010-2017 Benjamin Peterson
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
import itertools
|
|
import sys
|
|
|
|
|
|
PY2 = sys.version_info[0] == 2
|
|
PY3 = sys.version_info[0] == 3
|
|
|
|
if PY2:
|
|
inf = float('inf')
|
|
nan = float('nan')
|
|
else:
|
|
import math
|
|
inf = math.inf
|
|
nan = math.nan
|
|
|
|
if PY2:
|
|
string_classes = basestring
|
|
else:
|
|
string_classes = (str, bytes)
|
|
|
|
|
|
if PY2:
|
|
int_classes = (int, long)
|
|
else:
|
|
int_classes = int
|
|
|
|
|
|
if PY2:
|
|
FileNotFoundError = IOError
|
|
else:
|
|
FileNotFoundError = FileNotFoundError
|
|
|
|
|
|
if PY2:
|
|
import Queue as queue
|
|
else:
|
|
import queue
|
|
|
|
|
|
def with_metaclass(meta, *bases):
|
|
"""Create a base class with a metaclass."""
|
|
# This requires a bit of explanation: the basic idea is to make a dummy
|
|
# metaclass for one level of class instantiation that replaces itself with
|
|
# the actual metaclass.
|
|
class metaclass(meta):
|
|
|
|
def __new__(cls, name, this_bases, d):
|
|
return meta(name, bases, d)
|
|
return type.__new__(metaclass, 'temporary_class', (), {})
|
|
|
|
|
|
# A portable way of referring to the generator version of map
|
|
# in both Python 2 and Python 3.
|
|
# TODO: Move this into an appropriate utility library.
|
|
if hasattr(itertools, 'imap'):
|
|
imap = itertools.imap
|
|
else:
|
|
imap = map
|
|
|
|
|
|
if PY3:
|
|
import builtins
|
|
exec_ = getattr(builtins, "exec")
|
|
else:
|
|
def exec_(_code_, _globs_=None, _locs_=None):
|
|
"""Execute code in a namespace."""
|
|
if _globs_ is None:
|
|
frame = sys._getframe(1)
|
|
_globs_ = frame.f_globals
|
|
if _locs_ is None:
|
|
_locs_ = frame.f_locals
|
|
del frame
|
|
elif _locs_ is None:
|
|
_locs_ = _globs_
|
|
exec("""exec _code_ in _globs_, _locs_""")
|
|
|
|
|
|
if sys.version_info[:2] == (3, 2):
|
|
exec_("""def raise_from(value, from_value):
|
|
try:
|
|
if from_value is None:
|
|
raise value
|
|
raise value from from_value
|
|
finally:
|
|
value = None
|
|
""")
|
|
elif sys.version_info[:2] > (3, 2):
|
|
exec_("""def raise_from(value, from_value):
|
|
try:
|
|
raise value from from_value
|
|
finally:
|
|
value = None
|
|
""")
|
|
else:
|
|
def raise_from(value, from_value):
|
|
raise value
|
|
|
|
if PY2:
|
|
import collections
|
|
container_abcs = collections
|
|
elif PY3:
|
|
import collections.abc
|
|
container_abcs = collections.abc
|
|
|
|
# Gets a function from the name of a method on a type
|
|
if PY2:
|
|
def get_function_from_type(cls, name):
|
|
method = getattr(cls, name, None)
|
|
return getattr(method, "__func__", None)
|
|
elif PY3:
|
|
def get_function_from_type(cls, name):
|
|
return getattr(cls, name, None)
|
|
|
|
if PY2:
|
|
import __builtin__ as builtins
|
|
elif PY3:
|
|
import builtins
|
|
|
|
|
|
# The codes below is not copied from the six package, so the copyright
|
|
# declaration at the beginning does not apply.
|
|
#
|
|
# Copyright(c) PyTorch contributors
|
|
#
|
|
|
|
def istuple(obj):
|
|
# Usually instances of PyStructSequence is also an instance of tuple
|
|
# but in some py2 environment it is not, so we have to manually check
|
|
# the name of the type to determine if it is a namedtupled returned
|
|
# by a pytorch operator.
|
|
t = type(obj)
|
|
return isinstance(obj, tuple) or t.__module__ == 'torch.return_types'
|