-
-
Notifications
You must be signed in to change notification settings - Fork 10k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NEP-18: Handling aliased NumPy functions #12974
Comments
Thought I'd prod here. I've been working with @pentschev a bit a on this. I think that he's happy to do some work here to solve this issue. It would be helpful to get some feedback from maintainers on what they think the right approach is. Also cc'ing @hameerabbasi |
Yes, the fallback in The difference is one would have to return a sentinel |
Is there a way to protect with a |
There are cases where natural recursion may happen... Like a Dask array using a CuPy array within it. |
cc @mhvk |
I don't think we need to write a new NEP. We can just tweak NEP-18 (running it by the mailing list, of course) to describe the adjusted interface and why we need it. The NEP is still marked as "Provisional" after all. |
I'm somewhat confused by it all: if you want to call the original function in your But I don't think we should allow some fall-back where it is fine to have |
It would still be nice to have a way to fall back to the original NumPy implementation, even in your super-nice scenario, where you override a subset of the API and get everything dependent on just what you overrode automatically. For this to happen, the original implementation needs to be accessible. That is basically what's being asked about here. 🙂 |
On second thought, I do see what you mean about people becoming reliant on the "NumPy will cast stuff for us" behavior and us getting stuck to it unwillingly. I'd also recommend the CuPy team to make "dummy" functions that just call |
Yes, that is exactly my worry: by allowing one to mix old and new, we will never get to a state where we can change the implementation. Note that in this respect, it is no different from
Note that one can also just do |
p.s. If the above is considered a good way forward, then it might be good to be explicit about it in the documentation! |
The problem is, while we have The reverse dispatcher could also have other uses. |
Ah, yes, for One other solution would be to allow the |
That would require a reverse dispatcher in general as well, so it's useful either way. ;-) |
Adding an officially supported alias like the current def __array_function__(self, func, types, args, kwargs):
if func not in SPECIAL_TREATMENT_FUNCTIONS:
return np.NotImplementedButCoercible
# more interesting stuff you would write: def __array_function__(self, func, types, args, kwargs):
if func not in SPECIAL_TREATMENT_FUNCTIONS:
return func.implementation(*args, **kwargs)
# more interesting stuff This is at least much more transparent than adding more black-box logic to NumPy's internal dispatching rules. From a forwards compatibility perspective it definitely locks us in just as much as adding |
@shoyer - I see the advantage of exposing the wrapped function - though I think it would still be good if eventually this method became the place where the |
OK, how about:
I'm not entirely sure |
I was just checking CuPy's upstream, and the infinite recursion issue seems to have been resolved on their side in https://github.com/cupy/cupy/pull/2024/files. However, I think the coercion discussion is really relevant and would be great to have a consensus on it, since CuPy itself doesn't fully implement the NumPy API, so I foresee this being useful in a very near future. Pardon me for not chiming into the discussion, but I think it's gotten much more advanced than my not-so-deep understanding of NumPy. |
@pentschev - agreed that the discussion you triggered is relevant - and I think we're getting there with @shoyer's suggestion. @hameerabbasi - as you noted, implementing a @shoyer - I liked the suggestion and agree we can leave |
I agree with all this, but let's post to the mailing list and go through with the formal process. As for the discussions: I have another project where both the dispatchers are useful. I'd love to have them exposed, along with some of the other machinery so SciPy/the Scikits can be overriden. |
I'm not sure I follow. For now, we could simply set
The only way to do this would be to manually write this "reverse dispatcher" function for each dispatcher, e.g., for def _stack_dispatcher(arrays, axis=None, out=None):
if out is not None:
# optimize for the typical case where only arrays is provided
arrays = list(arrays)
arrays.append(out)
return arrays and now you'd need something that can be called like def _stack_reverse_dispatcher(dispatched_args, arrays, axis=0, out=None):
if out is not None:
arrays_ = dispatched_args[:-1]
out_ = dispatched_args[0]
else:
arrays_ = dispatched_args
out_ = None
return stack(arrays, axis, out) So this is totally doable, but it approximately doubles the amount of dispatcher boilerplate in NumPy. We can imagine using these reverse dispatcher internally in NumPy whenever a function call happens, i.e.,
This hypothetical model has two problems:
Given all this, I'm not really sure it's worth the trouble. |
You're right that we could start with |
Please take a look at the proposed revision of NEP-18 here: #13305 |
By the way, it turns out CuPy isn't unique in aliasing a handful of NumPy functions like |
Re-opening this, since we are rolling back the NEP changes in #13305 |
Re-read this thread. One idea that still seems nice is to have a way to do reverse dispatch so that one can separate out coercion from the real implementation. Above, it was discussed that this was not easy in general and would need separate routines for different functions. But is it really that bad? Many functions have just one argument, so those are all trivial; looking at everything in I'll think a bit more about it... The worry about extra overhead is certainly there... |
@rgommers, would you be able to weigh in here? This is one of the issues we were discussing at SciPy. |
@jakirkham the revert of These solutions would solve real use cases, but at the cost of additional complexity. We would like to gain experience with how I think that pretty much sums it up. For some aliasing that libraries do those simply need to be avoided or reimplemented (that'd be a few lines of code per function, and there shouldn't be many functions). I think the case where the library implementing |
The discussion for this issue started in cupy/cupy#2029, please refer to it for all the details.
Currently, CuPy creates aliases from some NumPy C functions that use the
__array_function__
dispatch. This causes an infinite loop since CuPy keeps on calling the NumPy function, and this dispatches the CuPy alias. Note that the same case may apply to any other libraries that implementation the__array_function__
protocol.Summary of fix suggestions we have at the moment:
__array_function__
protocolNone
) as a fallback to NumPy function, similar to the NEP proposal@shoyer @mrocklin
The text was updated successfully, but these errors were encountered: