Skip to content

Implement __array_ufunc__#1247

Merged
okuta merged 15 commits intocupy:masterfrom
martindurant:ufunc
Jun 4, 2018
Merged

Implement __array_ufunc__#1247
okuta merged 15 commits intocupy:masterfrom
martindurant:ufunc

Conversation

@martindurant
Copy link

So that numpy functions should work on cupy arrays. Would allow cupy arrays to be used transparently in much numpy-oriented code.

The current plan is to error, if in a non-unary ufunc, a numpy (non-device) and a cupy array are combined.

Based on

Fixes #1230 (eventually)

Copy link
Contributor

@mrocklin mrocklin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm glad to see this start. Some small comments follow.

Also, to avoid confusion with the term "generalized ufuncs" I recommend changing the title of the PR to something like "implement array_ufunc protocol"

for x in check:
if not isinstance(x, (ndarray, Number)):
# do *not* pass any old ndarray-like
return NotImplemented
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this extra logic on top of what cupy already does?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing to cupy results in
TypeError: Unsupported type <class 'numpy.ndarray'>
or using this as-is results in
TypeError: operand type(s) all returned NotImplemented from __array_ufunc__

Keeping the current means that, in theory, some other implementation could still work in combination with a cupy.ndarray, because NotImplemented implies trying the __array_ufunc__ of the other object involved; but in practice, I think it can only realistically be cupy or a subclass. I thunk best would be to raise a TypeError with a carefully phrased message.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that CuPy could also return NotImplemented if they want to give up and hand to some other system and your solution here would just pass that through. Is this correct? If so then this probably isn't the right place to implement this logic. Moreover, I don't think that you and I have enough experience with how CuPy is used to make policy decisions like this. I think that we should avoid this discussion, or perhaps raise it as a separate issue that lives independently from this effort, which I think should be restricted to implementing the __array_ufunc__ protocol.

name = ufunc.__name__
if method == '__call__':
if ufunc.signature is not None:
return NotImplemented
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use a comment explaining that we don't yet support generalized ufuncs

if 'out' in kwargs:
# need to unfold tuple argument in kwargs
check = inputs + kwargs['out']
kwargs['out'] = kwargs['out'][0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious, why was this logic necessary? Also, should we be concerned that if out has length greater than one that we've lost information?

@mrocklin
Copy link
Contributor

This could also generally use some tests. I think that this will communicate the intended outcome of this work more clearly to other potential reviewers.

@martindurant martindurant changed the title WIP: general ufuncs WIP: implement __array__ufunc__ May 18, 2018
@martindurant martindurant changed the title WIP: implement __array__ufunc__ WIP: implement __array_ufunc__ May 18, 2018
@mrocklin
Copy link
Contributor

@martindurant is it ok to remove the WIP label?

@martindurant
Copy link
Author

I am happy with the code that is here as well as the tests, so yes.
I still need to decide whether to test the add.at() case, or just drop it.

@martindurant martindurant changed the title WIP: implement __array_ufunc__ implement __array_ufunc__ May 21, 2018
@okuta okuta added the cat:feature New features/APIs label May 22, 2018
Copy link
Contributor

@beam2d beam2d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR. I'm excited to support np.xxx functions directly for CuPy. I added some minor comments. I will also ask @okuta for the design direction.

import cupy as cp # top-level ufuncs
if 'out' in kwargs:
# need to unfold tuple argument in kwargs
check = inputs + kwargs['out']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks check is not used.

# we don't support generalised-ufuncs (gufuncs)
return NotImplemented
try:
cp_ufunc = getattr(cp, ufunc.__name__)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use name (or remove name).

return NotImplemented
try:
cp_ufunc = getattr(cp, ufunc.__name__)
return cp_ufunc(*inputs, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this line outside the try clause.

# the only ufunc attribute currently
# http://docs-cupy.chainer.org/en/stable/reference/ufunc.html#ufunc-at
# self.scatter_add(*inputs, **kwargs)
else:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a chance to support some of the reduction routines (like add.reduce via sum, maximum.reduce via amax, etc.) here (maybe via ad-hoc manual mapping)? Or do you think it's better to first support reduce in CuPy's ufuncs (so that no ad-hoc coding is needed here)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that numpy reductions already dispatch if an appropriate method is defined

In [1]: class Foo:
   ...:     def sum(*args, **kwargs):
   ...:         return 123
   ...:     

In [2]: import numpy as np

In [3]: np.sum(Foo())
Out[3]: 123

Adding support for ufunc.reduce would also be good, but this seems better to do in the ufuncs themselves rather than in the __array_ufunc__ protocol.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sum is already dispatched. I misunderstood.

a number, i.e., raise ValueError instead of silently converting a
numpy array.
"""
import cupy as cp # top-level ufuncs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove as cp, use cupy directly.

@okuta
Copy link
Member

okuta commented May 22, 2018

Thank you for useful PR!

@okuta okuta self-assigned this May 22, 2018
@okuta okuta added this to the v5.0.0b2 milestone May 22, 2018
Copy link
Member

@okuta okuta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM except comment

"""
import cupy # top-level ufuncs
if 'out' in kwargs:
# need to unfold tuple argument in kwargs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cloud you check tuple length?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand, the out tuple could in theory have more entries, for some future ufunc that has multiple outputs. I don't think numpy as any of these, never mind cupy - so I agree that an exception could be raised if the tuple length>1. This is what numpy does, e.g., np.sin(x, out=(x, x)) -> ValueError.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I understood.

@okuta
Copy link
Member

okuta commented May 22, 2018

jenkins, test this please.

This PR will be merge in v5.0.0b2.

@mrocklin
Copy link
Contributor

Is there anything more to do here?

@okuta
Copy link
Member

okuta commented May 24, 2018

I found error. Can you fix this?
https://gist.github.com/okuta/fb26f7e8dc30020bb4d9735304fb0d06

@martindurant
Copy link
Author

@okuta , cupy does not implement the divmod ufunc, so I expect the operation to truly raise an error. However, I cannot figure out what the test is actually testing for, and why it passed before. Has my code actually prevented one of the the previously erroring codepaths from happening?

@okuta
Copy link
Member

okuta commented May 25, 2018

This is message to testing bot.

jenkins, test this please.

@beam2d
Copy link
Contributor

beam2d commented May 28, 2018

@okuta Can I merge it?

@okuta
Copy link
Member

okuta commented May 28, 2018

Please wait. Some tests are failed. I will investigate it.

@okuta okuta added the st:blocked-by-another-pr Blocked by another pull-request label May 28, 2018
@okuta
Copy link
Member

okuta commented May 28, 2018

@beam2d Cloud you merge #1286 ?

@beam2d
Copy link
Contributor

beam2d commented May 30, 2018

I merged #1286, so I think this PR is CI ready.
Jenkins, test this please.

@martindurant
Copy link
Author

I merged in those changes.
It is interesting that coverage showed up, and the patch is 100% covered but the overall coverage apparently went down.

@okuta
Copy link
Member

okuta commented Jun 1, 2018

jenkins, test this please.

@okuta okuta removed the st:blocked-by-another-pr Blocked by another pull-request label Jun 3, 2018


@testing.gpu
class TestArrayUfunc(unittest.TestCase):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add @testing.with_requires('numpy>=1.13') because, test is failed with NumPy<1.13.0.

@martindurant
Copy link
Author

@okuta , done

@okuta
Copy link
Member

okuta commented Jun 3, 2018

Thanks!

jenkins, test this please.

@okuta
Copy link
Member

okuta commented Jun 4, 2018

LGTM!

@okuta okuta merged commit cb5821c into cupy:master Jun 4, 2018
@mrocklin
Copy link
Contributor

mrocklin commented Jun 4, 2018

I'm very happy to see this merged!

@martindurant
Copy link
Author

Hurray

@martindurant martindurant deleted the ufunc branch June 4, 2018 02:37
@ericmjl
Copy link
Contributor

ericmjl commented Jun 4, 2018

OMG you guys made it happen!!

@kmaehashi kmaehashi changed the title implement __array_ufunc__ Implement __array_ufunc__ Jun 20, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cat:feature New features/APIs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants