Skip to content

Add support for cupyx sparse to dask.array.dot#6846

Merged
martindurant merged 5 commits intodask:masterfrom
anaruse:support_scipy_sparse
Nov 26, 2020
Merged

Add support for cupyx sparse to dask.array.dot#6846
martindurant merged 5 commits intodask:masterfrom
anaruse:support_scipy_sparse

Conversation

@anaruse
Copy link
Copy Markdown
Contributor

@anaruse anaruse commented Nov 17, 2020

This addresses #6820 and makes it available to run dask.array.dot with cupyx sparse (and scipy sparse as well).

import dask.array as da

import cupy as xp
from cupyx.scipy.sparse import csr_matrix
# import numpy as xp
# from scipy.sparse import csr_matrix

matrix_type = csr_matrix
print('matrix_type: {}'.format(matrix_type))

dtype = 'f'
x = xp.arange(24, dtype=dtype).reshape(4, 6)
w = xp.arange(18, dtype=dtype).reshape(6, 3)
ref = xp.dot(x, w)
print('ref:\n{}'.format(ref))

dx = da.from_array(x, chunks=(2, 3), asarray=False, fancy=False)
dx = dx.map_blocks(matrix_type, dtype=dtype)

dw = da.from_array(w, chunks=(3, 1), asarray=False, fancy=False)
dw = dw.map_blocks(matrix_type, dtype=dtype)

ret = da.dot(dx, dw).compute()
print('type(ret): {}'.format(type(ret)))
print('ret:\n{}'.format(ret.todense()))
Click to see the output with current master
matrix_type: <class 'cupyx.scipy.sparse.csr.csr_matrix'>
ref:
[[ 165.  180.  195.]
 [ 435.  486.  537.]
 [ 705.  792.  879.]
 [ 975. 1098. 1221.]]
Traceback (most recent call last):
  File "test-cupyx-sparse.py", line 23, in <module>
    ret = da.dot(dx, dw).compute()
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/base.py", line 224, in compute
    (result,) = compute(self, traverse=False, **kwargs)
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/base.py", line 512, in compute
    results = schedule(dsk, keys, **kwargs)
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/threaded.py", line 84, in get
    **kwargs
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/local.py", line 486, in get_async
    raise_exception(exc, tb)
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/local.py", line 316, in reraise
    raise exc
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/local.py", line 222, in execute_task
    result = _execute_task(task, data)
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/core.py", line 121, in _execute_task
    return func(*(_execute_task(a, cache) for a in args))
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/optimization.py", line 963, in __call__
    return core.get(self.dsk, self.outkey, dict(zip(self.inkeys, args)))
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/core.py", line 151, in get
    result = _execute_task(task, cache)
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/core.py", line 121, in _execute_task
    return func(*(_execute_task(a, cache) for a in args))
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/core.py", line 121, in <genexpr>
    return func(*(_execute_task(a, cache) for a in args))
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/core.py", line 115, in _execute_task
    return [_execute_task(a, cache) for a in arg]
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/core.py", line 115, in <listcomp>
    return [_execute_task(a, cache) for a in arg]
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/core.py", line 121, in _execute_task
    return func(*(_execute_task(a, cache) for a in args))
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/utils.py", line 29, in apply
    return func(*args, **kwargs)
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/dask-2.30.0+57.g3e264491-py3.7.egg/dask/array/routines.py", line 229, in _tensordot
    x = tensordot(a, b, axes=axes)
  File "<__array_function__ internals>", line 6, in tensordot
  File "/home/anaruse/.pyenv/versions/miniconda3-4.7.12/lib/python3.7/site-packages/numpy/core/numeric.py", line 1075, in tensordot
    if as_[axes_a[k]] != bs[axes_b[k]]:
IndexError: tuple index out of range
Click to see the output with this PR
matrix_type: <class 'cupyx.scipy.sparse.csr.csr_matrix'>
ref:
[[ 165.  180.  195.]
 [ 435.  486.  537.]
 [ 705.  792.  879.]
 [ 975. 1098. 1221.]]
type(ret): <class 'cupyx.scipy.sparse.coo.coo_matrix'>
ret:
[[ 165.  180.  195.]
 [ 435.  486.  537.]
 [ 705.  792.  879.]
 [ 975. 1098. 1221.]]

@jsignell
Copy link
Copy Markdown
Member

Pinging @jakirkham to review if you get a chance.

@mrocklin
Copy link
Copy Markdown
Member

mrocklin commented Nov 17, 2020 via email

Copy link
Copy Markdown
Member

@pentschev pentschev left a comment

Choose a reason for hiding this comment

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

The changes look good to me overall, I've added some suggestions though.


concatenate_lookup.register(scipy.sparse.spmatrix, _concatenate)

def _tensordot(a, b, axes):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems like this implementation is exactly the same as the one above, is that correct? I would suggest we have the implementation on a separate function and have both lookups calling that function, this will simplify future maintenance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That is correct. I will fix it to use the same implementation for CuPy sparse and SciPy sparse.

for a in sorted(axes[0]):
ind.insert(a, None)
x = x[tuple(ind)]
if len(axes[0]) != 1:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are changes in this file a general Dask issue/limitation or are these specific for CuPy and SciPy? Dask can also use PyData's Sparse, so perhaps adding a test to cover these changes for CuPy in https://github.com/dask/dask/blob/master/dask/array/tests/test_cupy.py and PyData Sparse in https://github.com/dask/dask/blob/master/dask/array/tests/test_sparse.py would be good.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This change affects not only CuPy and SciPy but also others, so I will add tests to check that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't think we need to add a test for PyData Sparse, as there already seems to be a test for tensordot.

def test_tensordot():
x = da.random.random((2, 3, 4), chunks=(1, 2, 2))
x[x < 0.8] = 0
y = da.random.random((4, 3, 2), chunks=(2, 2, 1))
y[y < 0.8] = 0
xx = x.map_blocks(sparse.COO.from_numpy)
yy = y.map_blocks(sparse.COO.from_numpy)
assert_eq(da.tensordot(x, y, axes=(2, 0)), da.tensordot(xx, yy, axes=(2, 0)))
assert_eq(da.tensordot(x, y, axes=(1, 1)), da.tensordot(xx, yy, axes=(1, 1)))
assert_eq(
da.tensordot(x, y, axes=((1, 2), (1, 0))),
da.tensordot(xx, yy, axes=((1, 2), (1, 0))),
)

@anaruse
Copy link
Copy Markdown
Contributor Author

anaruse commented Nov 18, 2020

Thanks for your comment, @pentschev ! I've fixed the PR based on your suggestions, so could you take a look?

Copy link
Copy Markdown
Member

@pentschev pentschev left a comment

Choose a reason for hiding this comment

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

Changes look good to me, thanks @anaruse ! :)

From my side, this is good to be merged, could someone with merge rights take a final look, maybe @mrocklin ?

@martindurant martindurant merged commit fbccc4e into dask:master Nov 26, 2020
@martindurant
Copy link
Copy Markdown
Member

Thanks @anaruse !

@tomwhite
Copy link
Copy Markdown
Contributor

Unfortunately this introduces memory issues for general Dask users due to the use of concatenate=True (there's some related discussion in the context of matmul in #6874). Is the change to use concatenate=True necessary for cupyx sparse, or is it an unrelated change?

@anaruse
Copy link
Copy Markdown
Contributor Author

anaruse commented Nov 30, 2020

It is a necessary change for cupyx (and scipy) sparse...
Because cupyx sparse only support 2D arrays, you can't adopt the method of reducing the dimension(s) with sum call after dot , which is being discussed in #6874.

@tomwhite
Copy link
Copy Markdown
Contributor

Thanks for the clarification @anaruse - I think we need some way of avoiding concatenate=True in the non-cupyx case though due to the memory scalability issues it introduces.

@ravwojdyla
Copy link
Copy Markdown
Contributor

We could have a special case for sparse array (based on type) that introduces the contraction in _tensordot only for sparse arrays? But I wonder if there is a more generic solution 🤔

@jakirkham
Copy link
Copy Markdown
Member

Could someone please file a new issue with an (ideally not too large) example showing the issue?

@tomwhite
Copy link
Copy Markdown
Contributor

tomwhite commented Dec 1, 2020

I filed #6916 with an example for this issue.

jsignell pushed a commit that referenced this pull request Mar 15, 2022
This is a quick fix for issue #6916

It removes the memory problem for non-sparse array types. Note that tensordot with sparse arrays still needs a proper fix for the memory issues observed after #6846
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants