Skip to content

Add JIT examples to CUDA docs - #7918

Merged
sklam merged 32 commits into
numba:mainfrom
brandon-b-miller:jit-examples
May 23, 2022
Merged

sklam merged 32 commits into
numba:mainfrom
brandon-b-miller:jit-examples

Conversation

@brandon-b-miller

Copy link
Copy Markdown
Contributor

Hi! I thought I'd add some examples I have pulled together around some use cases for cuda.jit since 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

@stuartarchibald stuartarchibald added 3 - Ready for Review Effort - medium Medium size effort needed CUDA CUDA related issue/PR labels Mar 17, 2022

@sklam sklam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@gmarkall

Copy link
Copy Markdown
Member

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:

  • Simplify the wording / shorten it,
  • Change the example code into a test - the same process should be repeatable for the other examples,
  • Present more idiomatic usage of CUDA (e.g. not passing in array bounds, using a grid of reasonable size, using device_array_like for empty allocations, etc.)

I'll continue to look at the examples but wanted to push this feedback sooner rather than later.

Comment thread docs/source/cuda/examples.rst Outdated
data[i] = next_temp

# wait for every thread to write before moving on
cuda.syncthreads()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread docs/source/cuda/examples.rst Outdated
data[i] = next_temp

# wait for every thread to write before moving on
cuda.syncthreads()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread docs/source/cuda/examples.rst Outdated

.. code-block:: python

print(data_gpu.copy_to_host())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to include before and after plots generated with something like:

import matplotlib.pyplot as plt

plt.plot(np.arange(len(data)), data_gpu.copy_to_host())
plt.title('Heat in a bar')
plt.xlabel('Position')
plt.ylabel('Temperature')
plt.show()

to give something like:

temp

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/source/cuda/examples.rst Outdated

.. code-block:: python

print(data_gpu.copy_to_host())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to include before and after plots generated with something like:

import matplotlib.pyplot as plt

plt.plot(np.arange(len(data)), data_gpu.copy_to_host())
plt.title('Heat in a bar')
plt.xlabel('Position')
plt.ylabel('Temperature')
plt.show()

to give something like:

temp

Comment thread docs/source/cuda/examples.rst Outdated

# check elements 'forward' of this one
# until a new session boundary is found
while results[gid + look_ahead] == 0:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is it guaranteed that if another session boundary is written by the next thread, it is correctly seen by the current thread?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/source/cuda/examples.rst Outdated

# check elements 'forward' of this one
# until a new session boundary is found
while results[gid + look_ahead] == 0:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is it guaranteed that if another session boundary is written by the next thread, it is correctly seen by the current thread?

Comment thread docs/source/cuda/examples.rst Outdated
Comment on lines +297 to +308
### 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be made a little clearer what this is doing by writing it as:

Suggested change
### 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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What did you think of this suggestion? I think the original version of the code is in the test file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/source/cuda/examples.rst Outdated

@cuda.jit
def sessionize(
user_id, timestamp, results, size

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not pass in the size and instead use len(user_id) in the kernel?

Comment thread docs/source/cuda/examples.rst Outdated

@cuda.jit
def sessionize(
user_id, timestamp, results, size

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not pass in the size and instead use len(user_id) in the kernel?

Comment thread docs/source/cuda/examples.rst Outdated

.. code-block:: python

X = cp.array([1, 10, 234])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I do love to champion CuPy / Numba interoperability, we should probably keep the Numba examples self-contained and use Numba device arrays here.

Comment thread docs/source/cuda/examples.rst Outdated

..code-block:: python

print(business_logic(1,2,3)) # -126.79644737231007

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Comment thread docs/source/cuda/examples.rst Outdated
.. code-block:: python

@cuda.jit
def f(res, xarr, yarr, zarr, size):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use len(xarr) or len(res) instead of passing in the size?

Comment thread docs/source/cuda/examples.rst Outdated

@gmarkall gmarkall left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@gmarkall gmarkall added 4 - Waiting on author Waiting for author to respond to review and removed 3 - Ready for Review labels Mar 31, 2022
brandon-b-miller and others added 6 commits April 28, 2022 14:44
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
@gmarkall

Copy link
Copy Markdown
Member

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.
@gmarkall

Copy link
Copy Markdown
Member

gpuci run tests

gmarkall
gmarkall previously approved these changes Apr 29, 2022

@gmarkall gmarkall left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many thanks!

@gmarkall gmarkall added Pending BuildFarm For PRs that have been reviewed but pending a push through our buildfarm 4 - Waiting on CI Review etc done, waiting for CI to finish and removed 4 - Waiting on author Waiting for author to respond to review labels Apr 29, 2022
@gmarkall

Copy link
Copy Markdown
Member

@stuartarchibald / @esc Could this have a CUDA buildfarm run please?

@esc

esc commented May 16, 2022

Copy link
Copy Markdown
Member

Build numba_smoketest_cuda_yaml_127 has started

@gmarkall

Copy link
Copy Markdown
Member

gpuci run tests

@gmarkall

Copy link
Copy Markdown
Member

@esc could this have another buildfarm run please?

@esc

esc commented May 17, 2022

Copy link
Copy Markdown
Member

@esc could this have another buildfarm run please?

Build numba_smoketest_cuda_yaml_128 has started

@esc

esc commented May 17, 2022

Copy link
Copy Markdown
Member

@esc could this have another buildfarm run please?

Build numba_smoketest_cuda_yaml_128 has started

all green 💚

@gmarkall

Copy link
Copy Markdown
Member

all green green_heart

Many thanks!

@gmarkall gmarkall added BuildFarm Passed For PRs that have been through the buildfarm and passed 5 - Ready to merge Review and testing done, is ready to merge and removed Pending BuildFarm For PRs that have been reviewed but pending a push through our buildfarm 4 - Waiting on CI Review etc done, waiting for CI to finish labels May 18, 2022
@gmarkall gmarkall added this to the Numba 0.56 RC milestone May 18, 2022
@sklam
sklam merged commit ad8fbd9 into numba:main May 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

5 - Ready to merge Review and testing done, is ready to merge BuildFarm Passed For PRs that have been through the buildfarm and passed CUDA CUDA related issue/PR Effort - medium Medium size effort needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add numba.cuda.jit examples

5 participants