You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
apply_ufunc support for chunks on input_core_dims #1995
where a and b are xarray.DataArray's, both with dimension x and both with dask backend.
I successfully obtained a 5.5x speedup with the following:
@numba.guvectorize(['void(float64[:], float64[:], float64[:])'], '(n),(n)->()', nopython=True, cache=True)
def mulsum(a, b, res):
acc = 0
for i in range(a.size):
acc += a[i] * b[i]
res.flat[0] = acc
c = xarray.apply_ufunc(
mulsum, a, b,
input_core_dims=[['x'], ['x']],
dask='parallelized', output_dtypes=[float])
The problem is that this introduces a (quite problematic, in my case) constraint that a and b can't be chunked on dimension x - which is theoretically avoidable as long as the kernel function doesn't need interaction between x[i] and x[j] (e.g. it can't work for an interpolator, which would require to rely on dask ghosting).
Proposal
Add a parameter to apply_ufunc, reduce_func=None. reduce_func is a function which takes as input two parameters a, b that are the output of func. apply_ufunc will invoke it whenever there's chunking on an input_core_dim.
e.g. my use case above would simply become:
c = xarray.apply_ufunc(
mulsum, a, b,
input_core_dims=[['x'], ['x']],
dask='parallelized', output_dtypes=[float], reduce_func=operator.sum)
So if I have 2 chunks in a and b on dimension x, apply_ufunc will internally do
Note that reduce_func will be invoked exclusively in presence of dask='parallelized' and when there's chunking on one or more of the input_core_dims. If reduce_func is left to None, apply_ufunc will keep crashing like it does now.
I think if a and b have common dimensions other than x, even xarray.dot() does not allow chunking along x (because it internally uses apply_ufunc with dask=parallerized).
I think it would be nice if we could have a way to allow chunking along input_core_dims, though I do not yet imagine how it should look like.
If a.dims=('x', 'y', 'z') and b.dims=('x', 'y', 'w'), then we can't use tensordot, as we need to multiply along dimension y.
Maybe we can use matmul in some limited case, but generally no.
One way to allow chunking across x would be to finish up dask.array.einsum: dask/dask#732
I'm reluctant to add reduce_func to xarray because it isn't clear to me exactly what the underlying abstraction is. It's something like a gufunc, but does a little bit more. Also, ideally we'd like this to be in dask.array, maybe as part of dask.array.apply_gufunc (dask/dask#3109).
For this specific problem, I think you could solve it with xarray.apply_ufunc by writing something like a gufunc that keeps the reduced axis as size 1 to apply to each chunk, and afterwards summing up along that dimension.
For this specific problem, I think you could solve it with xarray.apply_ufunc by writing something like a gufunc that keeps the reduced axis as size 1 to apply to each chunk, and afterwards summing up along that dimension.
@shoyer could you make an example? That was my first thought but I could not figure out how to make the apply_ufunc do it.
could you make an example? That was my first thought but I could not figure out how to make the apply_ufunc do it.
OK, thinking a little more about it, this would not work with dask='parallelized' which does not allow for chunking over core dimensions. You would have parallelize the function with dask yourself, e.g., with dask.array.map_blocks, but then you could use apply_ufunc with dask='allowed'.
@shoyer , close, but your version doesn't work in case of broadcasting.
I think I fixed it although it won't work correctly if only one between a or b has dask backend, and I'm not sure how to fix it:
Modify apply_ufunc:
remove the check that the input_core_dims must not be chunked
add parameter output_chunks
My main concern is ensuring that someone does not inadvertently apply a function not designed for multiple chunks to dask arrays. For example, suppose the function being applied is np.median.
Some loud flag that makes it very obvious what's going on seems like a good idea, e.g., possibly_chunked_core_dims=['x']?
Then we also need some sort of guarantee that chunked core dimensions aren't entirely removed, or else xarray/dask won't know how to stack them back up. I guess we could check to make sure that at least as many output core dimensions appear as appear in inputs cor edimensions?
@shoyer , you don't really need a parameter possibly_chunked_core_dims=['x']; you are already specifying output_chunks - without which apply_ufunc won't know what to do and crash...
I think this has only been possible since the implementation of xarray.apply_ufunc was switched from dask.array.blockwise to dask.array.apply_gufunc in #4060.
If this is actually doing what I think it's doing then we should document this possibility!
Activity
shoyer commentedon Mar 16, 2018
Have you tried the new
xarray.dot()? That might be even faster for this case.fujiisoup commentedon Mar 16, 2018
I think if
aandbhave common dimensions other thanx, evenxarray.dot()does not allow chunking alongx(because it internally usesapply_ufuncwithdask=parallerized).I think it would be nice if we could have a way to allow chunking along
input_core_dims, though I do not yet imagine how it should look like.shoyer commentedon Mar 16, 2018
For two inputs, don't we use dask.array.tensordot?
fujiisoup commentedon Mar 16, 2018
If
a.dims=('x', 'y', 'z')andb.dims=('x', 'y', 'w'), then we can't usetensordot, as we need to multiply along dimensiony.Maybe we can use matmul in some limited case, but generally no.
shoyer commentedon Mar 16, 2018
One way to allow chunking across
xwould be to finish updask.array.einsum: dask/dask#732I'm reluctant to add
reduce_functo xarray because it isn't clear to me exactly what the underlying abstraction is. It's something like a gufunc, but does a little bit more. Also, ideally we'd like this to be in dask.array, maybe as part ofdask.array.apply_gufunc(dask/dask#3109).For this specific problem, I think you could solve it with
xarray.apply_ufuncby writing something like a gufunc that keeps the reduced axis as size 1 to apply to each chunk, and afterwards summing up along that dimension.crusaderky commentedon Mar 16, 2018
@shoyer could you make an example? That was my first thought but I could not figure out how to make the apply_ufunc do it.
shoyer commentedon Mar 16, 2018
OK, thinking a little more about it, this would not work with
dask='parallelized'which does not allow for chunking over core dimensions. You would have parallelize the function with dask yourself, e.g., withdask.array.map_blocks, but then you could use apply_ufunc withdask='allowed'.shoyer commentedon Mar 16, 2018
Try:
crusaderky commentedon Mar 16, 2018
[EDIT] drastically simplified chunking algorithm
@shoyer , close, but your version doesn't work in case of broadcasting.
I think I fixed it although it won't work correctly if only one between a or b has dask backend, and I'm not sure how to fix it:
Proposal 2
Modify apply_ufunc:
My initial example would become:
Although I'm not sure this approach would be univocous when there's more than one core_dim...
shoyer commentedon Mar 16, 2018
My main concern is ensuring that someone does not inadvertently apply a function not designed for multiple chunks to dask arrays. For example, suppose the function being applied is
np.median.Some loud flag that makes it very obvious what's going on seems like a good idea, e.g.,
possibly_chunked_core_dims=['x']?Then we also need some sort of guarantee that chunked core dimensions aren't entirely removed, or else xarray/dask won't know how to stack them back up. I guess we could check to make sure that at least as many output core dimensions appear as appear in inputs cor edimensions?
crusaderky commentedon Apr 24, 2018
@shoyer , you don't really need a parameter
possibly_chunked_core_dims=['x']; you are already specifyingoutput_chunks- without which apply_ufunc won't know what to do and crash...stale commentedon Mar 24, 2020
In order to maintain a list of currently relevant issues, we mark issues as stale after a period of inactivity
If this issue remains relevant, please comment here or remove the
stalelabel; otherwise it will be marked as closed automaticallyTomNicholas commentedon May 17, 2021
Has this not been solved by the argument
allow_rechunk?@crusaderky isn't this effectively what you were trying to achieve?
returns
I think this has only been possible since the implementation of
xarray.apply_ufuncwas switched fromdask.array.blockwisetodask.array.apply_gufuncin #4060.If this is actually doing what I think it's doing then we should document this possibility!