Repository: numba/numba

Issue:
Wrong dtype when using np.asarray on DeviceNDArray
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I have tried using the latest released version of Numba (most recent is
 visible in the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included a self contained code sample to reproduce the problem.
  i.e. it's possible to run as 'python bug.py'.

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->

#### Reproduce Code

```python
>>> from numba import cuda
>>> import numpy as np
>>> arr = np.arange(10).astype(np.int16)
>>> arr.dtype
dtype('int16')

>>> darr = cuda.to_device(arr)
>>> darr.dtype
dtype('int16')

>>> arr = np.asarray(darr)
>>> arr.dtype
dtype('float64')
```

_Originally posted by @ashutoshvarma in https://github.com/numba/numba/issues/6563#issuecomment-774035027_

The `test_auto_transfer` is failing due the following change :-
Without it all tests pass without problem
```diff
@@ -107,7 +108,7 @@ class UFuncMechanism(object):
         """
         for i, ary in enumerate(self.arrays):
             if ary is not None:
-                self.argtypes[i] = ary.dtype
+                self.argtypes[i] = np.asarray(ary).dtype
```
Since `ary` would always be either `np.ndarray` or `DeviceNDArray` type,  keeping `ary.dtype` should be fine? (correct me if I missed something )


Ideally, the above change should not break anything, but for some reason, if `ary` is of DeviceNDArray type then `np.asarray(ary).dtype` gives the wrong dtype.
For DeviceNDArray ary, `np.asarray(ary)` will always be a `float64` ndarray irrespective of original dtype


```python
>>> x = np.arange(n, dtype=np.int32)
>>> dx = cuda.to_device(x)
>>> print(dx.dtype)
>>> print(np.asarray(dx).dtype)
int32
float64
```

After digging more, looks like problem might be in `DeviceNDArray.__array__()` method
https://github.com/numba/numba/blob/00ad12751725391e8423d67ef845ba56110c55df/numba/cuda/cudadrv/devicearray.py#L482-L486

In above, passing even `None` (dtype) to `__array__` of ndarray will return new array with default dtype (`float64`)
`ndarray.__array__` docs - https://numpy.org/doc/stable/reference/generated/numpy.ndarray.__array__.html
```python
>>> import numpy as np
>>> a = np.arange(10, dtype=np.int16)   
>>> b = a.__array__(np.int16)          # no copy
>>> c = a.__array__(None)              # copy
>>> d = a.__array__()                  # no copy
>>> print(f"{a.dtype=}")
>>> print(f"{b.dtype=}")
>>> print(f"{c.dtype=}")
>>> print(f"{d.dtype=}")
a.dtype=dtype('int16')
b.dtype=dtype('int16')
c.dtype=dtype('float64')
d.dtype=dtype('int16')
```

Changing `__array__(dtype) ` to either `__array__(self.dtype) ` or `__array__() ` should fix this.


Wrong dtype when using np.asarray on DeviceNDArray
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I have tried using the latest released version of Numba (most recent is
 visible in the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included a self contained code sample to reproduce the problem.
  i.e. it's possible to run as 'python bug.py'.

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->

#### Reproduce Code

```python
>>> from numba import cuda
>>> import numpy as np
>>> arr = np.arange(10).astype(np.int16)
>>> arr.dtype
dtype('int16')

>>> darr = cuda.to_device(arr)
>>> darr.dtype
dtype('int16')

>>> arr = np.asarray(darr)
>>> arr.dtype
dtype('float64')
```

_Originally posted by @ashutoshvarma in https://github.com/numba/numba/issues/6563#issuecomment-774035027_

The `test_auto_transfer` is failing due the following change :-
Without it all tests pass without problem
```diff
@@ -107,7 +108,7 @@ class UFuncMechanism(object):
         """
         for i, ary in enumerate(self.arrays):
             if ary is not None:
-                self.argtypes[i] = ary.dtype
+                self.argtypes[i] = np.asarray(ary).dtype
```
Since `ary` would always be either `np.ndarray` or `DeviceNDArray` type,  keeping `ary.dtype` should be fine? (correct me if I missed something )


Ideally, the above change should not break anything, but for some reason, if `ary` is of DeviceNDArray type then `np.asarray(ary).dtype` gives the wrong dtype.
For DeviceNDArray ary, `np.asarray(ary)` will always be a `float64` ndarray irrespective of original dtype


```python
>>> x = np.arange(n, dtype=np.int32)
>>> dx = cuda.to_device(x)
>>> print(dx.dtype)
>>> print(np.asarray(dx).dtype)
int32
float64
```

After digging more, looks like problem might be in `DeviceNDArray.__array__()` method
https://github.com/numba/numba/blob/00ad12751725391e8423d67ef845ba56110c55df/numba/cuda/cudadrv/devicearray.py#L482-L486

In above, passing even `None` (dtype) to `__array__` of ndarray will return new array with default dtype (`float64`)
`ndarray.__array__` docs - https://numpy.org/doc/stable/reference/generated/numpy.ndarray.__array__.html
```python
>>> import numpy as np
>>> a = np.arange(10, dtype=np.int16)   
>>> b = a.__array__(np.int16)          # no copy
>>> c = a.__array__(None)              # copy
>>> d = a.__array__()                  # no copy
>>> print(f"{a.dtype=}")
>>> print(f"{b.dtype=}")
>>> print(f"{c.dtype=}")
>>> print(f"{d.dtype=}")
a.dtype=dtype('int16')
b.dtype=dtype('int16')
c.dtype=dtype('float64')
d.dtype=dtype('int16')
```

Changing `__array__(dtype) ` to either `__array__(self.dtype) ` or `__array__() ` should fix this.




Discussion:

Resolving patch:
diff --git a/numba/cuda/cudadrv/devicearray.py b/numba/cuda/cudadrv/devicearray.py
--- a/numba/cuda/cudadrv/devicearray.py
+++ b/numba/cuda/cudadrv/devicearray.py
@@ -483,7 +483,10 @@ def __array__(self, dtype=None):
         """
         :return: an `numpy.ndarray`, so copies to the host.
         """
-        return self.copy_to_host().__array__(dtype)
+        if dtype:
+            return self.copy_to_host().__array__(dtype)
+        else:
+            return self.copy_to_host().__array__()
 
     def __len__(self):
         return self.shape[0]


Test patch:
diff --git a/numba/cuda/tests/cudadrv/test_cuda_ndarray.py b/numba/cuda/tests/cudadrv/test_cuda_ndarray.py
--- a/numba/cuda/tests/cudadrv/test_cuda_ndarray.py
+++ b/numba/cuda/tests/cudadrv/test_cuda_ndarray.py
@@ -407,6 +407,12 @@ def test_devicearray_typing_order_broadcasted(self):
         d = cuda.to_device(a)
         self.assertEqual(d._numba_type_.layout, 'A')
 
+    def test_bug6697(self):
+        ary = np.arange(10, dtype=np.int16)
+        dary = cuda.to_device(ary)
+        got = np.asarray(dary)
+        self.assertEqual(got.dtype, dary.dtype)
+
 
 class TestRecarray(CUDATestCase):
     def test_recarray(self):