[doc-only] cuda_core: add "10 minutes to cuda.core" tutorial to docs#2289
Conversation
This comment has been minimized.
This comment has been minimized.
Add a beginner-friendly "10 minutes to cuda.core" guide that walks through the core workflow (select a device, compile a kernel, allocate memory, copy, launch, time with events, use multiple streams, capture a CUDA graph, and interoperate with CuPy/PyTorch), then wire it into the cuda.core docs table of contents between the installation and examples sections. Signed-off-by: Sri Koundinyan <skoundinyan@nvidia.com>
976cc4d to
046eac2
Compare
Andy-Jost
left a comment
There was a problem hiding this comment.
This looks like a great addition to the docs. Blocking merge for a few minor correctness issues but mostly it looks fine to me.
| :doc:`examples <examples>` show this scaled up across multiple GPUs. | ||
|
|
||
|
|
||
| Capturing work in a CUDA graph |
There was a problem hiding this comment.
It's probably worth adding a sentence to note that cuda.core also provides an explicit graph interface. It can be used to build, inspect, and modify graphs (including graphs built via stream capture). There are lots of examples under cuda_core/tests/graph.
There was a problem hiding this comment.
Good call, added a short paragraph pointing to the explicit cuda.core.graph interface with a link to the graph tests.
|
|
||
| .. code-block:: console | ||
|
|
||
| $ pip install cuda-core[cu13] |
There was a problem hiding this comment.
Square brackets require being quoted in most shells.
|
|
||
| .. code-block:: python | ||
|
|
||
| dev = Device() # device 0 by default; Device(1) selects another GPU |
There was a problem hiding this comment.
The comment is misleading. Device() returns the current device, while falling back to device 0 if none is current.
There was a problem hiding this comment.
Fixed: now "current device (device 0 if none is current)".
| :class:`Device` objects are thread-local singletons: ``Device(0)`` always hands | ||
| you back the same object for device 0 on that thread, so libraries sharing your | ||
| process see and use the same GPU. Rich device attributes live under |
There was a problem hiding this comment.
This is a false cause-and-effect. Thread-locality is not the reason why libraries can share the same device.
What users may observe is that on a given thread Device(0) always hands back the same object for device 0, so its context, default memory resource, and cached attributes are reused rather than rebuilt on each call.
On different threads, Device(0) returns different objects bound to distinct contexts. This makes it possible to interact with CUDA from multiple threads in cuda.core.
There was a problem hiding this comment.
Got it. Updated to include a short note (same-thread Device(0) returns the same object, so repeat calls are cheap) and left the primary-context sharing content to the interop section.
| try: | ||
| Program(bad, code_type="c++", options=opts).compile("cubin") | ||
| except Exception as e: | ||
| print(e) # includes: error: identifier "not_a_real_symbol" is undefined |
There was a problem hiding this comment.
Could say assert 'identifier "not_a_real_symbol" is undefined' in str(e)
| Capturing work in a CUDA graph | ||
| ------------------------------ | ||
|
|
||
| When you launch the same sequence of operations repeatedly, per-launch CPU |
There was a problem hiding this comment.
We can't use "launch" to refer to an arbitrary CUDA operation. CUDA graphs capture many types of operations, and kernel launches are only one type.
There was a problem hiding this comment.
Reworded to "operations" rather than "launches". "launch" now only refers to launching the graph itself.
| ------------------------------ | ||
|
|
||
| When you launch the same sequence of operations repeatedly, per-launch CPU | ||
| overhead adds up. A CUDA graph lets you record that sequence once and replay it |
There was a problem hiding this comment.
"Sequence" here is potentially confusing. In general, a graph is not a sequence.
There was a problem hiding this comment.
Got it. Removed "sequence" from the prose and the code comment.
|
|
||
| When you launch the same sequence of operations repeatedly, per-launch CPU | ||
| overhead adds up. A CUDA graph lets you record that sequence once and replay it | ||
| with a single launch. Use the stream's graph builder: begin building, issue the |
There was a problem hiding this comment.
The graph lets you replay a computation many times without incurring the cost of CUDA API calls each time.
There was a problem hiding this comment.
A slight reframing: "record that work once and then launch the whole graph repeatedly, instead of re-issuing each operation every time."
| ts = dev.create_stream(PyTorchStreamWrapper(torch.cuda.current_stream())) | ||
| launch(ts, config, scale, t.data_ptr(), np.float32(2.0), np.uint64(t.numel())) | ||
| ts.sync() | ||
| assert torch.allclose(t, torch.full_like(t, 2.0)) |
There was a problem hiding this comment.
Exact comparison is appropriate here. It could match line 342 exactly or at least use torch.equal instead of allclose.
| # or scope a buffer with a with-statement: | ||
| with dev.allocate(nbytes, stream=stream) as tmp: | ||
| ... # tmp is freed at the end of the block |
There was a problem hiding this comment.
@leofang I'm surprised to see context manager support in Buffer. I'm for it, but I thought we were avoiding context managers in cuda.core. FYI, it was added in PR #1701 ("Make GraphicsResource inherit from Buffer").
Users reading this might expect other cuda.core objects with close members to support this as well. Is that something we can add?
There was a problem hiding this comment.
I thought there was a special reason why graphic resource works differently. Are we teaching it here? If not, we should not teach about context managers.
There was a problem hiding this comment.
Removed the context-manager example
| for _ in range(100): | ||
| launch(stream, config, scale, dbuf, np.float32(1.0), np.uint64(n)) | ||
| stream.record(end) | ||
| end.sync() |
There was a problem hiding this comment.
Should this be stream.sync()?
There was a problem hiding this comment.
Thanks. I kept end.sync(). It matches the pattern in the Event docstring. But stream.sync() would also work since end is the last op.
| # PyTorch: wrap torch's stream via the __cuda_stream__ protocol | ||
| import torch | ||
|
|
||
| class PyTorchStreamWrapper: |
There was a problem hiding this comment.
This adapter is no longer needed. You can use
ts = dev.create_stream(torch.cuda.current_stream())
below.
There was a problem hiding this comment.
Dropped the wrapper. Now dev.create_stream(torch.cuda.current_stream()) directly.
Mainly: fix the Device()/thread-local wording for accuracy, simplify the PyTorch stream example (torch supports __cuda_stream__ natively), stop teaching Buffer as a context manager, and tidy up the CUDA graph section. Signed-off-by: Sri Koundinyan <skoundinyan@nvidia.com>
Andy-Jost
left a comment
There was a problem hiding this comment.
Looks great, thanks for the updates.
|
This adds a beginner-friendly "10 minutes to cuda.core" guide to the
cuda.coredocs. It walks through the core workflow with small, runnable snippets: selecting a device, compiling a CUDA C++ kernel, allocating memory, copying, launching, timing with events, using multiple streams, capturing a CUDA graph, and interoperating with CuPy and PyTorch. The new page lives atcuda_core/docs/source/10_minutes_to_cuda_core.rstand is added to the docs table of contents between the Installation and Examples sections. All code snippets were verified to run end-to-end on CUDA 13.