Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/tensors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ A tensor can be constructed from a Python :class:`list` or sequence using the
:meth:`~torch.Tensor.requires_grad_` or
:meth:`~torch.Tensor.detach` to avoid a copy.
If you have a numpy array and want to avoid a copy, use
:func:`torch.from_numpy`.
:func:`torch.as_tensor`.

An tensor of specific data type can be constructed by passing a
:class:`torch.dtype` and/or a :class:`torch.device` to a
Expand Down
1 change: 1 addition & 0 deletions docs/source/torch.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Creation Ops
range of distributions.

.. autofunction:: tensor
.. autofunction:: as_tensor
.. autofunction:: from_numpy
.. autofunction:: zeros
.. autofunction:: zeros_like
Expand Down
39 changes: 39 additions & 0 deletions test/test_torch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2043,6 +2043,45 @@ def test_new_tensor(self):
self.assertIs(torch.int, res1.dtype)
self.assertEqual(res1.get_device(), expected.get_device())

def test_as_tensor(self):
# from python data
x = [[0, 1], [2, 3]]
self.assertEqual(torch.tensor(x), torch.as_tensor(x))
self.assertEqual(torch.tensor(x, dtype=torch.float32), torch.as_tensor(x, dtype=torch.float32))

# from tensor (doesn't copy unless type is different)
y = torch.tensor(x)
self.assertIs(y, torch.as_tensor(y))
self.assertIsNot(y, torch.as_tensor(y, dtype=torch.float32))
if torch.cuda.is_available():
self.assertIsNot(y, torch.as_tensor(y, device='cuda'))
y_cuda = y.to('cuda')
self.assertIs(y_cuda, torch.as_tensor(y_cuda))
self.assertIs(y_cuda, torch.as_tensor(y_cuda, device='cuda'))

if TEST_NUMPY:
# doesn't copy
n = np.random.rand(5, 6)
n_astensor = torch.as_tensor(n)
self.assertEqual(torch.tensor(n), n_astensor)
n_astensor[0][0] = 250.7
self.assertEqual(torch.tensor(n), n_astensor)

# changing dtype causes copy
n = np.random.rand(5, 6).astype(np.float32)
n_astensor = torch.as_tensor(n, dtype=torch.float64)
self.assertEqual(torch.tensor(n, dtype=torch.float64), n_astensor)
n_astensor[0][1] = 250.8
self.assertNotEqual(torch.tensor(n, dtype=torch.float64), n_astensor)

# changing device causes copy
if torch.cuda.is_available():
n = np.random.randn(5, 6)
n_astensor = torch.as_tensor(n, device='cuda')
self.assertEqual(torch.tensor(n, device='cuda'), n_astensor)
n_astensor[0][2] = 250.9
self.assertNotEqual(torch.tensor(n, device='cuda'), n_astensor)

def test_diag(self):
x = torch.rand(100, 100)
res1 = torch.diag(x)
Expand Down
8 changes: 8 additions & 0 deletions tools/autograd/templates/python_torch_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ static PyObject * THPVariable_arange(PyObject* self, PyObject* args, PyObject* k
END_HANDLE_TH_ERRORS
}

static PyObject * THPVariable_as_tensor(PyObject* self, PyObject* args, PyObject* kwargs)
{
HANDLE_TH_ERRORS
return THPVariable_Wrap(torch::utils::as_tensor(default_type(), args, kwargs));
END_HANDLE_TH_ERRORS
}

// The Python clamp() syntax has to be mapped to one of three C++ functions
static PyObject * THPVariable_clamp(PyObject* module, PyObject* args, PyObject* kwargs)
{
Expand Down Expand Up @@ -215,6 +222,7 @@ static PyObject * THPVariable_tensor(PyObject* self, PyObject* args, PyObject* k

static PyMethodDef torch_functions[] = {
{"arange", (PyCFunction)THPVariable_arange, METH_VARARGS | METH_KEYWORDS | METH_STATIC, NULL},
{"as_tensor", (PyCFunction)THPVariable_as_tensor, METH_VARARGS | METH_KEYWORDS | METH_STATIC, NULL},
{"clamp", (PyCFunction)THPVariable_clamp, METH_VARARGS | METH_KEYWORDS | METH_STATIC, NULL},
{"dsmm", (PyCFunction)THPVariable_mm, METH_VARARGS | METH_KEYWORDS | METH_STATIC, NULL},
{"from_numpy", (PyCFunction)THPVariable_from_numpy, METH_STATIC | METH_O, NULL},
Expand Down
58 changes: 47 additions & 11 deletions torch/_torch_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ def parse_kwargs(desc):
returned tensor. Default: ``False``.
""")

factory_data_common_args = parse_kwargs("""
data (array_like): Initial data for the tensor. Can be a list, tuple,
NumPy ``ndarray``, scalar, and other types.
dtype (:class:`torch.dtype`, optional): the desired data type of returned tensor.
Default: if None, infers data type from :attr:`data`.
device (:class:`torch.device`, optional): the desired device of returned tensor.
Default: if None, uses the current device for the default tensor type
(see :func:`torch.set_default_tensor_type`). :attr:`device` will be the CPU
for CPU tensor types and the current CUDA device for CUDA tensor types.
requires_grad (bool, optional): If autograd should record operations on the
returned tensor. Default: ``False``.
""")

add_docstr(torch.abs,
r"""
abs(input, out=None) -> Tensor
Expand Down Expand Up @@ -388,6 +401,35 @@ def parse_kwargs(desc):
[ 3., 6.]])
""")

add_docstr(torch.as_tensor,
r"""
as_tensor(data, dtype=None, device=None) -> Tensor

Convert the data into a `torch.Tensor`. If the data is already a `Tensor` of the same `dtype` and `device`, no copy
will be performed. Similarly, if the data is an ``ndarray`` of the corresponding `dtype` and the `device` is the cpu,
no copy will be performed.

Args:
{data}
{dtype}
{device}

Example::

>>> torch.tensor([[0.1, 1.2], [2.2, 3.1], [4.9, 5.2]])
tensor([[ 0.1000, 1.2000],
[ 2.2000, 3.1000],
[ 4.9000, 5.2000]])

>>> a = numpy.array([1, 2, 3])
>>> t = torch.from_numpy(a)
>>> t
tensor([ 1, 2, 3])
>>> t[0] = -1
>>> a
array([-1, 2, 3])
""".format(**factory_data_common_args))

add_docstr(torch.asin,
r"""
asin(input, out=None) -> Tensor
Expand Down Expand Up @@ -3514,16 +3556,10 @@ def parse_kwargs(desc):
:func:`torch.from_numpy`.

Args:
data (array_like): Initial data for the tensor. Can be a list, tuple,
NumPy ``ndarray``, scalar, and other types.
dtype (:class:`torch.dtype`, optional): the desired data type of returned tensor.
Default: if None, infers data type from :attr:`data`.
device (:class:`torch.device`, optional): the desired device of returned tensor.
Default: if None, uses the current device for the default tensor type
(see :func:`torch.set_default_tensor_type`). :attr:`device` will be the CPU
for CPU tensor types and the current CUDA device for CUDA tensor types.
requires_grad (bool, optional): If autograd should record operations on the
returned tensor. Default: ``False``.
{data}
{dtype}
{device}
{requires_grad}


Example::
Expand All @@ -3546,7 +3582,7 @@ def parse_kwargs(desc):

>>> torch.tensor([]) # Create an empty tensor (of size (0,))
tensor([])
""")
""".format(**factory_data_common_args))

add_docstr(torch.range,
r"""
Expand Down
15 changes: 15 additions & 0 deletions torch/csrc/utils/tensor_new.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,21 @@ Tensor tensor_ctor(const Type& type, PyObject* args, PyObject* kwargs) {
throw std::runtime_error("tensor(): invalid arguments");
}

Tensor as_tensor(const Type& type, PyObject* args, PyObject* kwargs) {
// TODO: add requires_grad once we decide on semantics for sharing data.
static PythonArgParser parser({
"as_tensor(PyObject* data, *, ScalarType dtype=None, Device? device=None)",
});

ParsedArgs<3> parsed_args;
auto r = parser.parse(args, kwargs, parsed_args);
if (r.idx == 0) {
bool type_inference = r.isNone(1);
return internal_new_from_data(
typeWithDefault(r, 1, 2, type), r.deviceOptional(2), r.pyobject(0), false, false, type_inference);
}
throw std::runtime_error("tensor(): invalid arguments");
}

Tensor new_tensor(const Type& type, PyObject* args, PyObject* kwargs) {
static PythonArgParser parser({
Expand Down
1 change: 1 addition & 0 deletions torch/csrc/utils/tensor_new.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ at::Tensor legacy_tensor_new(const at::Type& type, PyObject* args, PyObject* kwa
at::Tensor legacy_new_from_data(const at::Type& type, at::optional<Device> device, PyObject *data);
at::Tensor sparse_coo_tensor_ctor(const at::Type& type, PyObject* args, PyObject* kwargs);
at::Tensor tensor_ctor(const at::Type& type, PyObject* args, PyObject* kwargs);
at::Tensor as_tensor(const at::Type& type, PyObject* args, PyObject* kwargs);
at::Tensor new_tensor(const at::Type& type, PyObject* args, PyObject* kwargs);
at::Tensor new_empty(const at::Type& type, PyObject* args, PyObject* kwargs);
at::Tensor new_full(const at::Type& type, PyObject* args, PyObject* kwargs);
Expand Down