Tackle "ValueError: buffer source array is read-only"
See original GitHub issueOne of the biggest pitfalls of running dask.array with a distributed scheduler is the dreaded ValueError: buffer source array is read-only
.
This error is typical when one runs a memoryview-based Cython kernel in distributed, and it’s particularly insidious as it will never show up in unit tests performed with the local dask multithreaded scheduler. It might even not show up when you run your problem on distributed and the arrays just happen to never transit across nodes or to the disk cache (which is exactly what the scheduler will try to achieve if enough RAM and CPU power are available).
In other words, this is a textbook example of an issue that risks appearing in production for the first time!
I can see a few ways of tackling the problem:
- in Cython: if you ain’t writing to an array, it should just work. As this issue has been around for the longest time, I suspect it might not be trivial?
- in distributed, making sure that all arrays passed to all kernels are writeable
- in dask.array, making sure that all arrays passed to all kernels are NOT writeable, which actually makes a lot of sense regardless of distributed. This will make the error crop up immediately in any naive unit tests. It will also wreak havoc for many existing dask.array users though. Possibly a opt-in setting?
- in the distributed docs, with a thorough tutorial on how to reproduce the problem in unit testing and how to change your kernels to fix it, so that it becomes the first result when anybody googles the exception.
On the last point, I personally solved the problem as follows:
In the kernels:
def _memoryview_safe(x):
"""Make array safe to run in a Cython memoryview-based kernel. These
kernels typically break down with the error ``ValueError: buffer source
array is read-only`` when running in dask distributed.
"""
if not x.flags.writeable:
if not x.flags.owndata:
x = x.copy(order='C')
x.setflags(write=True)
return x
def splev(x_new, t, c, k=3, extrapolate=True):
x_new = _memoryview_safe(x_new)
t = _memoryview_safe(t)
c = _memoryview_safe(c)
spline = scipy.interpolate.BSpline.construct_fast(t, c, k, axis=0, extrapolate=extrapolate)
return spline(x_new)
In the unit test:
def test_distributed():
def ro_array(a):
a = np.array(a)
a.setflags(write=False)
# Return a view of a, so that setting the write flag on the view is not enough
return a[:]
t = ro_array([1, 2])
c = ro_array([10, 20])
x_new = ro_array([1.5, 1.8])
splev(x_new, t, c, k=1)
If you comment out any of those calls to _memoryview_safe
, the test falls over.
Above I’m calling the kernel directly, but a similar thing can also be invoked from the dask wrapper (probably a more robust design).
Issue Analytics
- State:
- Created 5 years ago
- Reactions:7
- Comments:28 (17 by maintainers)
Top GitHub Comments
Reproduced with stack as of May 2018; cannot reproduce with latest stack as of Feb 2020. Note how downgrading Cython was not enough to reproduce the issue; I did not investigate which package/version fixed the problem exactly.
POC
demo.pyx
main.py
With legacy stack
With latest stack
Just a quick comment on this since I was involved in providing feed-back about the Cython PR (this read-only problem happens quite often too in a scikit-learn context, or more precisely in a joblib context which automatically memmaps inputs in read-only mode, and we were quite interested by the functionality). To benefit from the cython feature you need to add a
const
in your cython function signature along these lines:There is a limitation of const memoryview at the moment: you can not use const memoryview with fused types, see https://github.com/cython/cython/issues/1772 for more details. As far as scikit-learn is concerned this is the main reason we have not moved to using const memoryviews.