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

Code changes based on reviews #144

Merged
merged 20 commits into from
Aug 8, 2017
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e4ddaab
code changes according to review comments
eric-haibin-lin Aug 2, 2017
c15ca3c
overhauled reqs for sparse operators
eric-haibin-lin Aug 6, 2017
5fd9740
patch FCompExFallback with mutable inputs. update test_optimizer with…
eric-haibin-lin Aug 7, 2017
451a8b4
change executor debug macro to env var
eric-haibin-lin Aug 7, 2017
39fac9b
add comment
eric-haibin-lin Aug 7, 2017
43bbbb8
update doc
eric-haibin-lin Aug 7, 2017
e3ccf32
change ndarray.aux_shape() to return const reference
eric-haibin-lin Aug 7, 2017
1dd4faa
remove todense to_rsp to_csr. replace with tostype
eric-haibin-lin Aug 7, 2017
26b821e
replace manual calls to cast_storage with tostype
eric-haibin-lin Aug 7, 2017
1aaf999
disable gpu fallback test for optimizer
eric-haibin-lin Aug 7, 2017
ccd1836
fix lint
eric-haibin-lin Aug 7, 2017
a9e7980
Merge branch 'pr-change' of https://github.com/eric-haibin-lin/mxnet …
eric-haibin-lin Aug 7, 2017
25d51f9
add backward pass for cast_storage. refactor cast_storage test
eric-haibin-lin Aug 7, 2017
8f54803
rand_sparse_ndarray bug fix
eric-haibin-lin Aug 7, 2017
351724d
fix cast_storage for gpu
eric-haibin-lin Aug 7, 2017
79fa6f6
disable csr test for fp16
eric-haibin-lin Aug 7, 2017
dcf1bb7
Merge branch 'pr-change' of https://github.com/eric-haibin-lin/mxnet …
eric-haibin-lin Aug 7, 2017
0569955
update row sparse ndarray doc
eric-haibin-lin Aug 8, 2017
87db8fe
Merge branch 'pr-change' of https://github.com/eric-haibin-lin/mxnet …
eric-haibin-lin Aug 8, 2017
ccc4953
update doc
eric-haibin-lin Aug 8, 2017
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
24 changes: 24 additions & 0 deletions include/mxnet/c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,30 @@ MXNET_DLL int MXCreateCachedOp(SymbolHandle handle,
* \brief free cached operator
*/
MXNET_DLL int MXFreeCachedOp(CachedOpHandle handle);
/*!
* \brief invoke cached operator
*/
MXNET_DLL int MXInvokeCachedOp(CachedOpHandle handle,
int num_inputs,
NDArrayHandle *inputs,
int *num_outputs,
NDArrayHandle **outputs);
/*!
* \brief invoke a cached op
* \param handle the handle to the cached op
* \param num_inputs number of input NDArrays
* \param inputs input NDArrays
* \param num_outputs number of output NDArrays
* \param outputs output NDArrays
* \param out_stypes output ndarrays' stypes
* \return 0 when success, -1 when failure happens
*/
MXNET_DLL int MXInvokeCachedOpEx(CachedOpHandle handle,
int num_inputs,
NDArrayHandle *inputs,
int *num_outputs,
NDArrayHandle **outputs,
const int** out_stypes);
/*!
* \brief invoke cached operator
*/
Expand Down
14 changes: 8 additions & 6 deletions include/mxnet/ndarray.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,16 @@ class NDArray {
}

/*!
* \return the shape of aux data at ith index. If it doesn't exist, return an empty one.
* \brief get the shape of aux_data(index)
* \param index the index of the aux data
* \return the shape of aux data at given index
*/
inline const TShape aux_shape(size_t i) const {
inline const TShape aux_shape(size_t index) const {

Choose a reason for hiding this comment

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

Why not return const reference and avoid construction?

CHECK(storage_type() != kDefaultStorage);
return ptr_->aux_shapes[i];
return ptr_->aux_shapes[index];
}

/* \return the shapes of all aux data */
const std::vector<TShape>& aux_shapes() const {
CHECK(storage_type() != kDefaultStorage);
return ptr_->aux_shapes;
Expand All @@ -212,8 +215,8 @@ class NDArray {
* for the final result. After the operation is done, the exact size of
* the shape is known and need to be reset using this function.
*/
inline void set_aux_shape(size_t i, const TShape& shape) const {
ptr_->set_aux_shape(i, shape);
inline void set_aux_shape(size_t index, const TShape& shape) const {
ptr_->set_aux_shape(index, shape);
}

/*!
Expand Down Expand Up @@ -851,7 +854,6 @@ size_t num_aux_data(NDArrayStorageType stype);
* \param from the ndarray we want to copy data from
* \param to the target ndarray
* \param priority Priority of the action.
* \param alloc_output whether to allocate memory for the output ndarray
* \note The function name explicitly marks the order of from and to
* due to different possible convention carried by copy function.
*/
Expand Down
15 changes: 11 additions & 4 deletions python/mxnet/_ctypes/ndarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,24 @@ def __call__(self, *args, **kwargs):
"CachedOp.__call__ got unexpected keyword argument(s): " + \
', '.join(kwargs.keys()))

check_call(_LIB.MXInvokeCachedOp(
# return output stypes to avoid the c_api call for checking
# a handle's stype in _ndarray_cls
out_stypes = ctypes.POINTER(ctypes.c_int)()

check_call(_LIB.MXInvokeCachedOpEx(
self.handle,
ctypes.c_int(len(args)),
c_array(NDArrayHandle, [arr.handle for arr in args]),
ctypes.byref(num_output),
ctypes.byref(output_vars)))
ctypes.byref(output_vars),
ctypes.byref(out_stypes)))

if original_output is not None:
return original_output
if num_output.value == 1:
return _ndarray_cls(ctypes.cast(output_vars[0], NDArrayHandle))
return _ndarray_cls(ctypes.cast(output_vars[0], NDArrayHandle),
stype=_STORAGE_TYPE_ID_TO_STR[out_stypes[0]])
else:
return [_ndarray_cls(ctypes.cast(output_vars[i], NDArrayHandle))
return [_ndarray_cls(ctypes.cast(output_vars[i], NDArrayHandle),
stype=_STORAGE_TYPE_ID_TO_STR[out_stypes[i]])
for i in range(num_output.value)]
3 changes: 2 additions & 1 deletion python/mxnet/kvstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,11 +255,12 @@ def row_sparse_pull(self, key, out=None, priority=0, row_ids=None):
row_ids : NDArray or list of NDArray
The row_ids for which to pull for each value. The row_ids doesn't have to be unique
or sorted.
Examples
--------
>>> shape = (3, 3)
>>> kv.init('3', mx.nd.ones(shape)._to_rsp())
>>> a = mx.nd.zeros(shape)
>>> a = mx.nd.zeros(shape, stype='row_sparse')
>>> row_ids = mx.nd.array([0, 2], dtype='int64')
>>> kv.row_sparse_pull('3', out=a, row_ids=row_ids)
>>> print a.asnumpy()
Expand Down
4 changes: 2 additions & 2 deletions python/mxnet/ndarray/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
from .op import CachedOp
# pylint: disable=wildcard-import
from .ndarray import *
from .ndarray_utils import load, save, zeros, empty, array
from .utils import load, save, zeros, empty, array
from .sparse_ndarray import _ndarray_cls, todense
from .sparse_ndarray import csr, row_sparse, BaseSparseNDArray, RowSparseNDArray, CSRNDArray
from .sparse_ndarray import csr_matrix, row_sparse_array, BaseSparseNDArray, RowSparseNDArray, CSRNDArray
71 changes: 44 additions & 27 deletions python/mxnet/ndarray/sparse_ndarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ def _aux_data(self, i):

# pylint: disable=abstract-method
class CSRNDArray(BaseSparseNDArray):
"""A CSRNDArray represents a NDArray as three separate arrays: `data`,
"""A sparse representation of tensor in standard CSR format.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to change tensor to matrix since csr only represents matrices?

Also, "in the standard CSR format."

Copy link
Owner Author

Choose a reason for hiding this comment

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

what about A sparse representation of a 2D NDArray?


A CSRNDArray represents an NDArray as three separate arrays: `data`,
`indptr` and `indices`. It uses the standard CSR representation where the column indices for
row i are stored in indices[indptr[i]:indptr[i+1]] and their corresponding values are stored
in values[indptr[i]:indptr[i+1]].
Expand Down Expand Up @@ -269,13 +271,16 @@ def __getitem__(self, key):

Examples
--------
>>> x = mx.nd.zeros((2, 3), stype='csr')
>>> x[:] = mx.nd.arange(0,6).reshape((2,3))
>>> x.asnumpy()
array([[ 0., 1., 2.],
[ 3., 4., 5.]], dtype=float32)
>>> x[1:2].asnumpy()
array([[ 3., 4., 5.]], dtype=float32)
>>> indptr = np.array([0, 2, 3, 6])
>>> indices = np.array([0, 2, 2, 0, 1, 2])
>>> data = np.array([1, 2, 3, 4, 5, 6])
>>> a = mx.nd.csr_matrix(data, indptr, indices, (3, 3))
>>> a.asnumpy()
array([[1, 0, 2],
[0, 0, 3],
[4, 5, 6]])
>>> a[1:2].asnumpy()
array([[0, 0, 3]], dtype=float32)
"""
if isinstance(key, int):
raise ValueError("__getitem__ with int key is not implemented for CSRNDArray")
Expand Down Expand Up @@ -420,26 +425,34 @@ def copyto(self, other):

# pylint: disable=abstract-method
class RowSparseNDArray(BaseSparseNDArray):
"""A RowSparseNDArray is typically used to represent a subset of a larger
NDArray with `default` of shape [LARGE0, D1, .. , DN] where LARGE0 >> D0. The values
in indices are the indices in the first dimension of the slices that have been extracted from
the larger NDArray. The indices are expected to be sorted in ascending order.
"""A sparse representation of a set of tensor slices at given indices.

The corresponding NDArray ``dense`` with `default` storage represented by a ``rsp``
RowSparseNDArray
A RowSparseNDArray represents a multidimensional NDArray as two separate arrays: `data` and
Copy link
Collaborator

Choose a reason for hiding this comment

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

"as" -> "using"

`indices`. The `indices` stores the indices of the multidimensional `data` slices extracted
from the dense NDArray in the first dimension. The corresponding NDArray ``dense``
represented by RowSparseNDArray ``rsp`` has

``dense[rsp.indices[i], :, :, :, ...] = rsp.data[i, :, :, :, ...]``,

``dense[rsp.indices[i], :, :, :, ...] = rsp.values[i, :, :, :, ...]``
where `indices` is an 1-D integer NDArray with shape [D0], and `data` is an NDArray of any
dtype with shape [D0, D1, .., DK]. If the index of a slice in the first dimension
doesn't appear in `indices`, its values are zeros.

A RowSparseNDArray is typically used to represent a subset of a larger dense NDArray of
shape [LARGE0, D1, .. , DK] where LARGE0 >> D0 and most row slices are zeros.

The indices are expected to be sorted in ascending order.

RowSparseNDArray is used principally in the definition of gradients for operations
that have sparse gradients (e.g. dot with sparse inputs).

Examples
--------
>>> import mxnet as mx
>>> dense = mx.nd.array([[1,2],[0,0],[3,0],[0,0]])
>>> dense = mx.nd.array([[1,2],[0,0],[3,0],[0,0],[0,0],[0,0]])
>>> rsp = dense._to_rsp()
>>> rsp.indices.asnumpy()
array([0, 2], dtype=int32)
array([0, 2], dtype=int64)
>>> rsp.data.asnumpy()
array([[ 1., 2.],
[ 3., 0.]], dtype=float32)
Expand Down Expand Up @@ -608,6 +621,9 @@ def copyto(self, other):
raise TypeError('copyto does not support type ' + str(type(other)))

def _prepare_src_array(src, dtype, default_dtype):
"""Prepare `src` and its dtype so that they can be used to construct NDArray.
`src` is converted to a `np.ndarray` if it's neither an `NDArray` nor an `np.ndarray`.
"""
if isinstance(src, NDArray):
dtype = src.dtype if dtype is None else dtype
else:
Expand All @@ -620,8 +636,9 @@ def _prepare_src_array(src, dtype, default_dtype):
return src, dtype


def csr(data, indptr, indices, shape, ctx=None, dtype=None, indptr_type=None, indices_type=None):
"""Creates a 2D array with compressed sparse row format.
def csr_matrix(data, indptr, indices, shape, ctx=None, dtype=None, indptr_type=None,
indices_type=None):
"""Creates a 2D array with compressed sparse row(CSR) format.

Parameters
----------
Expand All @@ -640,10 +657,10 @@ def csr(data, indptr, indices, shape, ctx=None, dtype=None, indptr_type=None, in
if `values` is an `NDArray`, `float32` otherwise.
indptr_type: str or numpy.dtype, optional
The data type of the indices array. The default dtype is ``indptr.dtype``
if `indptr` is an `NDArray`, `int32` otherwise.
if `indptr` is an `NDArray`, `int64` otherwise.
indices_type: str or numpy.dtype, optional
The data type of the indices array. The default dtype is ``indices.dtype``
if `indicies` is an `NDArray`, `int32` otherwise.
if `indicies` is an `NDArray`, `int64` otherwise.

Returns
-------
Expand All @@ -653,7 +670,7 @@ def csr(data, indptr, indices, shape, ctx=None, dtype=None, indptr_type=None, in
Example
-------
>>> import mxnet as mx
>>> a = mx.nd.csr([1, 2, 3], [0, 1, 2, 2, 3], [1, 0, 2], (4, 3))
>>> a = mx.nd.csr_matrix([1, 2, 3], [0, 1, 2, 2, 3], [1, 0, 2], (4, 3))
>>> a.asnumpy()
array([[ 0., 1., 0.],
[ 2., 0., 0.],
Expand Down Expand Up @@ -696,13 +713,13 @@ def csr(data, indptr, indices, shape, ctx=None, dtype=None, indptr_type=None, in
return result


def row_sparse(data, indices, shape, ctx=None, dtype=None, indices_type=None):
"""Creates a row sparse array with a set of tensor slices at given indices.
def row_sparse_array(data, indices, shape, ctx=None, dtype=None, indices_type=None):
"""Creates a multidimensional row sparse array with a set of tensor slices at given indices.

Parameters
----------
data: array_like
An object exposing the array interface, with shape [D0, D1, .. Dn], where D0 is
An object exposing the array interface, with shape [D0, D1, .. DK], where D0 is
the number of rows with non-zeros entries.
indices: array_like
An object exposing the array interface, with shape [D0].
Expand All @@ -713,7 +730,7 @@ def row_sparse(data, indices, shape, ctx=None, dtype=None, indices_type=None):
if `data` is an `NDArray`, `float32` otherwise.
indices_type: str or numpy.dtype, optional
The data type of the indices array. The default dtype is ``indices.dtype``
if `indicies` is an `NDArray`, `int32` otherwise.
if `indicies` is an `NDArray`, `int64` otherwise.

Returns
-------
Expand All @@ -722,7 +739,7 @@ def row_sparse(data, indices, shape, ctx=None, dtype=None, indices_type=None):

Example
-------
>>> a = mx.nd.row_sparse([[1, 2], [3, 4]], [1, 4], (6, 2))
>>> a = mx.nd.row_sparse_array([[1, 2], [3, 4]], [1, 4], (6, 2))
>>> a.asnumpy()
array([[ 0., 0.],
[ 1., 2.],
Expand Down
File renamed without changes.
6 changes: 2 additions & 4 deletions python/mxnet/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ class SGD(Optimizer):
state = momentum * state + lr * rescale_grad * clip(grad, clip_gradient) + wd * weight
weight = weight - state

For details of the update algorithm see :class:`~mxnet.ndarray.sgd_update` and
:class:`~mxnet.ndarray.sgd_mom_update`.
Sparse updating is supported. For details of the update algorithm see
:class:`~mxnet.ndarray.sgd_update` and :class:`~mxnet.ndarray.sgd_mom_update`.

This optimizer accepts the following parameters in addition to those accepted
by :class:`.Optimizer`.
Expand All @@ -348,8 +348,6 @@ def create_state(self, index, weight):
momentum = None
weight_master_copy = None
if self.multi_precision and weight.dtype == numpy.float16:
assert(weight.stype == 'default'), \
"multi-precision doesn't supprot non-default weight yet"
weight_master_copy = array(weight, ctx=weight.context, dtype=numpy.float32)
if self.momentum != 0.0:
momentum = zeros(weight.shape, weight.context, dtype=numpy.float32,
Expand Down
16 changes: 8 additions & 8 deletions python/mxnet/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,34 +79,34 @@ def random_sample(population, k):
return population_copy[0:k]


def rand_sparse_ndarray(shape, stype, density=None):
def rand_sparse_ndarray(shape, stype, density=None, dtype=None):
"""Generate a random sparse ndarray. Returns the ndarray, value(np) and indices(np) """
density = rnd.rand() if density is None else density
if stype == 'row_sparse':
# sample index
idx_sample = rnd.rand(shape[0])
indices = np.argwhere(idx_sample < density).flatten()
if indices.shape[0] == 0:
result = mx.nd.zeros(shape, stype='row_sparse')
result = mx.nd.zeros(shape, stype='row_sparse', dtype=dtype)
return result, (np.array([], dtype='int64'), np.array([], dtype='int64'))
# generate random values
val = rnd.rand(indices.shape[0], *shape[1:])
arr = mx.nd.row_sparse(val, indices, shape, indices_type=np.int64)
val = rnd.rand(indices.shape[0], *shape[1:]).astype(dtype)
arr = mx.nd.row_sparse_array(val, indices, shape, indices_type=np.int64, dtype=dtype)
return arr, (val, indices)
elif stype == 'csr':
assert(len(shape) == 2)
csr = sp.rand(shape[0], shape[1], density=density, format='csr')
result = mx.nd.csr(csr.data, csr.indptr, csr.indices, shape)
result = mx.nd.csr_matrix(csr.data, csr.indptr, csr.indices, shape, dtype=dtype)
return result, (csr.indptr, csr.indices, csr.data)
else:
assert(False), "unknown storage type"


def rand_ndarray(shape, stype, density=None):
def rand_ndarray(shape, stype, density=None, dtype=None):
if stype == 'default':
arr = mx.nd.array(random_arrays(shape))
arr = mx.nd.array(random_arrays(shape), dtype=dtype)
else:
arr, _ = rand_sparse_ndarray(shape, stype, density=density)
arr, _ = rand_sparse_ndarray(shape, stype, density=density, dtype=dtype)
return arr


Expand Down
18 changes: 18 additions & 0 deletions src/c_api/c_api_ndarray.cc
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,24 @@ int MXInvokeCachedOp(CachedOpHandle handle,
API_END();
}

int MXInvokeCachedOpEx(CachedOpHandle handle,
int num_inputs,
NDArrayHandle *inputs,
int *num_outputs,
NDArrayHandle **outputs,
const int **out_stypes) { // outputs storage types
API_BEGIN();
MXInvokeCachedOp(handle, num_inputs, inputs, num_outputs, outputs);
MXAPIThreadLocalEntry *ret = MXAPIThreadLocalStore::Get();
NDArray** output_nds = reinterpret_cast<NDArray**>(*outputs);
ret->out_types.resize(*num_outputs);
for (int i = 0; i < *num_outputs; ++i) {
ret->out_types[i] = output_nds[i]->storage_type();
}
*out_stypes = dmlc::BeginPtr(ret->out_types);
API_END();
}

int MXAutogradSetIsTraining(int is_training, int* prev) {
API_BEGIN();
*prev = AutogradRuntime::Get()->SetIsTraining(static_cast<bool>(is_training));
Expand Down
2 changes: 1 addition & 1 deletion src/common/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ inline bool SetupDefaultBlobs(const std::vector<NDArray>& src,
if (idx_map != nullptr) {
(*idx_map)[i] = temp_dst->size();
}
NDArray temp(nd.shape(), nd.ctx(), false);
NDArray temp(nd.shape(), nd.ctx(), false, nd.dtype());
temp_src->emplace_back(nd);
temp_dst->emplace_back(temp);
blobs->emplace_back(temp.data());
Expand Down
Loading