Repository navigation
CUDA: Support multiple outputs for Generalized Ufuncs - #8341
Conversation
This only adds a check to prevent users providing an invalid signature - it does not affect functionality.
Also add a test for a CUDA multiple output gufunc now that it works.
In this tests the outputs are expected to have different values
Also add some more tests
|
gpuci run tests |
|
gpuci run tests |
|
gpuci run tests |
|
A note to myself: the following patch was supposed to be a start at refactoring or simplifying the logic to more explicitly spell out what it does: diff --git a/numba/np/ufunc/deviceufunc.py b/numba/np/ufunc/deviceufunc.py
index bccc2b39c..058c54a7f 100644
--- a/numba/np/ufunc/deviceufunc.py
+++ b/numba/np/ufunc/deviceufunc.py
@@ -731,7 +731,6 @@ class GUFuncCallSteps(object):
'norm_inputs',
'kernel_returnvalues',
'kernel_parameters',
- '_is_device_array',
'_need_device_conversion',
]
@@ -756,17 +755,16 @@ class GUFuncCallSteps(object):
self.outputs.append(output)
user_outputs_are_device.append(user_output_is_device)
- self._is_device_array = [self.is_device_array(a) for a in self.args]
- self._need_device_conversion = (not any(self._is_device_array) and
- not any(user_outputs_are_device))
+ _is_device_array = [self.is_device_array(a) for a in self.args]
+ # - If any of the arguments are device arrays, we leave the output on
+ # the device.
+ self._need_device_conversion = (not any(_is_device_array) and
+ not any(user_outputs_are_device))
+ print(f"Need device conversion: {self._need_device_conversion}")
# Normalize inputs
- inputs = []
- for a, isdev in zip(self.args, self._is_device_array):
- if isdev:
- inputs.append(self.as_device_array(a))
- else:
- inputs.append(np.asarray(a))
+ inputs = [a if self.is_device_array(a) else np.asarray(a)
+ for a in self.args]
self.norm_inputs = inputs[:nin]
# Check if there are extra arguments for outputs.
@@ -804,12 +802,8 @@ class GUFuncCallSteps(object):
self.kernel_returnvalues = retvals
def prepare_kernel_parameters(self):
- params = []
- for inp, isdev in zip(self.norm_inputs, self._is_device_array):
- if isdev:
- params.append(inp)
- else:
- params.append(self.to_device(inp))
+ params = [p if self.is_device_array(p) else self.to_device(p)
+ for p in self.norm_inputs]
assert all(self.is_device_array(a) for a in params)
self.kernel_parameters = params
but it introduces a failure: |
|
gpuci run tests |
Some small comments and structural edits, but mainly focused on being clearer about the logic deciding on whether to copy the result to the host.
The only work that the term `norm` is doing is making things more confusing.
|
gpuci run tests |
|
gpuci run tests |
|
@stuartarchibald Many thanks for the review - all comments should now be addressed so this should be ready for another look. |
|
gpuci run tests |
|
@stuartarchibald Many thanks for the round of review - I've addressed both comments, so this should be ready for another look. |
Many thanks, one minor suggestion RE string formatting else looks good. |
|
gpuci run tests |
|
@stuartarchibald Many thanks for the suggestion, which I've amended slightly. Assuming gpuci is OK this should be OK for another look. |
stuartarchibald
left a comment
There was a problem hiding this comment.
Thanks for the patch and fixes.
|
Buildfarm ID: |
This has not yet completed, but the build job for |
It passed on restart. Am trying another independent run: Buildfarm ID: |
This passed. |
Apologies, was looking at 189 again, 190 is still running, will post when it is done. |
Build |
|
I've manually tested this on Windows without issue. |
Many thanks for checking @gmarkall, this gives confidence that #8341 (comment) is/was unrelated. Will approve this now and keep a watch on future CUDA builds. |
Support for multiple outputs from CUDA gufuncs - previously only one output was supported, so it was possible to write a signature like
'(x)->(x)', but not'(x)->(x),(x)'.This PR consists of:
numba.np.ufunc.deviceufunc:outdtype->outdtypes)DeviceGUFuncVectorize.add()where I could. The logic is a bit contrived, but I've tried to make it clearer and better-explained in the comments.DeviceGUFuncVectorize.__call__()and theGUFuncCallStepsclass. The code in these was so far off looking like it does what it actually does that I ended up evolving them to something that looks completely different. For review purposes, it probably makes the most sense to check out the code locally and read these implementations, and ignore the diff, because a side-by-side comparison is (IMHO) impossible to make sense of._CUDAGufuncCallSteps, to remove theprepare_inputs()method, which shadowed the new method of the same name inGUFuncCallSteps, and didn't actually prepare any input - its functionality, which setself._stream, has been moved to the__init__()method, which still has an equivalent effect from that location - i.e. the_streamis set by the time it is required, and also not too soon.max_blocksizeproperty of CUDA GUFuncs. This has always (literally, always) been ignored, so it makes no sense to keep it.test_gufunc.pyfile in addition to the new tests for multiple outputs.Usually I'd squash this down into a tidy individually-reviewable patch series, but because it was so hard to understand how things worked I have left the evolutionary changes I made in, because if there's a regression that will make it much easier to bisect - I generally kept things passing tests in each commit as I went.
CC @s-m-e in support of your Poliastro use cases.
Fixes #8303.