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

[Sparse] add sparse tensor computation support #1289

Merged
merged 39 commits into from
Sep 6, 2018
Merged

Conversation

liangfu
Copy link
Member

@liangfu liangfu commented Jun 15, 2018

This PR implements following features:

  • Create tvm.contrib.sparse.CSRNDArray and tvm.contrib.sparse.placeholder for creating sparse tensor.
  • Support conversion between numpy.ndarray and tvm.contrib.sparse.CSRNDArray
  • Support topi.sparse.csrmv and topi.sparse.csrmm as SpMV and SpMM, and check correctness with dense tensor operations.
  • Enable dense operator for both sparse input and sparse weights

Here is a table that briefly shows performance between numpy.dot and topi.sparse.csrmm.

SpM M Sparsity openblas tvm speedup
512x512 512x512 50% 3.46 ms 3.20 ms 1.08x
512x512 512x512 80% 2.72 ms 1.21 ms 2.06x

The timing results are base on average of 10 iterations.

@tqchen
Copy link
Member

tqchen commented Jun 15, 2018

Supporting sparse matrix is great. This is a major change, please send an RFC so we can have more discussion before we actually start working on implementation.

@liangfu
Copy link
Member Author

liangfu commented Jun 26, 2018

As the intention of this PR is to provide a basic structure and prove it is possible to perform sparse matrix computation upon TVM Stack, and this is done by using existing Tensor and dynamic memory allocation, I think this PR is ready to merge at the moment. Please review.

@liangfu liangfu changed the title [WIP] [Sparse] add storage type support for sparse matrices [Sparse] add sparse tensor computation support Jun 27, 2018
@tqchen
Copy link
Member

tqchen commented Jul 19, 2018

@eric-haibin-lin can you help review this PR?

@tqchen
Copy link
Member

tqchen commented Jul 19, 2018

Sorry for the delayed review @liangfu I take a brief look, and I think the current way of constructing through IR Builder is ok, but nevertheless may loss some of the benefit of scheduling.

On the otherhand, it will be quite interesting if we pick specific sparse operators we are interested in and being used in real nn and push perf of that

@eric-haibin-lin
Copy link
Member

Yes, I'll take a look.

On the second point, sparse block net https://arxiv.org/pdf/1801.02108.pdf would be quite interesting, although it

  • requires sparsification during training
  • requires a sparse block format, which is different from csr

@liangfu
Copy link
Member Author

liangfu commented Jul 19, 2018

Thanks for your attention.
As we proposed in #1291 , we will add support to demonstrate sparse operators for real CNNs based on this PR.
I'm currently interested in reproducing https://arxiv.org/abs/1608.01409 in TVM Stack, because it provides source code and demonstration.

Copy link
Member

@eric-haibin-lin eric-haibin-lin left a comment

Choose a reason for hiding this comment

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

Nice effort. I'm new to TVM and hope if you guys can answer some of my questions

csr = "csr"

@register_node
class CSRNDArray(object):
Copy link
Member

Choose a reason for hiding this comment

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

Integration with external functions:
DLPack only supports dense array blob. How would this class support invoking cusparse/mkl-sparse-blas functions? Or is it never possible if we don't enable DLPack to pack sparse arrays?

Copy link
Member

Choose a reason for hiding this comment

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

What would be the pro and cons if it inherits NDArrayBase and throw exceptions on unimplemented method?

Copy link
Member Author

@liangfu liangfu Jul 20, 2018

Choose a reason for hiding this comment

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

Support invoking cusparse/mkl-sparse-blas functions?

I think we should keep these in mind. Let me give it a try and give you a definite answer later.

Will this bring changes to dlpack?

There is no need to change dlpack at the moment. @tqchen suggested that discussions are need before adding sparse matrix support into dlpack.

Inherit NDArrayBase and throw exceptions on unimplemented method?

Good suggestion. However, there would be too many changes at the moment, and csr_matrix is not really N-dimensional. Let's discuss what should be a proper way to make this change.

@register_node
class CSRNDArray(object):
"""Sparse tensor object in CSR format."""
def __init__(self, source_array=None,
Copy link
Member

Choose a reason for hiding this comment

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

Did you consider scipy.sparse style interface, where the 1st argument could either be a numpy array or tuples of (data, idx, indptr)? https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html

full[ridx, self.indices.asnumpy().astype('int32')] = self.data.asnumpy()
return full

def array(source_array, ctx=None):
Copy link
Member

Choose a reason for hiding this comment

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

Rename to csr_matrix? sparse.array could potentially create arrays of other sparse formats, including csr, csc, block sparse, etc.

Copy link
Member Author

Choose a reason for hiding this comment

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

Is this really necessary? I was trying to make the interface consistent between tvm.array and tvm.contrib.sparse.array. In the test case, I demonstrated that if I use

import tvm.contrib.sparse as tvmsp

Users could easily change their previous code writing in tvm.array to tvmsp.array with no extra effort. If we need to add support for other sparse formats, I would suggest adding an extra argument to specify the format and leave its default to csr.


Parameters
----------
data : tvm.contrib.CSRTensor
Copy link
Member

Choose a reason for hiding this comment

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

incorrect name: CSRTensor


Returns
-------
output : tvm.Tensor
Copy link
Member

Choose a reason for hiding this comment

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

Some sparse ops return sparse result (for example, csr+csr=csr). Would that be supported by specifying sparse placeholder in the IR?
I'm not familiar with tvm memory management, does it support operators with uncertain output shape? The data and indices buffer may have to be resized if we coalesce entries of the same indices when adding two csr matrices.

Copy link
Member Author

Choose a reason for hiding this comment

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

Would that be supported by specifying sparse placeholder in the IR?

Sparse results are not specified with PlaceholderOp, they are allocated with ExternOp or ComputeOp, I think.

Does it support operators with uncertain output shape?

I have not tried (csr+csr=csr) yet, however, I think tvm requires an estimate of the buffer size before fill-in the values.

self.name = name
self.stype = stype
self.data = _api.placeholder((nonzeros,), dtype=dtype, name=self.name+'_data')
self.indices = _api.placeholder((nonzeros,), dtype='int32', name=self.name+'_indices')
Copy link
Member

Choose a reason for hiding this comment

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

In some extreme cases people might want to use int64 for indices dtype (for ads/recommendation) because the number of features goes up to 10 billion. Does any code assume the dtype of indices/indptr is always int32?

@liangfu
Copy link
Member Author

liangfu commented Jul 20, 2018

Let me summarize the suggested changes as below:

Suggested Changes

  • Consider scipy.sparse style interface in tvm.contrib.sparse.CSRNDArray.
  • Fix incorrect name tvm.contrib.NDTensor in topi/python/topi/sparse/*.py, and improve the comments.
  • Ensure there is no code assume the dtype of indices/indptr is always int32.

Additional Changes

  • Enable dense operator for both sparse input and sparse weights

Unchanged Items

  • Invoking cusparse/mkl-sparse-blas functions is supported in tvm.contrib.sparse.array? In my observation, there is no easy way to check whether these functions could be supported.
  • Make tvm.contrib.sparse.CSRNDArray inherits tvm.NDArrayBase ? The advantage of making this change would bring abstraction layer between dense ndarray and sparse ndarray. However, after I tried to make the change for a while, I don't think CSRNDArray can be implemented to inherit NDArrayBase without changes to NDArrayBase itself, and the program terminated unexpectedly.
  • To make the interface consistent between tvm.array and tvmsp.array,
    I don't think tvm.contrib.sparse.array should be renamed to tvm.contrib.sparse.csr_matrix.

@liangfu
Copy link
Member Author

liangfu commented Aug 1, 2018

Hi @tqchen @eric-haibin-lin , as summarized above, most suggested changes have been made to reflect the suggestions in the review; and some items that are not changed are explained as well. Please take another review to see whether current PR is suitable for a merge.

In the long run, as suggested by @tqchen , we would port current implementation to real neural networks. I've create a experimental repo and trained a sparse mlp. The dense operator proposed in this PR can be used to perform inference in the sparse mlp.

@yzhliu
Copy link
Member

yzhliu commented Aug 6, 2018

Please rebase according to #1394
Sorry for the inconvenience.

@yzhliu yzhliu added the status: need update need update based on feedbacks label Aug 13, 2018
@yzhliu
Copy link
Member

yzhliu commented Aug 13, 2018

@liangfu Could you update according to Haibin's comments?

@liangfu
Copy link
Member Author

liangfu commented Aug 16, 2018

yes, definitely.

@liangfu
Copy link
Member Author

liangfu commented Aug 16, 2018

List of Changes

  • Remove unused codeitype = int64
  • Now, we don't need the stype argument for CSRPlaceholderOp; and for placeholder, the default stype would be csr, it could be any other format if we could have them defined.
  • SparsePlaceholderOp class have been created and inherited.
  • conflict with upstream have been fixed.

I'm not quite sure whether it's okay to have duplicated entries in CSR, like what have been implemented in scipy.

@eric-haibin-lin @yzhliu @tqchen I think most of the concerns have been covered by now. Please check out whether this is suitable for a merge.

raise NotImplementedError('stype=%s is not supported yet.' % (stype,))
return ret

@register_node
Copy link
Member

Choose a reason for hiding this comment

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

do not need register_node for now if it is not part of node system

@tqchen
Copy link
Member

tqchen commented Aug 21, 2018

Copy link
Member

@yzhliu yzhliu left a comment

Choose a reason for hiding this comment

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

Sorry for late review.
The functionality looks good to me. Wait for @eric-haibin-lin 's confirming.

The name hint of the tensor

stype: str, optional
The name storage type of the sparse tensor (e.g. csr, coo, ell)
Copy link
Member

Choose a reason for hiding this comment

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

missing doc for nonzeros. so as that in SparsePlaceholderOp and CSRPlaceholderOp.


class SparsePlaceholderOp(object):
"""Placeholder class for sparse tensor representations."""
def __init__(self, shape, nonzeros, dtype, name):
Copy link
Member

Choose a reason for hiding this comment

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

shall we store nonzeros?

Copy link
Member Author

Choose a reason for hiding this comment

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

I left it unused intentionally.

dtype = float32 if dtype is None else dtype
stype = csr if stype is None else stype
ret = None
if stype == 'csr':
Copy link
Member

Choose a reason for hiding this comment

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

'csr' -> csr. Or just remove the constant def, always use str, actually I prefer the later.

The shape of the array
"""
if isinstance(arg1, tuple):
self.data, self.indices, self.indptr = arg1[0], arg1[1], arg1[2]
Copy link
Member

Choose a reason for hiding this comment

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

better to assert len(arg1) == 3 and do self.data, self.indices, self.indptr = arg1.
also have a better name for arg1

Copy link
Member Author

Choose a reason for hiding this comment

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

okay, i'll fix this.
a better name didn't come into my mind, the name arg1 was inspired by scipy.sparse.csr_matrix.

Copy link
Member

@yzhliu yzhliu left a comment

Choose a reason for hiding this comment

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

@tqchen Would you mind approve and merge if it is good to you?

@liangfu
Copy link
Member Author

liangfu commented Sep 6, 2018

@tqchen Would you mind approve and merge if it is good to you?

@tqchen
Copy link
Member

tqchen commented Sep 6, 2018

Thanks @liangfu @eric-haibin-lin @yzhliu , this is now merged

@tqchen tqchen merged commit d87c94d into apache:master Sep 6, 2018
@ajtulloch
Copy link
Contributor

This looks really cool, well done @liangfu.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: need review status: need update need update based on feedbacks
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants