Repository navigation
Add JIT examples to CUDA docs - #7918
Conversation
sklam
left a comment
There was a problem hiding this comment.
Thank you for the contribution.
One worry that I have is ease of testing the validity of the examples as API may drift. I'd suggest include the examples as a runnable .py script and use literalinclude to reference the lines. See example in https://github.com/numba/numba/blame/b57da5b52cb330ec03837a721e49eb8c9b01aa66/docs/source/cuda/examples.rst#L485
|
I have looked over the vector example, and in https://github.com/gmarkall/numba/tree/jit-examples in the commit gmarkall@7632af7 I have some suggested edits. The main aim behind the edits is to:
I'll continue to look at the examples but wanted to push this feedback sooner rather than later. |
| data[i] = next_temp | ||
|
|
||
| # wait for every thread to write before moving on | ||
| cuda.syncthreads() |
There was a problem hiding this comment.
How does syncing at the block level have any meaningful effect here?
It feels like this example needs two copies of the domain along with a cooperative grid sync, or one kernel launch per timestep in order to ensure that all inputs are taken from the previous timestep and not some values coming from the current timestep at the boundaries between blocks.
Am I misunderstanding the operation of the example?
| data[i] = next_temp | ||
|
|
||
| # wait for every thread to write before moving on | ||
| cuda.syncthreads() |
There was a problem hiding this comment.
How does syncing at the block level have any meaningful effect here?
It feels like this example needs two copies of the domain along with a cooperative grid sync, or one kernel launch per timestep in order to ensure that all inputs are taken from the previous timestep and not some values coming from the current timestep at the boundaries between blocks.
Am I misunderstanding the operation of the example?
There was a problem hiding this comment.
Yeah, this example was one that I remembered from academia. I think I derived this from some memory of a pure c++ example which used a grid wide barrier. This will be updated.
|
|
||
| .. _cuda_reduction_shared: | ||
|
|
||
| Shared Memory Reduction |
There was a problem hiding this comment.
We should probably note that this as an example of how to use shared memory rather than the way in which reductions should actually be implemented using Numba - we could point to the @cuda.reduce decorator (https://numba.readthedocs.io/en/stable/cuda/reduction.html) and/or demonstrate its use, as it provides an easier interface for building a reduction and uses a slightly more sophisticated (hopefully more performant) implementation: https://github.com/numba/numba/blob/main/numba/cuda/kernels/reduction.py
There was a problem hiding this comment.
I will add a comment here, and I used the cuda.reduce technique later on in the monte-carlo example.
|
|
||
| .. _cuda_reduction_shared: | ||
|
|
||
| Shared Memory Reduction |
There was a problem hiding this comment.
We should probably note that this as an example of how to use shared memory rather than the way in which reductions should actually be implemented using Numba - we could point to the @cuda.reduce decorator (https://numba.readthedocs.io/en/stable/cuda/reduction.html) and/or demonstrate its use, as it provides an easier interface for building a reduction and uses a slightly more sophisticated (hopefully more performant) implementation: https://github.com/numba/numba/blob/main/numba/cuda/kernels/reduction.py
|
|
||
| .. code-block:: python | ||
|
|
||
| print(data_gpu.copy_to_host()) |
There was a problem hiding this comment.
There was a problem hiding this comment.
I really wanted to include some plots here but didn't see any precedent for that elsewhere in the docs. Happy to add them in though.
|
|
||
| .. code-block:: python | ||
|
|
||
| print(data_gpu.copy_to_host()) |
There was a problem hiding this comment.
|
|
||
| # check elements 'forward' of this one | ||
| # until a new session boundary is found | ||
| while results[gid + look_ahead] == 0: |
There was a problem hiding this comment.
How is it guaranteed that if another session boundary is written by the next thread, it is correctly seen by the current thread?
There was a problem hiding this comment.
Ahh, you are right. The reason is these used to be two separate kernels that I fused into one, forgetting that the synchronization that comes from two distinct kernel launches is actually critical. The first used to write the boundaries and the second built out the IDs. Will look into alternatives.
|
|
||
| # check elements 'forward' of this one | ||
| # until a new session boundary is found | ||
| while results[gid + look_ahead] == 0: |
There was a problem hiding this comment.
How is it guaranteed that if another session boundary is written by the next thread, it is correctly seen by the current thread?
| ### DETERMINE SESSION BOUNDARIES | ||
| is_sess_boundary = 0 | ||
| if 0 < gid < size: | ||
| if ( | ||
| user_id[gid] != user_id[gid - 1] | ||
| or timestamp[gid] - timestamp[gid - 1] > session_timeout | ||
| ): | ||
| is_sess_boundary = 1 | ||
| else: | ||
| is_sess_boundary = 0 | ||
| else: | ||
| is_sess_boundary = 1 |
There was a problem hiding this comment.
Could it be made a little clearer what this is doing by writing it as:
| ### DETERMINE SESSION BOUNDARIES | |
| is_sess_boundary = 0 | |
| if 0 < gid < size: | |
| if ( | |
| user_id[gid] != user_id[gid - 1] | |
| or timestamp[gid] - timestamp[gid - 1] > session_timeout | |
| ): | |
| is_sess_boundary = 1 | |
| else: | |
| is_sess_boundary = 0 | |
| else: | |
| is_sess_boundary = 1 | |
| # Exit early if our gid is beyond the bounds of the data | |
| if (gid >= len(user_id)): | |
| return | |
| # Determine whether we're on a session boundary | |
| different_user = (user_id[gid] != user_id[gid - 1]) | |
| time_diff = timestamp[gid] - timestamp[gid - 1] | |
| timed_out = time_diff > session_timeout | |
| first_datapoint = (gid == 0) | |
| is_sess_boundary = different_user or timed_out or first_datapoint |
i.e. the variable names describe the checks and the condition for being a session boundary "reads" in plain language as opposed to being a more complicated expression.
Also this would allow the next if to be removed and the code under it to be moved to the same level as the rest of the function.
(Note that my indentation in the edited code may not match that in the diff at present - there's some weird 3-space indentation going on in places)
There was a problem hiding this comment.
What did you think of this suggestion? I think the original version of the code is in the test file.
There was a problem hiding this comment.
I had tried this directly and the initial problem was that the 0'th thread can't safely index gid - 1, so it seemed like we can't avoid a special case for that thread. That's how I arrived at the current logic which I think is a mix between the original and this suggestion. I definitely liked the way this logic read more easily so I tried to incorporate that into the approach. Happy to iterate on this though if there's something I am missing.
|
|
||
| @cuda.jit | ||
| def sessionize( | ||
| user_id, timestamp, results, size |
There was a problem hiding this comment.
Can we not pass in the size and instead use len(user_id) in the kernel?
|
|
||
| @cuda.jit | ||
| def sessionize( | ||
| user_id, timestamp, results, size |
There was a problem hiding this comment.
Can we not pass in the size and instead use len(user_id) in the kernel?
|
|
||
| .. code-block:: python | ||
|
|
||
| X = cp.array([1, 10, 234]) |
There was a problem hiding this comment.
Although I do love to champion CuPy / Numba interoperability, we should probably keep the Numba examples self-contained and use Numba device arrays here.
|
|
||
| ..code-block:: python | ||
|
|
||
| print(business_logic(1,2,3)) # -126.79644737231007 |
There was a problem hiding this comment.
I think the 4-space indentation makes this fail to render as code (but it should probably be converted to a tested example and literalincluded anyway)
| .. code-block:: python | ||
|
|
||
| @cuda.jit | ||
| def f(res, xarr, yarr, zarr, size): |
There was a problem hiding this comment.
Can we use len(xarr) or len(res) instead of passing in the size?
gmarkall
left a comment
There was a problem hiding this comment.
Many thanks for the PR - in addition to the commit I linked to in the previous comment, I've left some additional comments on the diff. I think that each of the examples needs to be converted to a test and literalincluded as @sklam suggests, and this will also highlight a couple of things that prevent the code running as-is.
The use of CuPy is nice but I'd prefer if we kept to Numba code for these examples, although it would be great if we also had a Numba +CuPy interop example (I'm not asking for one to be included in this PR though :-) )
Once the examples are converted to tests I think we might be able to streamline some of the explanations and some of the code too, but I'd prefer to iterate on that once we have the code converted to tested samples.
Co-authored-by: Graham Markall <535640+gmarkall@users.noreply.github.com>
- Use two swappable buffers so that only a single sync is needed - Add a check so that threads outside the domain boundary don't compute - Add a check based on the integral of the solution
|
gpuci run tests |
The example relies on a single block being launched, but a forall isn't guaranteed to generate a configuration with a single block - for example, on the RTX A6000 it creates the configuration `[2, 768]`. Explicitly configuring the launch resolves this issue.
|
gpuci run tests |
|
@stuartarchibald / @esc Could this have a CUDA buildfarm run please? |
|
|
|
gpuci run tests |
|
@esc could this have another buildfarm run please? |
|
all green 💚 |
Many thanks! |

Hi! I thought I'd add some examples I have pulled together around some use cases for
cuda.jitsince there is an issue for it. I added some of my own as well as those I could glean from the discussion in the issue.There was some discussion of moving these to the numba examples repo but after discussion with @gmarkall we decided to submit them here.
Closes #4266