-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Conversation
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. |
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. |
@eric-haibin-lin can you help review this PR? |
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 |
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
|
Thanks for your attention. |
There was a problem hiding this 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
python/tvm/contrib/sparse.py
Outdated
csr = "csr" | ||
|
||
@register_node | ||
class CSRNDArray(object): |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
python/tvm/contrib/sparse.py
Outdated
@register_node | ||
class CSRNDArray(object): | ||
"""Sparse tensor object in CSR format.""" | ||
def __init__(self, source_array=None, |
There was a problem hiding this comment.
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
python/tvm/contrib/sparse.py
Outdated
full[ridx, self.indices.asnumpy().astype('int32')] = self.data.asnumpy() | ||
return full | ||
|
||
def array(source_array, ctx=None): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
topi/python/topi/sparse/csrmv.py
Outdated
|
||
Parameters | ||
---------- | ||
data : tvm.contrib.CSRTensor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
incorrect name: CSRTensor
topi/python/topi/sparse/csrmv.py
Outdated
|
||
Returns | ||
------- | ||
output : tvm.Tensor |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
python/tvm/contrib/sparse.py
Outdated
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') |
There was a problem hiding this comment.
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?
Let me summarize the suggested changes as below: Suggested Changes
Additional Changes
Unchanged Items
|
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. |
Please rebase according to #1394 |
@liangfu Could you update according to Haibin's comments? |
yes, definitely. |
Conflicts: topi/python/topi/__init__.py
List of Changes
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. |
python/tvm/contrib/sparse.py
Outdated
raise NotImplementedError('stype=%s is not supported yet.' % (stype,)) | ||
return ret | ||
|
||
@register_node |
There was a problem hiding this comment.
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
There was a problem hiding this 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) |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shall we store nonzeros
?
There was a problem hiding this comment.
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.
python/tvm/contrib/sparse.py
Outdated
dtype = float32 if dtype is None else dtype | ||
stype = csr if stype is None else stype | ||
ret = None | ||
if stype == 'csr': |
There was a problem hiding this comment.
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.
python/tvm/contrib/sparse.py
Outdated
The shape of the array | ||
""" | ||
if isinstance(arg1, tuple): | ||
self.data, self.indices, self.indptr = arg1[0], arg1[1], arg1[2] |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
.
There was a problem hiding this 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?
@tqchen Would you mind approve and merge if it is good to you? |
Thanks @liangfu @eric-haibin-lin @yzhliu , this is now merged |
This looks really cool, well done @liangfu. |
This PR implements following features:
tvm.contrib.sparse.CSRNDArray
andtvm.contrib.sparse.placeholder
for creating sparse tensor.numpy.ndarray
andtvm.contrib.sparse.CSRNDArray
topi.sparse.csrmv
andtopi.sparse.csrmm
as SpMV and SpMM, and check correctness with dense tensor operations.Here is a table that briefly shows performance between
numpy.dot
andtopi.sparse.csrmm
.The timing results are base on average of 10 iterations.