#12601·dask

dask.array.reductions.nanquantile never dispatches float32 input to numbagg, forcing a much slower fallback

Author: benritchieCreated Sep 17, 2026Updated Sep 18, 2026
Labelsneeds triage

What we're seeing

dask.array.reductions.nanquantile's dispatch condition only routes to numbagg's nanquantile (GIL-releasing) when the input array is float64 or int:

if (
    HAS_NUMBAGG
    and (a.dtype.kind in "ui" or a.dtype == np.float64)
    and q_arr.dtype == np.float64
    ...
):
    func = _numbagg_nanquantile
else:
    func = _custom_nanquantile

float32 input unconditionally falls back to _custom_nanquantile. now numbagg doesn't have an optimised float32 version, but it does happily haldle float32 (NumPy/numba's generic ufunc casting silently upcasts to match the declared signature).

Why it matters

without numbagg, we fall back on _custom_nanquantile. That's slower, and most importantly, holds the GIL much more ~29.6% GIL-free vs. numbagg's ~63.4%. Casting the array to float64 immediately before the quantile call (and back to float32 after) recovered numbagg dispatch and measured 29–43% faster wall clock and lower GIL contention on our workload, with no functional downside — but it would be much cleaner to just allow dask to dispatch float32 to numbagg.

Question / ask

Could we relax the dispatch condition to also route float32 arrays to numbagg? I'm happy to handle submitting a PR if this is something that would be accepted.

Versions: numbagg 0.9.4

Minimal repro:

import numpy as np, dask.array as da

x32 = da.from_array(np.random.default_rng(0).uniform(size=(20, 1000)).astype(np.float32), chunks=(20, 500))
x64 = x32.astype(np.float64)

# Confirm which function actually runs, e.g. via a quick monkeypatch/profile of
# dask.array.reductions._numbagg_nanquantile vs _custom_nanquantile
da.nanquantile(x32, 0.5, axis=0).compute()  # -> _custom_nanquantile
da.nanquantile(x64, 0.5, axis=0).compute()  # -> _numbagg_nanquantile