Skip to content
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b973ce2
Device GUFuncs: Enforce void or unspecified return type
gmarkall Aug 11, 2022
4a7b0eb
Device ufuncs: remove long-deprecated argtypes / restype support
gmarkall Aug 11, 2022
507c352
Device ufuncs: Make the kernelmap hold multiple output dtypes
gmarkall Aug 11, 2022
a43cdfc
Device ufuncs: return multiple output dtypes from schedule
gmarkall Aug 11, 2022
7a0a7bc
Device ufuncs: support callsteps holding multiple outputs
gmarkall Aug 11, 2022
9ba159c
Device ufuncs: More generality for multiple outputs in callsteps
gmarkall Aug 11, 2022
e927fc7
Device ufuncs: allow multiple kernel return values in callsteps
gmarkall Aug 11, 2022
e8b6c97
Device ufuncs: correct parameter passing for multiple outputs
gmarkall Aug 11, 2022
08e1835
CUDA ufuncs: another multiple output test
gmarkall Aug 11, 2022
b1e2dd6
Device ufuncs: fix allocation of multiple outputs
gmarkall Aug 11, 2022
6acf970
Merge remote-tracking branch 'numba/main' into issue-8303-2
gmarkall Nov 17, 2022
a650d54
Merge remote-tracking branch 'numba/main' into issue-8303-2
gmarkall Dec 2, 2022
24592e4
Skip ufunc multiple output tests on simulator
gmarkall Dec 2, 2022
d3e16c4
Merge remote-tracking branch 'numba/main' into issue-8303-2
gmarkall Feb 22, 2023
575592f
Device ufuncs: some refactors for clarity
gmarkall Feb 22, 2023
00da70f
Device ufuncs: rename `norm_inputs` to `inputs`
gmarkall Feb 22, 2023
f63ac9e
Device ufuncs: corrext description of kernelmap
gmarkall Feb 22, 2023
dac4659
Device gufunc call steps: remove some state
gmarkall Feb 22, 2023
c82575c
Device ufuncs: remove kernel_returnvalues from state
gmarkall Feb 22, 2023
b30def6
Device gufuncs: Implement call steps as an ABC
gmarkall Feb 22, 2023
8ade7fc
Device gufuncs: refactor input and output preparation
gmarkall Feb 22, 2023
e4d9e42
Device GUFuncs: Add docs and refactor
gmarkall Feb 22, 2023
583762a
Merge remote-tracking branch 'numba/main' into issue-8303-2
gmarkall Mar 7, 2023
4879b05
Remove `max_blocksize` from CUDA GUFuncs
gmarkall Mar 7, 2023
56a98d2
Merge remote-tracking branch 'numba/main' into issue-8303-2
gmarkall Mar 12, 2023
d73b7ec
Some stylistic fixes from PR #8341 feedback
gmarkall Mar 15, 2023
1a5f2f5
Rename [G]UFuncMechanism.device_array() to allocate_device_array()
gmarkall Mar 15, 2023
00eee0c
deviceufunc: clarify generality of implementation (PR #8341 feedback)
gmarkall Mar 15, 2023
efca2c5
deviceufunc.py: refer to normalized args
gmarkall Mar 15, 2023
f2a4ae3
deviceufunc.py: Clarify some docstrings, ensure `post_process_outputs()`
gmarkall Mar 15, 2023
3fe2a03
CUDA GUFuncs: Ensure the correct number of args is passed
gmarkall Mar 16, 2023
1b13d1d
CUDA GUfuncs: edit pos and kwarg argument exception message
gmarkall Mar 16, 2023
8c166f8
Device ufuncs: simplify formatting of "positional argument[s]"
gmarkall Mar 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions docs/source/cuda/ufunc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,6 @@ the CUDA ufunc functionality. This may be accomplished as follows::
def matmulcore(A, B, C):
...

There are times when the gufunc kernel uses too many of a GPU's
resources, which can cause the kernel launch to fail. The user can
explicitly control the maximum size of the thread block by setting
the `max_blocksize` attribute on the compiled gufunc object.

::

from numba import guvectorize

@guvectorize(...,>
def very_complex_kernel(A, B, C):
...

very_complex_kernel.max_blocksize = 32 # limits to 32 threads per block

.. comment

Expand Down
182 changes: 154 additions & 28 deletions numba/cuda/tests/cudapy/test_gufunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import numpy.core.umath_tests as ut

from collections import namedtuple
from numba import void, float32, float64
from numba import void, int32, float32, float64
from numba import guvectorize
from numba import cuda
from numba.cuda.testing import skip_on_cudasim, CUDATestCase
Expand All @@ -12,7 +12,7 @@
from numba.tests.support import override_config


def _get_matmulcore_gufunc(dtype=float32, max_blocksize=None):
def _get_matmulcore_gufunc(dtype=float32):
@guvectorize([void(dtype[:, :], dtype[:, :], dtype[:, :])],
'(m,n),(n,p)->(m,p)',
>
Expand All @@ -25,18 +25,15 @@ def matmulcore(A, B, C):
for k in range(n):
C[i, j] += A[i, k] * B[k, j]

gufunc = matmulcore
if max_blocksize:
gufunc.max_blocksize = max_blocksize
return gufunc
return matmulcore


@skip_on_cudasim('ufunc API unsupported in the simulator')
class TestCUDAGufunc(CUDATestCase):

def test_gufunc_small(self):

gufunc = _get_matmulcore_gufunc(max_blocksize=512)
gufunc = _get_matmulcore_gufunc()

matrix_ct = 2
A = np.arange(matrix_ct * 2 * 4, dtype=np.float32).reshape(matrix_ct, 2,
Expand All @@ -50,7 +47,7 @@ def test_gufunc_small(self):

def test_gufunc_auto_transfer(self):

gufunc = _get_matmulcore_gufunc(max_blocksize=512)
gufunc = _get_matmulcore_gufunc()

matrix_ct = 2
A = np.arange(matrix_ct * 2 * 4, dtype=np.float32).reshape(matrix_ct, 2,
Expand All @@ -66,7 +63,7 @@ def test_gufunc_auto_transfer(self):

def test_gufunc(self):

gufunc = _get_matmulcore_gufunc(max_blocksize=512)
gufunc = _get_matmulcore_gufunc()

matrix_ct = 1001 # an odd number to test thread/block division in CUDA
A = np.arange(matrix_ct * 2 * 4, dtype=np.float32).reshape(matrix_ct, 2,
Expand All @@ -80,7 +77,7 @@ def test_gufunc(self):

def test_gufunc_hidim(self):

gufunc = _get_matmulcore_gufunc(max_blocksize=512)
gufunc = _get_matmulcore_gufunc()

matrix_ct = 100 # an odd number to test thread/block division in CUDA
A = np.arange(matrix_ct * 2 * 4, dtype=np.float32).reshape(4, 25, 2, 4)
Expand All @@ -105,24 +102,9 @@ def test_gufunc_new_axis(self):
res2 = gufunc(X, np.tile(Y, (10, 1, 1)))
np.testing.assert_allclose(gold, res2)

def test_gufunc_adjust_blocksize(self):

gufunc = _get_matmulcore_gufunc(max_blocksize=512)

matrix_ct = 1001 # an odd number to test thread/block division in CUDA
A = np.arange(matrix_ct * 2 * 4, dtype=np.float32).reshape(matrix_ct, 2,
4)
B = np.arange(matrix_ct * 4 * 5, dtype=np.float32).reshape(matrix_ct, 4,
5)

gufunc.max_blocksize = 32
C = gufunc(A, B)
Gold = ut.matrix_multiply(A, B)
self.assertTrue(np.allclose(C, Gold))

def test_gufunc_stream(self):

gufunc = _get_matmulcore_gufunc(max_blocksize=512)
gufunc = _get_matmulcore_gufunc()

#cuda.driver.flush_pending_free()
matrix_ct = 1001 # an odd number to test thread/block division in CUDA
Expand Down Expand Up @@ -153,6 +135,21 @@ def copy(A, B):
for i in range(B.size):
B[i] = A[i]

A = np.arange(10, dtype=np.float32) + 1
B = np.zeros_like(A)
copy(A, out=B)
np.testing.assert_allclose(A, B)

def test_copy_unspecified_return(self):
# Ensure that behaviour is correct when the return type is not
# specified in the signature.
@guvectorize([(float32[:], float32[:])],
'(x)->(x)',
>
def copy(A, B):
for i in range(B.size):
B[i] = A[i]

A = np.arange(10, dtype=np.float32) + 1
B = np.zeros_like(A)
copy(A, out=B)
Expand Down Expand Up @@ -266,7 +263,7 @@ def foo(inp, out):
with self.assertRaises(ValueError) as raises:
foo(inp, out, out=out)

msg = "cannot specify 'out' as both a positional and keyword argument"
msg = "cannot specify argument 'out' as both positional and keyword"
self.assertEqual(str(raises.exception), msg)

def check_tuple_arg(self, a, b):
Expand Down Expand Up @@ -305,9 +302,138 @@ def test_tuple_of_array_arg(self):
self.check_tuple_arg(a, b)

def test_gufunc_name(self):
gufunc = _get_matmulcore_gufunc(max_blocksize=512)
gufunc = _get_matmulcore_gufunc()
self.assertEqual(gufunc.__name__, 'matmulcore')

def test_bad_return_type(self):
with self.assertRaises(TypeError) as te:
@guvectorize([int32(int32[:], int32[:])], '(m)->(m)',>
def f(x, y):
pass

msg = str(te.exception)
self.assertIn('guvectorized functions cannot return values', msg)
self.assertIn('specifies int32 return type', msg)

def test_incorrect_number_of_pos_args(self):
@guvectorize([(int32[:], int32[:], int32[:])],
'(m),(m)->(m)',>
def f(x, y, z):
pass

arr = np.arange(5)

# Inputs only, too few
with self.assertRaises(TypeError) as te:
f(arr)

msg = str(te.exception)
self.assertIn('gufunc accepts 2 positional arguments', msg)
self.assertIn('or 3 positional arguments', msg)
self.assertIn('Got 1 positional argument.', msg)

# Inputs and outputs, too many
with self.assertRaises(TypeError) as te:
f(arr, arr, arr, arr)

msg = str(te.exception)
self.assertIn('gufunc accepts 2 positional arguments', msg)
self.assertIn('or 3 positional arguments', msg)
self.assertIn('Got 4 positional arguments.', msg)


@skip_on_cudasim('ufunc API unsupported in the simulator')
class TestMultipleOutputs(CUDATestCase):
def test_multiple_outputs_same_type_passed_in(self):
@guvectorize([void(float32[:], float32[:], float32[:])],
'(x)->(x),(x)',
>
def copy(A, B, C):
for i in range(B.size):
B[i] = A[i]
C[i] = A[i]

A = np.arange(10, dtype=np.float32) + 1
B = np.zeros_like(A)
C = np.zeros_like(A)
copy(A, B, C)
np.testing.assert_allclose(A, B)
np.testing.assert_allclose(A, C)

def test_multiple_outputs_distinct_values(self):

@guvectorize([void(float32[:], float32[:], float32[:])],
'(x)->(x),(x)',
>
def copy_and_double(A, B, C):
for i in range(B.size):
B[i] = A[i]
C[i] = A[i] * 2

A = np.arange(10, dtype=np.float32) + 1
B = np.zeros_like(A)
C = np.zeros_like(A)
copy_and_double(A, B, C)
np.testing.assert_allclose(A, B)
np.testing.assert_allclose(A * 2, C)

def test_multiple_output_allocation(self):
@guvectorize([void(float32[:], float32[:], float32[:])],
'(x)->(x),(x)',
>
def copy_and_double(A, B, C):
for i in range(B.size):
B[i] = A[i]
C[i] = A[i] * 2

A = np.arange(10, dtype=np.float32) + 1
B, C = copy_and_double(A)
np.testing.assert_allclose(A, B)
np.testing.assert_allclose(A * 2, C)

def test_multiple_output_dtypes(self):

@guvectorize([void(int32[:], int32[:], float64[:])],
'(x)->(x),(x)',
>
def copy_and_multiply(A, B, C):
for i in range(B.size):
B[i] = A[i]
C[i] = A[i] * 1.5

A = np.arange(10, dtype=np.int32) + 1
B = np.zeros_like(A)
C = np.zeros_like(A, dtype=np.float64)
copy_and_multiply(A, B, C)
np.testing.assert_allclose(A, B)
np.testing.assert_allclose(A * np.float64(1.5), C)

def test_incorrect_number_of_pos_args(self):
@guvectorize([(int32[:], int32[:], int32[:], int32[:])],
'(m),(m)->(m),(m)',>
def f(x, y, z, w):
pass

arr = np.arange(5)

# Inputs only, too few
with self.assertRaises(TypeError) as te:
f(arr)

msg = str(te.exception)
self.assertIn('gufunc accepts 2 positional arguments', msg)
self.assertIn('or 4 positional arguments', msg)
self.assertIn('Got 1 positional argument.', msg)

# Inputs and outputs, too many
with self.assertRaises(TypeError) as te:
f(arr, arr, arr, arr, arr)

msg = str(te.exception)
self.assertIn('gufunc accepts 2 positional arguments', msg)
self.assertIn('or 4 positional arguments', msg)
self.assertIn('Got 5 positional arguments.', msg)


if __name__ == '__main__':
unittest.main()
20 changes: 6 additions & 14 deletions numba/cuda/vectorizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,8 @@ class CUDAUFuncDispatcher(object):

def __init__(self, types_to_retty_kernels, pyfunc):
self.functions = types_to_retty_kernels
self._maxblocksize = 0 # ignored
self.__name__ = pyfunc.__name__

@property
def max_blocksize(self):
return self._maxblocksize

@max_blocksize.setter
def max_blocksize(self, blksz):
self._max_blocksize = blksz

def __call__(self, *args, **kws):
"""
*args: numpy arrays or DeviceArrayBase (created by cuda.to_device).
Expand Down Expand Up @@ -94,6 +85,10 @@ class _CUDAGUFuncCallSteps(GUFuncCallSteps):
'_stream',
]

def __init__(self, nin, nout, args, kwargs):
super().__init__(nin, nout, args, kwargs)
self._stream = kwargs.get('stream', 0)

def is_device_array(self, obj):
return cuda.is_cuda_array(obj)

Expand All @@ -114,12 +109,9 @@ def to_host(self, devary, hostary):
out = devary.copy_to_host(hostary, stream=self._stream)
return out

def device_array(self, shape, dtype):
def allocate_device_array(self, shape, dtype):
return cuda.device_array(shape=shape, dtype=dtype, stream=self._stream)

def prepare_inputs(self):
self._stream = self.kwargs.get('stream', 0)

def launch_kernel(self, kernel, nelem, args):
kernel.forall(nelem, stream=self._stream)(*args)

Expand Down Expand Up @@ -177,7 +169,7 @@ def to_device(self, hostary, stream):
def to_host(self, devary, stream):
return devary.copy_to_host(stream=stream)

def device_array(self, shape, dtype, stream):
def allocate_device_array(self, shape, dtype, stream):
return cuda.device_array(shape=shape, dtype=dtype, stream=stream)

def broadcast_device(self, ary, shape):
Expand Down
Loading