Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
1bc7556
Rework CreateArrayDeps to use block_info dict, which is more general.
Mar 5, 2021
2f1cafd
Use block_info in uniform array creation.
Mar 5, 2021
4732153
WIP refactor from_array to use high level graph array creation.
Dec 16, 2020
9c11f5a
Update lock test.
Dec 16, 2020
ea1ff6e
Test array embedding in graph_from_arraylike
Dec 17, 2020
9cffa5e
Remove inline_array, which doesn't make much sense in the context of a
Mar 6, 2021
ef89354
This test is no longer relevant when using HLG.
Mar 8, 2021
3685f9a
WIP blockwise IO random arrays.
Mar 9, 2021
b7ad844
WIP more work on random.
Mar 9, 2021
5cee0ec
Fall back on materialized graph if there are dask.array.Array
Mar 10, 2021
b733296
Handle small args,kwargs in blockwise branch.
Mar 10, 2021
e991b0f
Cleanup
Mar 18, 2021
83a2744
Move cached_cumsum to general utils to avoid top-level numpy import in
Mar 18, 2021
cd17f94
Move BlockwiseCreateRandomArray into layers, remove use of numpy.
Mar 18, 2021
707d41e
Add TODO note
Mar 18, 2021
1e4a232
WIP handling extra chunks
Mar 19, 2021
716097d
Handle scalar results
Mar 19, 2021
0cf7881
Deserialization works out of the box.
Mar 19, 2021
b555e2e
Finish fixing imports.
Mar 19, 2021
5c4a1bd
Update import
Mar 23, 2021
1e9b49a
Get deserialization working.
Mar 24, 2021
10fa918
Revert random changes for now.
Apr 12, 2021
3d4f291
Merge branch 'main' into blockwise-array-creation-redux
Apr 14, 2021
80f9d04
Restore the ability to not inline array
Apr 13, 2021
7003df9
Fix mistaken import
Apr 14, 2021
96fe5c6
Remove debug info
Apr 14, 2021
f45ff24
Merge branch 'main' into blockwise-array-creation-redux
Apr 29, 2021
e92f014
Merge branch 'main' into blockwise-array-creation-redux
May 4, 2021
4887543
Clean up after bad merge
May 4, 2021
7ac4ff6
Merge branch 'main' into blockwise-array-creation-redux
Jun 24, 2021
bd5ac4f
Improve test to be less fragile in drilling into subgraph callable.
Jun 25, 2021
9b324f8
Rewrite lock test to not drill into object hierarchy
Jun 25, 2021
25f5108
Merge branch 'main' into blockwise-array-creation-redux
Oct 29, 2021
b09ac1e
Default to inline_array=True.
Nov 12, 2021
5e933cc
WIP decoupling CreateArrayDeps into multiple helpers.
Jan 4, 2022
fc530eb
Merge branch 'main' into blockwise-array-creation-redux
Jan 4, 2022
ef42b08
WIP refactoring blockwise array deps
Jan 5, 2022
5ed22e0
wip
Jan 5, 2022
fe4449c
Remove BlockwiseCreateArray helper class
Jan 5, 2022
04405e2
Clean up naming
Jan 5, 2022
69002e0
Update tests.
Jan 5, 2022
c6243e7
Cleanup
Jan 5, 2022
cdb2536
Update docstring.
Jan 5, 2022
8899811
Share some code for BlockwiseDeps
Jan 5, 2022
a3a5476
Convert non-inlined array to HLG
Jan 6, 2022
ea1e777
Address review comments.
Jan 6, 2022
44b9ac9
Light type annotations for graph_from_arraylike
Jan 7, 2022
cc7c2ce
Revert "Light type annotations for graph_from_arraylike"
Jan 7, 2022
f6bf935
Revert "Default to inline_array=True."
Jan 10, 2022
28d5d4b
Annotations for BlockwiseDeps
Jan 11, 2022
947aa5c
Cleanup
Jan 11, 2022
1330bcc
Merge branch 'main' into blockwise-array-creation-redux
Jan 11, 2022
474eff4
Fix doctest
Jan 11, 2022
91de1d6
Merge branch 'main' into blockwise-array-creation-redux
Jan 14, 2022
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
131 changes: 87 additions & 44 deletions dask/array/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@
persist,
tokenize,
)
from ..blockwise import blockwise as core_blockwise
from ..blockwise import broadcast_dimensions
from ..context import globalmethod
from ..core import quote
from ..delayed import Delayed, delayed
from ..highlevelgraph import HighLevelGraph
from ..layers import reshapelist
from ..highlevelgraph import HighLevelGraph, MaterializedLayer
from ..layers import ArraySliceDep, reshapelist
from ..sizeof import sizeof
from ..utils import (
IndexCallable,
Expand Down Expand Up @@ -232,46 +233,88 @@ def slices_from_chunks(chunks):
return list(product(*slices))


def getem(

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.

This function certainly seems to be private/internal, but perhaps we should get confirmation that it isn't being used directly by down-stream libraries (like xarray) before removing?

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.

A quick search seems to indicate it is unused in xarray. Are there other projects we should check with as well?

@jakirkham jakirkham Nov 15, 2021

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.

In the Dask org there are a couple old blogposts that reference this getem function. Not seeing any other references in the org. Idk if people are still looking at blogposts that far back or using them anywhere

https://blog.dask.org/2014/12/27/Towards-OOC
https://blog.dask.org/2014/12/30/Towards-OOC-Frontend

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I didn't remove it, but renamed it graph_from_arraylike, as something a bit more descriptive. I'd be happy to roll that back in the interests of backwards compatibility (thought the signature of the function will have also changed to be HLG-ified, so the backwards compatibility ship may have sailed)

@GenevieveBuckley GenevieveBuckley Nov 16, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's pretty straightforward to add a note to old blogposts telling people things have changed in more recent versions of Dask. We could do that if you want.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi, this broke Vaex, in case anyone reads this, this was my compatibility fix:

arr,
def graph_from_arraylike(
arr, # Any array-like which supports slicing
Comment thread
ian-r-rose marked this conversation as resolved.
chunks,
shape,
name,
getitem=getter,
shape=None,
out_name=None,
lock=False,
asarray=True,
dtype=None,
):
"""Dask getting various chunks from an array-like

>>> getem('X', chunks=(2, 3), shape=(4, 6)) # doctest: +SKIP
{('X', 0, 0): (getter, 'X', (slice(0, 2), slice(0, 3))),
('X', 1, 0): (getter, 'X', (slice(2, 4), slice(0, 3))),
('X', 1, 1): (getter, 'X', (slice(2, 4), slice(3, 6))),
('X', 0, 1): (getter, 'X', (slice(0, 2), slice(3, 6)))}

>>> getem('X', chunks=((2, 2), (3, 3))) # doctest: +SKIP
{('X', 0, 0): (getter, 'X', (slice(0, 2), slice(0, 3))),
('X', 1, 0): (getter, 'X', (slice(2, 4), slice(0, 3))),
('X', 1, 1): (getter, 'X', (slice(2, 4), slice(3, 6))),
('X', 0, 1): (getter, 'X', (slice(0, 2), slice(3, 6)))}
inline_array=False,
) -> HighLevelGraph:
"""
HighLevelGraph for slicing chunks from an array-like according to a chunk pattern.

If ``inline_array`` is True, this make a Blockwise layer of slicing tasks where the
array-like is embedded into every task.,

If ``inline_array`` is False, this inserts the array-like as a standalone value in
a MaterializedLayer, then generates a Blockwise layer of slicing tasks that refer
to it.

>>> dict(graph_from_arraylike(arr, chunks=(2, 3), shape=(4, 6), name="X", inline_array=True)) # doctest: +SKIP
{(arr, 0, 0): (getter, arr, (slice(0, 2), slice(0, 3))),
(arr, 1, 0): (getter, arr, (slice(2, 4), slice(0, 3))),
(arr, 1, 1): (getter, arr, (slice(2, 4), slice(3, 6))),
(arr, 0, 1): (getter, arr, (slice(0, 2), slice(3, 6)))}

>>> dict( # doctest: +SKIP
graph_from_arraylike(arr, chunks=((2, 2), (3, 3)), shape=(4,6), name="X", inline_array=False)
)
{"original-X": arr,
('X', 0, 0): (getter, 'original-X', (slice(0, 2), slice(0, 3))),
('X', 1, 0): (getter, 'original-X', (slice(2, 4), slice(0, 3))),
('X', 1, 1): (getter, 'original-X', (slice(2, 4), slice(3, 6))),
('X', 0, 1): (getter, 'original-X', (slice(0, 2), slice(3, 6)))}
"""
out_name = out_name or arr
chunks = normalize_chunks(chunks, shape, dtype=dtype)
keys = product([out_name], *(range(len(bds)) for bds in chunks))
slices = slices_from_chunks(chunks)
out_ind = tuple(range(len(shape)))

if (
has_keyword(getitem, "asarray")
and has_keyword(getitem, "lock")
and (not asarray or lock)
):
values = [(getitem, arr, x, asarray, lock) for x in slices]
getter = partial(getitem, asarray=asarray, lock=lock)
else:
# Common case, drop extra parameters
values = [(getitem, arr, x) for x in slices]
getter = getitem

if inline_array:
layer = core_blockwise(
getter,
name,
out_ind,
arr,
None,
ArraySliceDep(chunks),
out_ind,
numblocks={},
)
return HighLevelGraph.from_collections(name, layer)
else:
Comment thread
ian-r-rose marked this conversation as resolved.
original_name = "original-" + name

return dict(zip(keys, values))
layers = {}
layers[original_name] = MaterializedLayer({original_name: arr})
layers[name] = core_blockwise(
getter,
name,
out_ind,
original_name,
None,
ArraySliceDep(chunks),
out_ind,
numblocks={},
)
Comment thread
ian-r-rose marked this conversation as resolved.

deps = {
original_name: set(),
name: {original_name},
}
return HighLevelGraph(layers, deps)


def dotmany(A, B, leftfunc=None, rightfunc=None, **kwargs):
Expand Down Expand Up @@ -3233,13 +3276,10 @@ def from_array(
)

if name in (None, True):
token = tokenize(x, chunks)
original_name = "array-original-" + token
token = tokenize(x, chunks, lock, asarray, fancy, getitem, inline_array)
name = name or "array-" + token
elif name is False:
original_name = name = "array-" + str(uuid.uuid1())
else:
original_name = name
name = "array-" + str(uuid.uuid1())

if lock is True:
lock = SerializableLock()
Expand All @@ -3266,23 +3306,17 @@ def from_array(
else:
Comment thread
ian-r-rose marked this conversation as resolved.
getitem = getter_nofancy

if inline_array:
get_from = x
else:
get_from = original_name

dsk = getem(
get_from,
dsk = graph_from_arraylike(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@rjzamora wanted to flag this as where we'll have to think through some of the array inlining logic.

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.

Got it. So, the original code allows you to include the array in the graph once (with a dedicated key), or to inline it in every task. So far, this PR is effectively inlining the array in every task by including it in the IO function (in graph_from_arraylike). Is that correct?

It seems like you could support inline_array=False, but you may need to further expand CreateArrayDeps to include optional args/kwargs that should be the same for all keys (and make it possible to specify these arguments in BlockwiseCreateArray). I guess if you can do this, you could avoid using partial to embed the array in io funciton altogether?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Got it. So, the original code allows you to include the array in the graph once (with a dedicated key), or to inline it in every task. So far, this PR is effectively inlining the array in every task by including it in the IO function (in graph_from_arraylike). Is that correct?

It seems like you could support inline_array=False, but you may need to further expand CreateArrayDeps to include optional args/kwargs that should be the same for all keys (and make it possible to specify these arguments in BlockwiseCreateArray). I guess if you can do this, you could avoid using partial to embed the array in io funciton altogether?

Yes, you have this exactly right -- I'm planning to re-introduce non-inlining, but haven't tackled it yet. For the case of lazy arrays like Zarr, or small ones, this should already do what we want.

x,
chunks,
x.shape,
name,
getitem=getitem,
shape=x.shape,
out_name=name,
lock=lock,
asarray=asarray,
dtype=x.dtype,
inline_array=inline_array,
)
if not inline_array:
dsk[original_name] = x

# Workaround for TileDB, its indexing is 1-based,
# and doesn't seems to support 0-length slicing
Expand Down Expand Up @@ -4278,7 +4312,7 @@ def asarray(
return from_array(a, getitem=getter_inline, **kwargs)


def asanyarray(a, dtype=None, order=None, *, like=None):
def asanyarray(a, dtype=None, order=None, *, like=None, inline_array=False):
"""Convert the input to a dask array.

Subclasses of ``np.ndarray`` will be passed through as chunks unchanged.
Expand All @@ -4305,6 +4339,9 @@ def asanyarray(a, dtype=None, order=None, *, like=None):
argument. If ``like`` is a Dask array, the chunk type of the
resulting array will be defined by the chunk type of ``like``.
Requires NumPy 1.20.0 or higher.
inline_array:
Whether to inline the array in the resulting dask graph. For more information,
see the documentation for ``dask.array.from_array()``.

Returns
-------
Expand Down Expand Up @@ -4343,7 +4380,13 @@ def asanyarray(a, dtype=None, order=None, *, like=None):
return a.map_blocks(np.asanyarray, like=like_meta, dtype=dtype, order=order)
else:
a = np.asanyarray(a, like=like_meta, dtype=dtype, order=order)
return from_array(a, chunks=a.shape, getitem=getter_inline, asarray=False)
return from_array(
a,
chunks=a.shape,
getitem=getter_inline,
asarray=False,
inline_array=inline_array,
)


def is_scalar_for_elemwise(arg):
Expand Down
65 changes: 3 additions & 62 deletions dask/array/slicing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
import warnings
from itertools import product
from numbers import Integral, Number
from operator import add, itemgetter
from operator import itemgetter

import numpy as np
from tlz import accumulate, concat, memoize, merge, pluck
from tlz import concat, memoize, merge, pluck

from .. import config, core, utils
from ..base import is_dask_collection, tokenize
from ..highlevelgraph import HighLevelGraph
from ..utils import is_arraylike
from ..utils import cached_cumsum, is_arraylike
from .chunk import getitem

colon = slice(None, None, None)
Expand Down Expand Up @@ -1288,65 +1288,6 @@ def shuffle_slice(x, index):
return x[index2].rechunk(chunks2)[index3]


class _HashIdWrapper:
"""Hash and compare a wrapped object by identity instead of value"""

def __init__(self, wrapped):
self.wrapped = wrapped

def __eq__(self, other):
if not isinstance(other, _HashIdWrapper):
return NotImplemented
return self.wrapped is other.wrapped

def __ne__(self, other):
if not isinstance(other, _HashIdWrapper):
return NotImplemented
return self.wrapped is not other.wrapped

def __hash__(self):
return id(self.wrapped)


@functools.lru_cache()
def _cumsum(seq, initial_zero):
if isinstance(seq, _HashIdWrapper):
seq = seq.wrapped
if initial_zero:
return tuple(accumulate(add, seq, 0))
else:
return tuple(accumulate(add, seq))


def cached_cumsum(seq, initial_zero=False):
"""Compute :meth:`toolz.accumulate` with caching.

Caching is by the identify of `seq` rather than the value. It is thus
important that `seq` is a tuple of immutable objects, and this function
is intended for use where `seq` is a value that will persist (generally
block sizes).

Parameters
----------
seq : tuple
Values to cumulatively sum.
initial_zero : bool, optional
If true, the return value is prefixed with a zero.

Returns
-------
tuple
"""
if isinstance(seq, tuple):
# Look up by identity first, to avoid a linear-time __hash__
# if we've seen this tuple object before.
result = _cumsum(_HashIdWrapper(seq), initial_zero)
else:
# Construct a temporary tuple, and look up by value.
result = _cumsum(tuple(seq), initial_zero)
return result


def parse_assignment_indices(indices, shape):
"""Reformat the indices for assignment.

Expand Down
Loading