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
_pickle.PicklingError: Can't pickle <ufunc 'smooth'>: it's not the same object as __main__.smooth
This happens only when using distributed. I launch a cluster in a Slurm cluster via ipyparallel's become_dask. Something like this. Without using distributed the example works fine.
These are the versions of some relevant packages I'm using:
Not sure if this is a bug. Probably it's something in my environment. I found similar serialization errors but nothing recent. Only solved issues earlier than 2022.
Looking into this, git bisect'd to #7564. but I think that just brought out the issue that has always been.
It worked before b/c we didn't try to pickle it. It will also 'fail' in dask w/o distributed if one tries to pickle the output array from the numba function. ie:
importnumbaimportdask.arrayasda@numba.guvectorize(["int8,int8[:]"], "()->()")defdouble(x, out):
out[:] =x*2defmain():
x=da.random.randint(0, 127, size=(10, 10, 10), chunks=('1 MB', None, None), dtype='int8')
y=double(x)
importpicklepickle.dumps(double) # Can pickle the function directly.# But not when the generated function from numba is part of the dask array.# Fails w/ PicklingError: Can't pickle <ufunc 'double'>: it's not the same object as __main__.doublepickle.dumps(y)
y.max().compute()
if__name__=='__main__':
main()
Essentially, I think someone, numba or dask, is generating a bad wrapper to the function and that's somehow brought out when attached to the graph.
For example this will raise the same error, as we're changing the location of "foo" function, pickle thinks it's on main, but it's been 'moved' to Foo.
classFoo:
def__init__(self, func):
self.func=funcdef__call__(self):
returnself.func()
defdecorator(func):
returnFoo(func)
@decoratordeffoo():
passdefmain():
importpicklepickle.dumps(foo) # Fails w/ same error "...it's not the same object as __main__.foo"if__name__=='__main__':
main()
By the time control reaches dask.array.Array.__array_ufunc__, it's too late - we've lost the reference to the GUFunc object as well as the wrapped pure-python object. I can't find a way to hack around it in our serialization layer.
Workaround 1
If you just need an elementwise operation, you can use @vectorize, which is unaffected by this issue:
@numba.vectorize(["int8(int8)"])defdouble(x):
returnx*2x=da.random.randint(...)
y=double(x)
pickle.dumps(y) # Works
dynamic vectorize works as well:
@numba.vectorize()defdouble(x):
returnx*2
Workaround 2
If you do need to operate on vectors, you can hack the call as follows:
Activity
fjetter commentedon Jan 17, 2024
Sorry for the late reply. I can reproduce this. here is another reproducer (note that
jitworks)fjetter commentedon Jan 17, 2024
Sorry, I believe the error I am reproducing with my example is actually a little different. My example only fails if the
cachekwarg isTrue.The dask-examples version fails with a different exception
milesgranger commentedon Jan 23, 2024
Looking into this, git bisect'd to #7564. but I think that just brought out the issue that has always been.
It worked before b/c we didn't try to pickle it. It will also 'fail' in dask w/o distributed if one tries to pickle the output array from the numba function. ie:
milesgranger commentedon Jan 23, 2024
Essentially, I think someone, numba or dask, is generating a bad wrapper to the function and that's somehow brought out when attached to the graph.
For example this will raise the same error, as we're changing the location of "foo" function, pickle thinks it's on main, but it's been 'moved' to
Foo.milesgranger commentedon Jan 23, 2024
xref #3450
crusaderky commentedon Mar 12, 2024
The problem is that a numba gufunc is a pure-python wrapper around a locally-compiled numpy ufunc. When you pickle the wrapper, you trigger serialization code which ships over the wrapped pure-python function and recompiles it upon unpickle:
https://github.com/numba/numba/blob/54433810bfa60458209ed2c24f62e793b12aca6b/numba/np/ufunc/gufunc.py#L33-L46
However, the wrapper undoes itself on
__call__:https://github.com/numba/numba/blob/54433810bfa60458209ed2c24f62e793b12aca6b/numba/np/ufunc/gufunc.py#L171-L172
By the time control reaches
dask.array.Array.__array_ufunc__, it's too late - we've lost the reference to the GUFunc object as well as the wrapped pure-python object. I can't find a way to hack around it in our serialization layer.Workaround 1
If you just need an elementwise operation, you can use
@vectorize, which is unaffected by this issue:dynamic vectorize works as well:
Workaround 2
If you do need to operate on vectors, you can hack the call as follows:
The
__array_ufunc__protocol is explained in NEP 13.Solution
This issue has been around at least since 2019 - i did some archeology and found my own ticket, never resolved, on the numba board: numba/numba#4314
Here I'm suggesting a solution: numba/numba#4314 (comment)