Repository navigation
CUDA compare and swap with index - #8790
Conversation
| @lower(stubs.atomic.cas, types.Array, types.intp, types.Any, types.Any) | ||
| @lower(stubs.atomic.cas, types.Array, types.Tuple, types.Any, types.Any) | ||
| @lower(stubs.atomic.cas, types.Array, types.UniTuple, types.Any, types.Any) | ||
| def ptx_atomic_cas(context, builder, sig, args): |
There was a problem hiding this comment.
This function could probably be combined with the previous one, but I haven't figured out how to do this yet.
There was a problem hiding this comment.
One way to combine them would be to modify the original implementation so that it inserts the type of the idx parameter into the signature and a zero value into the args, then calls your new implementation - for example:
diff --git a/numba/cuda/cudaimpl.py b/numba/cuda/cudaimpl.py
index 5a81ea7ea..72ed22f53 100644
--- a/numba/cuda/cudaimpl.py
+++ b/numba/cuda/cudaimpl.py
@@ -920,22 +920,10 @@ def ptx_atomic_nanmin(context, builder, dtype, ptr, val):
@lower(stubs.atomic.compare_and_swap, types.Array, types.Any, types.Any)
-def ptx_atomic_cas_tuple(context, builder, sig, args):
- aryty, oldty, valty = sig.args
- ary, old, val = args
- dtype = aryty.dtype
-
- lary = context.make_array(aryty)(context, builder, ary)
- zero = context.get_constant(types.intp, 0)
- ptr = cgutils.get_item_pointer(context, builder, aryty, lary, (zero,))
-
- if aryty.dtype in (cuda.cudadecl.integer_numba_types):
- lmod = builder.module
- bitwidth = aryty.dtype.bitwidth
- return nvvmutils.atomic_cmpxchg(builder, lmod, bitwidth, ptr, old, val)
- else:
- raise TypeError('Unimplemented atomic compare_and_swap '
- 'with %s array' % dtype)
+def ptx_atomic_compare_and_swap(context, builder, sig, args):
+ sig = sig.return_type(sig.args[0], types.intp, sig.args[1], sig.args[2])
+ args = (args[0], context.get_constant(types.intp, 0), args[1], args[2])
+ return ptx_atomic_cas(context, builder, sig, args)
@lower(stubs.atomic.cas, types.Array, types.intp, types.Any, types.Any)(note also the change of name of the original function, which apparently made little sense anyway!)
There was a problem hiding this comment.
Thanks, that is much more concise.
| np.testing.assert_equal(res, gold) | ||
|
|
||
| def check_compare_and_swap(self, n, fill, unfill, dtype): | ||
| def check_cas(self, n, fill, unfill, dtype, cas_func, ndim=1): |
There was a problem hiding this comment.
This function is used to test both compare_and_swap and cas.
|
@ianthomas23 Many thanks for the patch. @gmarkall any chance you could review this please? |
|
gpuci run tests |
gmarkall
left a comment
There was a problem hiding this comment.
Many thanks for the PR! This looks good in general, and there are just a few small thoughts on the diff.
The reference docs should probably be updated to mention the new function too, in this section: https://github.com/numba/numba/blob/main/docs/source/cuda-reference/kernel.rst#synchronization-and-atomic-operations
| maxlock = threading.Lock() | ||
| minlock = threading.Lock() | ||
| caslock = threading.Lock() | ||
| casindexlock = threading.Lock() |
There was a problem hiding this comment.
For consistency I'd probably call this caslock and rename the existing caslock to compare_and_swaplock.
| class cas(Stub): | ||
| """cas(ary, idx, old, val) | ||
|
|
||
| Conditionally assign ``val`` to the element ary[idx] of an array |
There was a problem hiding this comment.
Should that refer to the position of the element rather than the value itself?:
| Conditionally assign ``val`` to the element ary[idx] of an array | |
| Conditionally assign ``val`` to the element ``idx`` of an array |
| """cas(ary, idx, old, val) | ||
|
|
||
| Conditionally assign ``val`` to the element ary[idx] of an array | ||
| ``ary`` if the current value of ary[idx] matches ``old``. |
There was a problem hiding this comment.
For formatting consistency:
| ``ary`` if the current value of ary[idx] matches ``old``. | |
| ``ary`` if the current value of ``ary[idx]`` matches ``old``. |
| @lower(stubs.atomic.cas, types.Array, types.intp, types.Any, types.Any) | ||
| @lower(stubs.atomic.cas, types.Array, types.Tuple, types.Any, types.Any) | ||
| @lower(stubs.atomic.cas, types.Array, types.UniTuple, types.Any, types.Any) | ||
| def ptx_atomic_cas(context, builder, sig, args): |
There was a problem hiding this comment.
One way to combine them would be to modify the original implementation so that it inserts the type of the idx parameter into the signature and a zero value into the args, then calls your new implementation - for example:
diff --git a/numba/cuda/cudaimpl.py b/numba/cuda/cudaimpl.py
index 5a81ea7ea..72ed22f53 100644
--- a/numba/cuda/cudaimpl.py
+++ b/numba/cuda/cudaimpl.py
@@ -920,22 +920,10 @@ def ptx_atomic_nanmin(context, builder, dtype, ptr, val):
@lower(stubs.atomic.compare_and_swap, types.Array, types.Any, types.Any)
-def ptx_atomic_cas_tuple(context, builder, sig, args):
- aryty, oldty, valty = sig.args
- ary, old, val = args
- dtype = aryty.dtype
-
- lary = context.make_array(aryty)(context, builder, ary)
- zero = context.get_constant(types.intp, 0)
- ptr = cgutils.get_item_pointer(context, builder, aryty, lary, (zero,))
-
- if aryty.dtype in (cuda.cudadecl.integer_numba_types):
- lmod = builder.module
- bitwidth = aryty.dtype.bitwidth
- return nvvmutils.atomic_cmpxchg(builder, lmod, bitwidth, ptr, old, val)
- else:
- raise TypeError('Unimplemented atomic compare_and_swap '
- 'with %s array' % dtype)
+def ptx_atomic_compare_and_swap(context, builder, sig, args):
+ sig = sig.return_type(sig.args[0], types.intp, sig.args[1], sig.args[2])
+ args = (args[0], context.get_constant(types.intp, 0), args[1], args[2])
+ return ptx_atomic_cas(context, builder, sig, args)
@lower(stubs.atomic.cas, types.Array, types.intp, types.Any, types.Any)(note also the change of name of the original function, which apparently made little sense anyway!)
| out = cuda.atomic.cas(res, gid, fill_val, ary[gid]) | ||
| old[gid] = out |
There was a problem hiding this comment.
Whilst the original atomic_compare_and_swap uses this pattern, is it a little simpler as just:
| out = cuda.atomic.cas(res, gid, fill_val, ary[gid]) | |
| old[gid] = out | |
| old[gid] = cuda.atomic.cas(res, gid, fill_val, ary[gid]) |
(and is it worth updating the original function too?)
There was a problem hiding this comment.
Done, and on the other two functions too.
| out = cuda.atomic.cas(res, gid, fill_val, ary[gid]) | ||
| old[gid] = out |
There was a problem hiding this comment.
Similar as above:
| out = cuda.atomic.cas(res, gid, fill_val, ary[gid]) | |
| old[gid] = out | |
| old[gid] = cuda.atomic.cas(res, gid, fill_val, ary[gid]) |
I've added docs for I haven't added an entry for |
| indices for indexing into multiple dimensional arrays. The number of element | ||
| in ``idx`` must match the number of dimension of ``array``. | ||
|
|
||
| Returns the value of ``array[idx]`` before the storing the new value. |
There was a problem hiding this comment.
I've removed a few extraneous the from this file, which aren't strictly speaking relevant to this PR. I can revert if preferred.
There was a problem hiding this comment.
That's great, many thanks for a nice little tidy-up!
|
I think I've addressed all review comments so far. |
|
gpuci run tests |
gmarkall
left a comment
There was a problem hiding this comment.
Thanks for the quick updates and extra tidy-up - this is looking great! I think not documenting compare_and_swap was also the right decision - thanks for your thoughtfulness!
I'm going to approve this, with the anticipation that gpuCI passes, at which point it will be ready to merge. I think skipping the buildfarm is fine for this PR since that would only cover Windows in addition to gpuCI, and the implementation overlaps with existing patterns used in the target, I don't anticipate there is much chance of a Windows-specific issue.

Closes #6702.
Currently
numba.cuda.atomic.compare_and_swaponly operates on the first array element. This PR adds support for operating on any array element by index. Much of this PR is derived from a previous attempt (#7844) with the permission of the original author @bryevdv. The new function is callednumba.cuda.atomic.casand it takes one more argument (the array index) thancompare_and_swap.My use case is in datashader to create bespoke CUDA atomic operations that are more complicated than simple add or max. Datashader essentially writes to a 2D array, each element of which represents an output pixel, and multiple CUDA threads may write to the same pixel at the same time. The indexed
casfunction allows the use of a 2D integer array as an array of mutexes, one per pixel, to limit access to a single thread at a time per pixel.For anyone interested, here is my "datashader lite" implementation in which each pixel is visited multiple times with different integer values. Each pixel stores the two maximum values, in decreasing order, of all visits to that pixel. Without the
cas-based locking mechanism the checking and shuffling operation of the max values per pixel isn't atomic.Output using this PR is:
This is my first
numbaPR 😃