nv-legate / legate.core

The Foundation for All Legate Libraries
https://docs.nvidia.com/legate/24.06/
Apache License 2.0
189 stars 63 forks source link

[BUG] Ufuncs box scalars in array #946

Open suranap opened 5 months ago

suranap commented 5 months ago

Software versions

Python : 3.10.13 | packaged by conda-forge | (main, Dec 23 2023, 15:36:39) [GCC 12.3.0] Platform : Linux-5.4.0-169-generic-x86_64-with-glibc2.31 Legion : v23.11.00.dev-54-g40f6061 Legate : 23.11.00.dev+54.g40f6061 Cunumeric : 23.11.00.dev+36.gb2912ed7 Numpy : 1.26.4 Scipy : 1.12.0 Numba : 0.59.0 CTK package : cuda-version-11.7-h67201e3_2 (conda-forge) GPU driver : 535.54.03 GPU devices : GPU 0: Tesla P100-SXM2-16GB GPU 1: Tesla P100-SXM2-16GB GPU 2: Tesla P100-SXM2-16GB GPU 3: Tesla P100-SXM2-16GB

Jupyter notebook / Jupyter Lab version

No response

Expected behavior

Expect cunumeric to return a scalar value just like numpy.

Welcome to Legion Python interactive console
>>> import cunumeric as cnp
>>> import numpy as np
>>> np.sqrt(4.0)
2.0
>>> cnp.sqrt(4.0)
array(2.)

Observed behavior

Looks like ufuncs applied to a scalar returns an array.

>>> cnp.add(1,2)
array(3)
>>> cnp.power(3,2)
array(9)
>>> cnp.power(3.0,2.0)
array(9.)
>>> cnp.tan(4.0)
array(1.15782128)
>>> cnp.equal(1, 1)
array(True)
>>>

Example code or instructions

see above

Stack traceback or browser console output

No response

manopapad commented 5 months ago

Is there a need to match this behavior?

NumPy will often (but not always) convert a 0d array result to a python-level scalar, but cuNumeric avoids doing that, because in cuNumeric an array value can potentially represent an asynchronous computation. Passing that value in its unevaluated form to another operation normally doesn't force the program to block, which we prefer. Converting to a python-level scalar forces blocking, so we don't do it eagerly.

So the general policy (which is, admittedly, not well-documented currently) is that we don't guarantee that operations which in NumPy return scalars always return scalars in cuNumeric. But the values that we do return should always be usable in place of a scalar. For example, if the user tries to do anything with the result of cn.random.randint(1234, size=None) that requires the value right away, e.g. printing it out, then we block and wait for the computation to complete.

lightsighter commented 5 months ago

So the general policy (which is, admittedly, not well-documented currently) is that we don't guarantee that operations which in NumPy return scalars always return scalars in cuNumeric. But the values that we do return should always be usable in place of a scalar.

I'll also note that is consistent with Python's duck typing philosophy. Types of objects do not need to match, they just need to behave identically. In this case, returning something that behaves like a scalar enables more asynchronous execution and better performance.

suranap commented 5 months ago

I ported Autograd on top of cuNumeric to do differential programming. This means there's yet another wrapper layer pretending to be NumPy. I got the following error: *** ValueError: Out-of-bounds projection on dimension 0 with index 1 for a store of shape Shape((1, 194, 3072)) and tracked it to this issue.

I'll debug further to see where this distinction between scalar and 0d array is popping up.