Dask Array: chunked NumPy

Dask · Practical · 15 min

Dask Array scales NumPy code to arrays larger than memory, such as climate, imaging and simulation data.

A Dask Array is a grid of NumPy arrays (chunks) behind a NumPy-like API: da.ones, da.random.default_rng().random(...), da.from_array(numpy_or_h5py_or_zarr, chunks=...), da.from_zarr(path). Element-wise math, broadcasting, slicing, reductions with axis=, matmul, many linalg functions and NumPy ufuncs all work, lazily.

Chunks are the key design choice:

  • Aim for chunks of roughly 100 MB (for example (4000, 4000) float64 is 128 MB). chunks="auto" picks a size for you.
  • Align chunks with how you access data: for per-row time series, chunk so each row's data is in few chunks.
  • Operations that combine many chunks, such as transposes and some reductions, cost communication.

x.rechunk((1000, -1)) changes chunking (-1 means the whole axis in one chunk); it's expensive, so do it once and deliberately. x.chunks and x.chunksize show the layout, and printing an array in Jupyter shows a helpful diagram.

For custom per-chunk logic, use x.map_blocks(func), or da.map_overlap when each chunk needs a border of neighbouring values, as in image filters and stencils. Store big results with x.to_zarr(path) rather than computing them into memory.

Example

import dask.array as da
import numpy as np

rng = da.random.default_rng(42)
x = rng.random((20_000, 5_000), chunks=(5_000, 5_000))   # ~800 MB, 4 chunks
print(x.numblocks, x.chunksize)       # (4, 1) (5000, 5000)
print(round(x.nbytes / 1e6))          # 800

col_means = x.mean(axis=0)            # lazy reduction
centered = x - col_means              # broadcasting, still lazy
print(np.allclose(centered.mean(axis=0).compute(), 0, atol=1e-9))   # True

y = x.rechunk((1_000, -1))            # whole rows per chunk
print(y.numblocks)                    # (20, 1)

z = x[:4, :3].map_blocks(np.round, 2)
print(z.compute().shape)              # (4, 3)

Remember

  • A Dask Array is a grid of NumPy chunks with the NumPy API
  • Chunks of around 100 MB aligned with your access pattern work best
  • rechunk is expensive; map_blocks and map_overlap run custom code per chunk

Watch out for

  • Creating millions of tiny chunks (e.g. chunks=(10, 10)) and drowning in scheduler overhead.

Check yourself

  1. A 40 GB array is chunked as (100, 100) float64 chunks (80 KB each). What is the likely problem?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.dask.org/en/stable/array-chunks.html