Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features/372 data layout #423

Merged
merged 45 commits into from
Dec 10, 2019
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
93a8ccc
Added dndarray property 'stride' (same as torch.Tensor.stride).
ClaudiaComito Oct 28, 2019
6df3bed
Merge branch 'master' into features/372-data_layout
ClaudiaComito Nov 20, 2019
c2faa47
Implemented tensor property strides (numpy-like), added to docs.
ClaudiaComito Nov 20, 2019
8cb873c
Merge branch 'master' into features/372-data_layout
ClaudiaComito Nov 21, 2019
7c12fe5
First pass of column-first memory layout, single-node only.
ClaudiaComito Nov 22, 2019
b01511b
Implemented stride_tricks.sanitize_memory_layout, first pass.
ClaudiaComito Nov 23, 2019
d01d14d
Property DNDarray.strides now gets the correct information from the u…
ClaudiaComito Nov 25, 2019
5b6276d
Moved sanitize_memory_layout from stride_tricks to module memory.
ClaudiaComito Nov 25, 2019
2d0596d
Implemented memory.sanitize_memory_layout.
ClaudiaComito Nov 25, 2019
5ace9e1
Introduced attribute order in factories.array, enables specification …
ClaudiaComito Nov 25, 2019
89caeaa
sanitize_memory_layout(), moved tests for least likely occurrencies t…
ClaudiaComito Nov 25, 2019
bfaec9c
Fixed typos.
ClaudiaComito Nov 25, 2019
d619ce6
Merge branch 'master' into features/372-data_layout
ClaudiaComito Nov 27, 2019
a60482f
ht.array, docs and examples for attribute "order" defining memory lay…
ClaudiaComito Nov 27, 2019
dba61a2
Expanded documentation to sanitize_memory_layout().
ClaudiaComito Nov 27, 2019
90b6e16
Keyword argument order introduced for all factories (except those cre…
ClaudiaComito Nov 27, 2019
12934d1
Typo.
ClaudiaComito Nov 28, 2019
c442fa8
Implemented function assertTrue_memory_layout()
ClaudiaComito Nov 28, 2019
1da8a74
Modified sanitize_memory_layout() to address tensors with unknown/wro…
ClaudiaComito Nov 28, 2019
bb9a2b6
sanitize_memory_layout(), removed unnecessary checks (ndim < 2 and st…
ClaudiaComito Nov 29, 2019
c62974b
Added keyword argument order in factories calls.
ClaudiaComito Nov 29, 2019
e18eee6
First draft of function test_sanitize_memory_layout()
ClaudiaComito Nov 29, 2019
c725a8b
Modified row_major, column_major condition to allow for shape=1 along…
ClaudiaComito Nov 29, 2019
76a1617
Added memory layout test for 5D tensor, non distributed, shape contai…
ClaudiaComito Nov 29, 2019
5628fac
Added memory layout test for non distributed tensor after reduction o…
ClaudiaComito Nov 29, 2019
932a85e
Removed test for non distributed tensor after reduction operation, no…
ClaudiaComito Nov 29, 2019
c3ede72
Implemented tests for sanitize_memory_layout()
ClaudiaComito Nov 29, 2019
5beea49
In assert_array_equal(), Allreduce running on self._comm, not on self…
ClaudiaComito Nov 29, 2019
43df1ab
Removing unused variable.
ClaudiaComito Dec 2, 2019
e47ac20
Fixed error that messed up column-major memory layout for ndim>3
ClaudiaComito Dec 2, 2019
3a096ad
Fixed and expanded tests for sanitize_memory_layout
ClaudiaComito Dec 2, 2019
814caa9
Fixed wrong variable reference after pre-commit
ClaudiaComito Dec 2, 2019
b659576
pre-commit changes
ClaudiaComito Dec 2, 2019
eea901b
Improved docs.
ClaudiaComito Dec 2, 2019
c38dede
Extending BasicTest in TestMemory
ClaudiaComito Dec 6, 2019
eca64fa
Merge branch 'master' into features/372-data_layout
coquelin77 Dec 6, 2019
e7d0090
Re-adding assertTrue_memory_layout to BasicTest
ClaudiaComito Dec 7, 2019
3bb13ba
Function test_dndarray.test_stride_and_strides(), first pass,
ClaudiaComito Dec 9, 2019
218345b
Added float32, row-major and float64, column-major stride tests
ClaudiaComito Dec 9, 2019
d3a7499
test_stride_and_strides: Added test cases for distributed tensors in …
ClaudiaComito Dec 9, 2019
ae00b59
pre-commit reformatting
ClaudiaComito Dec 9, 2019
fbe9738
Added function test_basic_test.test_assertTrue_memory_layout()
ClaudiaComito Dec 9, 2019
c21d6be
Merge branch 'master' into features/372-data_layout
ClaudiaComito Dec 9, 2019
38802e8
dndrray.strides, replaced np.array(...)*itemsize with list comprehens…
ClaudiaComito Dec 10, 2019
46fbc2e
pre-commit minor changes
ClaudiaComito Dec 10, 2019
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
22 changes: 22 additions & 0 deletions heat/core/dndarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,28 @@ def split(self):
"""
return self.__split

@property
def stride(self):
"""
Returns
-------
tuple of ints: steps in each dimension when traversing a tensor.
torch-like usage: self.stride()
"""
return self.__array.stride

@property
def strides(self):
"""
Returns
-------
tuple of ints: bytes to step in each dimension when traversing a tensor.
numpy-like usage: self.strides
"""
stride = np.array(self._DNDarray__array.stride())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use torch here instead of numpy? numpy doesnt like GPUs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is even no need to return a array (Numpy or PyTorch) at all as stride() already returns a tuple

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krajsek this is the numpy-like version of stride (output in bytes), I need to be able to multiply it by the element size.

@coquelin77 if you expect problems I'll use list comprehension instead of numpy. Must we be wary of every numpy call then, or only when dealing with large arrays?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o.k., I see

@krajsek this is the numpy-like version of stride (output in bytes), I need to be able to multiply it by the element size.

@coquelin77 if you expect problems I'll use list comprehension instead of numpy. Must we be wary of every numpy call then, or only when dealing with large arrays?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we should always be wary of using numpy. we should aim to use primarily pytorch functions. Also, if i dont misunderstand, wrapping this in a numpy array doesnt really get us anything since its already a torch tensor

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Daniel.
This:
self._DNDarray__array.stride()
is a tuple. In order to provide numpy-like information (output in bytes), this tuple has to be multiplied by the element size of the tensor (scalar, bytes). I need to change the tuple into something that can be multiplied by a scalar. I'll use list comprehension and be done with it.

itemsize = self._DNDarray__array.storage().element_size()
return tuple(stride * itemsize)

@property
def T(self, axes=None):
return linalg.transpose(self, axes)
Expand Down
165 changes: 143 additions & 22 deletions heat/core/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .stride_tricks import sanitize_axis, sanitize_shape
from . import devices
from . import dndarray
from . import memory
from . import types

__all__ = [
Expand Down Expand Up @@ -131,7 +132,17 @@ def arange(*args, dtype=None, split=None, device=None, comm=None):
return dndarray.DNDarray(data, gshape, htype, split, device, comm)


def array(obj, dtype=None, copy=True, ndmin=0, split=None, is_split=None, device=None, comm=None):
def array(
obj,
dtype=None,
copy=True,
ndmin=0,
order="C",
split=None,
is_split=None,
device=None,
comm=None,
):
"""
Create a tensor.
Parameters
Expand All @@ -149,6 +160,11 @@ def array(obj, dtype=None, copy=True, ndmin=0, split=None, is_split=None, device
ndmin : int, optional
Specifies the minimum number of dimensions that the resulting array should have. Ones will, if needed, be
attached to the shape if ndim>0 and prefaced in case of ndim<0 to meet the requirement.
order: str, optional
Options: 'C' or 'F'. Specifies the memory layout of the newly created tensor. Default is order='C', meaning the array
will be stored in row-major order (C-like). If order=‘F’, the array will be stored in column-major order (Fortran-like).
Raises NotImplementedError for NumPy options 'K' and 'A'.
#TODO: implement 'K' option when torch.clone() fix to preserve memory layout is released.
split : None or int, optional
The axis along which the passed array content obj is split and distributed in memory. Mutually exclusive with
is_split.
Expand Down Expand Up @@ -198,6 +214,71 @@ def array(obj, dtype=None, copy=True, ndmin=0, split=None, is_split=None, device
(1/2) >>> ht.array([3, 4], is_split=0)
(0/2) tensor([1, 2, 3, 4])
(1/2) tensor([1, 2, 3, 4])

Memory layout, single-node:
>>> a = np.arange(2 * 3).reshape(2, 3)
>>> a
array([[ 0, 1, 2],
[ 3, 4, 5]])
>>> a.strides
(24, 8)
>>> b = ht.array(a)
>>> b
tensor([[0, 1, 2],
[3, 4, 5]])
>>> b.strides
(24, 8)
>>> b._DNDarray__array.storage() #TODO: implement ht.view()
0
1
2
3
4
5
[torch.LongStorage of size 6]
>>> c = ht.array(a, order='F')
>>> c
tensor([[0, 1, 2],
[3, 4, 5]])
>>> c.strides
(8, 16)
>>> c._DNDarray__array.storage() #TODO: implement ht.view()
0
3
1
4
2
5
[torch.LongStorage of size 6]

Memory layout, distributed:
>>> a = np.arange(4 * 3).reshape(4, 3)
>>> a.strides
(24, 8)
>>> b = ht.array(a, order='F')
>>> b
(0/2) tensor([[0, 1, 2],
[3, 4, 5]])
(1/2) tensor([[ 6, 7, 8],
[ 9, 10, 11]])
>>> b.strides
(0/2) (8, 16)
(1/2) (8, 16)
>>> b._DNDarray__array.storage() #TODO: implement ht.view()
(0/2) 0
3
1
4
2
5
[torch.LongStorage of size 6]
(1/2) 6
9
7
10
8
11
[torch.LongStorage of size 6]
"""
# extract the internal tensor in case of a heat tensor
if isinstance(obj, dndarray.DNDarray):
Expand All @@ -210,6 +291,8 @@ def array(obj, dtype=None, copy=True, ndmin=0, split=None, is_split=None, device
# initialize the array
if bool(copy):
if isinstance(obj, torch.Tensor):
# TODO: watch out. At the moment clone() implies losing the underlying memory layout.
# pytorch fix in progress
obj = obj.clone().detach()
else:
try:
Expand Down Expand Up @@ -250,8 +333,10 @@ def array(obj, dtype=None, copy=True, ndmin=0, split=None, is_split=None, device
if split is not None:
_, _, slices = comm.chunk(obj.shape, split)
obj = obj[slices].clone()
obj = memory.sanitize_memory_layout(obj, order=order)
# check with the neighboring rank whether the local shape would fit into a global shape
elif is_split is not None:
obj = memory.sanitize_memory_layout(obj, order=order)
if comm.rank < comm.size - 1:
comm.Isend(lshape, dest=comm.rank + 1)
if comm.rank != 0:
Expand Down Expand Up @@ -284,11 +369,13 @@ def array(obj, dtype=None, copy=True, ndmin=0, split=None, is_split=None, device
comm.Allreduce(MPI.IN_PLACE, ttl_shape, MPI.SUM)
gshape[is_split] = ttl_shape[is_split]
split = is_split
elif split is None and is_split is None:
obj = memory.sanitize_memory_layout(obj, order=order)

return dndarray.DNDarray(obj, tuple(int(ele) for ele in gshape), dtype, split, device, comm)


def empty(shape, dtype=types.float32, split=None, device=None, comm=None):
def empty(shape, dtype=types.float32, split=None, device=None, comm=None, order="C"):
"""
Returns a new uninitialized array of given shape and data type. May be allocated split up across multiple
nodes along the specified axis.
Expand All @@ -305,6 +392,11 @@ def empty(shape, dtype=types.float32, split=None, device=None, comm=None):
Specifies the device the tensor shall be allocated on, defaults to None (i.e. globally set default device).
comm: Communication, optional
Handle to the nodes holding distributed parts or copies of this tensor.
order: str, optional
Options: 'C' or 'F'. Specifies the memory layout of the newly created tensor. Default is order='C', meaning the array
will be stored in row-major order (C-like). If order=‘F’, the array will be stored in column-major order (Fortran-like).
Raises NotImplementedError for NumPy options 'K' and 'A'.
#TODO: implement 'K' option when torch.clone() fix to preserve memory layout is released.

Returns
-------
Expand All @@ -323,10 +415,10 @@ def empty(shape, dtype=types.float32, split=None, device=None, comm=None):
tensor([[ 0.0000e+00, -2.0000e+00, 3.3113e+35],
[ 3.6902e+19, 1.2096e+04, 7.1846e+22]])
"""
return __factory(shape, dtype, split, torch.empty, device, comm)
return __factory(shape, dtype, split, torch.empty, device, comm, order)


def empty_like(a, dtype=None, split=None, device=None, comm=None):
def empty_like(a, dtype=None, split=None, device=None, comm=None, order="C"):
"""
Returns a new uninitialized array with the same type, shape and data distribution of given object. Data type and
data distribution strategy can be explicitly overriden.
Expand Down Expand Up @@ -361,10 +453,10 @@ def empty_like(a, dtype=None, split=None, device=None, comm=None):
tensor([[ 0.0000e+00, -2.0000e+00, 3.3113e+35],
[ 3.6902e+19, 1.2096e+04, 7.1846e+22]])
"""
return __factory_like(a, dtype, split, empty, device, comm)
return __factory_like(a, dtype, split, empty, device, comm, order=order)


def eye(shape, dtype=types.float32, split=None, device=None, comm=None):
def eye(shape, dtype=types.float32, split=None, device=None, comm=None, order="C"):
"""
Returns a new 2-D tensor with ones on the diagonal and zeroes elsewhere.

Expand All @@ -381,6 +473,11 @@ def eye(shape, dtype=types.float32, split=None, device=None, comm=None):
Specifies the device the tensor shall be allocated on, defaults to None (i.e. globally set default device).
comm : Communication, optional
Handle to the nodes holding distributed parts or copies of this tensor.
order: str, optional
Options: 'C' or 'F'. Specifies the memory layout of the newly created tensor. Default is order='C', meaning the array
will be stored in row-major order (C-like). If order=‘F’, the array will be stored in column-major order (Fortran-like).
Raises NotImplementedError for NumPy options 'K' and 'A'.
#TODO: implement 'K' option when torch.clone() fix to preserve memory layout is released.

Returns
-------
Expand Down Expand Up @@ -421,12 +518,13 @@ def eye(shape, dtype=types.float32, split=None, device=None, comm=None):
pos_y = i if split == 1 else i + offset
data[pos_x][pos_y] = 1

data = memory.sanitize_memory_layout(data, order=order)
return dndarray.DNDarray(
data, gshape, types.canonical_heat_type(data.dtype), split, device, comm
)


def __factory(shape, dtype, split, local_factory, device, comm):
def __factory(shape, dtype, split, local_factory, device, comm, order):
"""
Abstracted factory function for HeAT tensor initialization.

Expand Down Expand Up @@ -461,11 +559,11 @@ def __factory(shape, dtype, split, local_factory, device, comm):
_, local_shape, _ = comm.chunk(shape, split)
# create the torch data using the factory function
data = local_factory(local_shape, dtype=dtype.torch_type(), device=device.torch_device)

data = memory.sanitize_memory_layout(data, order=order)
return dndarray.DNDarray(data, shape, dtype, split, device, comm)


def __factory_like(a, dtype, split, factory, device, comm, **kwargs):
def __factory_like(a, dtype, split, factory, device, comm, order="C", **kwargs):
"""
Abstracted '...-like' factory function for HeAT tensor initialization

Expand All @@ -483,6 +581,12 @@ def __factory_like(a, dtype, split, factory, device, comm, **kwargs):
Specifies the device the tensor shall be allocated on, defaults to None (i.e. globally set default device).
comm: Communication
Handle to the nodes holding distributed parts or copies of this tensor.
order: str, optional
Options: 'C' or 'F'. Specifies the memory layout of the newly created tensor. Default is order='C', meaning the array
will be stored in row-major order (C-like). If order=‘F’, the array will be stored in column-major order (Fortran-like).
Raises NotImplementedError for NumPy options 'K' and 'A'.
#TODO: implement 'K' option when torch.clone() fix to preserve memory layout is released.


Returns
-------
Expand Down Expand Up @@ -517,10 +621,10 @@ def __factory_like(a, dtype, split, factory, device, comm, **kwargs):
# use the default communicator, if not set
comm = sanitize_comm(comm)

return factory(shape, dtype=dtype, split=split, device=device, comm=comm, **kwargs)
return factory(shape, dtype=dtype, split=split, device=device, comm=comm, order=order, **kwargs)


def full(shape, fill_value, dtype=types.float32, split=None, device=None, comm=None):
def full(shape, fill_value, dtype=types.float32, split=None, device=None, comm=None, order="C"):
"""
Return a new array of given shape and type, filled with fill_value.

Expand Down Expand Up @@ -557,10 +661,10 @@ def full(shape, fill_value, dtype=types.float32, split=None, device=None, comm=N
def local_factory(*args, **kwargs):
return torch.full(*args, fill_value=fill_value, **kwargs)

return __factory(shape, dtype, split, local_factory, device, comm)
return __factory(shape, dtype, split, local_factory, device, comm, order=order)


def full_like(a, fill_value, dtype=types.float32, split=None, device=None, comm=None):
def full_like(a, fill_value, dtype=types.float32, split=None, device=None, comm=None, order="C"):
"""
Return a full array with the same shape and type as a given array.

Expand Down Expand Up @@ -595,7 +699,7 @@ def full_like(a, fill_value, dtype=types.float32, split=None, device=None, comm=
tensor([[1., 1., 1.],
[1., 1., 1.]])
"""
return __factory_like(a, dtype, split, full, device, comm, fill_value=fill_value)
return __factory_like(a, dtype, split, full, device, comm, fill_value=fill_value, order=order)


def linspace(
Expand Down Expand Up @@ -755,7 +859,7 @@ def logspace(
return pow(base, y).astype(dtype, copy=False)


def ones(shape, dtype=types.float32, split=None, device=None, comm=None):
def ones(shape, dtype=types.float32, split=None, device=None, comm=None, order="C"):
"""
Returns a new array of given shape and data type filled with one values. May be allocated split up across multiple
nodes along the specified axis.
Expand All @@ -772,6 +876,12 @@ def ones(shape, dtype=types.float32, split=None, device=None, comm=None):
Specifies the device the tensor shall be allocated on, defaults to None (i.e. globally set default device).
comm : Communication, optional
Handle to the nodes holding distributed parts or copies of this tensor.
order: str, optional
Options: 'C' or 'F'. Specifies the memory layout of the newly created tensor. Default is order='C', meaning the array
will be stored in row-major order (C-like). If order=‘F’, the array will be stored in column-major order (Fortran-like).
Raises NotImplementedError for NumPy options 'K' and 'A'.
#TODO: implement 'K' option when torch.clone() fix to preserve memory layout is released.


Returns
-------
Expand All @@ -790,10 +900,10 @@ def ones(shape, dtype=types.float32, split=None, device=None, comm=None):
tensor([[1., 1., 1.],
[1., 1., 1.]])
"""
return __factory(shape, dtype, split, torch.ones, device, comm)
return __factory(shape, dtype, split, torch.ones, device, comm, order)


def ones_like(a, dtype=None, split=None, device=None, comm=None):
def ones_like(a, dtype=None, split=None, device=None, comm=None, order="C"):
"""
Returns a new array filled with ones with the same type, shape and data distribution of given object. Data type and
data distribution strategy can be explicitly overriden.
Expand Down Expand Up @@ -827,10 +937,10 @@ def ones_like(a, dtype=None, split=None, device=None, comm=None):
tensor([[1., 1., 1.],
[1., 1., 1.]])
"""
return __factory_like(a, dtype, split, ones, device, comm)
return __factory_like(a, dtype, split, ones, device, comm, order=order)


def zeros(shape, dtype=types.float32, split=None, device=None, comm=None):
def zeros(shape, dtype=types.float32, split=None, device=None, comm=None, order="C"):
"""
Returns a new array of given shape and data type filled with zero values. May be allocated split up across multiple
nodes along the specified axis.
Expand All @@ -847,6 +957,12 @@ def zeros(shape, dtype=types.float32, split=None, device=None, comm=None):
Specifies the device the tensor shall be allocated on, defaults to None (i.e. globally set default device).
comm: Communication, optional
Handle to the nodes holding distributed parts or copies of this tensor.
order: str, optional
Options: 'C' or 'F'. Specifies the memory layout of the newly created tensor. Default is order='C', meaning the array
will be stored in row-major order (C-like). If order=‘F’, the array will be stored in column-major order (Fortran-like).
Raises NotImplementedError for NumPy options 'K' and 'A'.
#TODO: implement 'K' option when torch.clone() fix to preserve memory layout is released.


Returns
-------
Expand All @@ -865,10 +981,10 @@ def zeros(shape, dtype=types.float32, split=None, device=None, comm=None):
tensor([[0., 0., 0.],
[0., 0., 0.]])
"""
return __factory(shape, dtype, split, torch.zeros, device, comm)
return __factory(shape, dtype, split, torch.zeros, device, comm, order=order)


def zeros_like(a, dtype=None, split=None, device=None, comm=None):
def zeros_like(a, dtype=None, split=None, device=None, comm=None, order="C"):
"""
Returns a new array filled with zeros with the same type, shape and data distribution of given object. Data type and
data distribution strategy can be explicitly overriden.
Expand All @@ -885,6 +1001,11 @@ def zeros_like(a, dtype=None, split=None, device=None, comm=None):
Specifies the device the tensor shall be allocated on, defaults to None (i.e. globally set default device).
comm: Communication, optional
Handle to the nodes holding distributed parts or copies of this tensor.
order: str, optional
Options: 'C' or 'F'. Specifies the memory layout of the newly created tensor. Default is order='C', meaning the array
will be stored in row-major order (C-like). If order=‘F’, the array will be stored in column-major order (Fortran-like).
Raises NotImplementedError for NumPy options 'K' and 'A'.
#TODO: implement 'K' option when torch.clone() fix to preserve memory layout is released.

Returns
-------
Expand All @@ -902,4 +1023,4 @@ def zeros_like(a, dtype=None, split=None, device=None, comm=None):
tensor([[0., 0., 0.],
[0., 0., 0.]])
"""
return __factory_like(a, dtype, split, zeros, device, comm)
return __factory_like(a, dtype, split, zeros, device, comm, order=order)
Loading