Repository navigation
[BUG]: CUDA API calls through cuda-bindings 3x slower than direct CUDA C++ API calls #659
Description
Is this a duplicate?
- I confirmed there appear to be no duplicate issues for this bug and that I agree to the Code of Conduct
Type of Bug
Performance
Component
cuda.bindings
Describe the bug
I have a same CUDA kernel, and I am calling it using two different methods.
- Compile the kernel to
.cubin, load it in Python and call it usingcuda-bindings. The TMA descriptors are also created viacuda-bindings. - Compile the kernel and relevant C++ host functions (including TMA desc, kernel attribute and launch config) to
.so, load the.soin Python and call the C++ host function.
As revealed in the two timelines below, the first path is 3x slower than the second path.


How to Reproduce
Follow the instructions in the reproduction repo https://github.com/lucifer1004/cuda-python-repro
Expected behavior
CUDA Python's perf should be on par with CUDA C++.
Operating System
Ubuntu Linux 24.04
nvidia-smi output
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.9 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA H100 80GB HBM3 On | 00000000:53:00.0 Off | 0 |
| N/A 27C P0 69W / 700W | 0MiB / 81559MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 1 NVIDIA H100 80GB HBM3 On | 00000000:64:00.0 Off | 0 |
| N/A 28C P0 68W / 700W | 0MiB / 81559MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 2 NVIDIA H100 80GB HBM3 On | 00000000:75:00.0 Off | 0 |
| N/A 27C P0 69W / 700W | 0MiB / 81559MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 3 NVIDIA H100 80GB HBM3 On | 00000000:86:00.0 Off | 0 |
| N/A 29C P0 68W / 700W | 0MiB / 81559MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 4 NVIDIA H100 80GB HBM3 On | 00000000:97:00.0 Off | 0 |
| N/A 30C P0 69W / 700W | 0MiB / 81559MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 5 NVIDIA H100 80GB HBM3 On | 00000000:A8:00.0 Off | 0 |
| N/A 28C P0 68W / 700W | 0MiB / 81559MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 6 NVIDIA H100 80GB HBM3 On | 00000000:B9:00.0 Off | 0 |
| N/A 28C P0 69W / 700W | 0MiB / 81559MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 7 NVIDIA H100 80GB HBM3 On | 00000000:CA:00.0 Off | 0 |
| N/A 26C P0 66W / 700W | 0MiB / 81559MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+
Activity
capybara-club commentedon Jun 5, 2025
@leofang
Maybe related. I have a cuda kernel that seems to run slow if i compile it to cubin and run it in the same python run even with a round of warmups. However after i cache the cubin to a database and run it a second time from a second python run it's much faster.
If this is odd and unexpected behavior I can paste a link to the code that's doing this.
leofang commentedon Sep 10, 2025
@capybara-club could you kindly create a new issue with a reproducer so that we can look at? It is unlikely that we can help you based only on this comment 😅
@lucifer1004 sorry this fell through the cracks. A few comments
kernel.pyallocates 3 TMA descriptors, set kernel attribute, and then launch a kernelwrapped_kernel.cumoves the kernel attribute setting out of the timing loop (here)kernel.pyis written adds obvious getattr overheads (very unnatural to Python eyes 😅): Instead ofplease rewrite it to import what you need
or even better
leofang commentedon Sep 10, 2025
cc @mdboom for vis
lucifer1004 commentedon Oct 28, 2025
@leofang I have simplified the code to only measure TMA descriptor creation. And here are the updated results:
CUDA 13.0
CUDA 12.9
mdboom commentedon Jan 29, 2026
I thought I'd report back on some findings. I have had a hard time getting access to hardware that can run the reproducer, however, it is easy enough to comment out the actual CUDA call and just measure the Python calling overhead on its own. That's good enough to find improvements while there is a lot of low-hanging fruit to be had, but eventually I'll probably need to reproduce more accurately. Just be aware that /my/ numbers don't include any time doing actual work on the device.
Accessing values of enums
It turns out that instead of
doing this:
reduces the function call overhead by about 25%
This is because
IntEnuminherits from int, so there is a good fast path in C to grab its value quickly this way.enum.valuehas to do both a dictionary lookup forvalueand run the Python-writtenvalueproperty code.This feels like a simple win that will be easy to implement.
Converting short lists/tuples of
driver.cuuint32_tvaluesThis function has 4 arguments which take short lists or tuples of
uint32_toruint64_t.The first performance problem is the type checking happens inside of a generator expression:
This generates a separate Python extension function to handle the generator callback which is then handed to Python's
allfunction. This is significantly more expensive than a C for loop. Additionally, this usesisinstance, when an exact type comparison would do:type(_x) is cuuint32_t. Changing this to a for loop and a direct type check reduces overhead by another 10%:The next thing we can tackle is that when the length of the list is > 1, it dynamically allocates a buffer on the heap to copy raw integer values into, and frees it at the end. This API (and I /think/ all CUDA APIs) have a maximum dimensionality of 5, so we can just statically allocate an array of 5 on the stack, avoiding dynamic memory allocation. This should be faster and also eliminate the latent memory leaks in the current implementation.
All told, the solution I came up with, which also leans on Cython to do the type-checking implicitly with a
?cast, gives another 25% speedup:This seems like a manageable change. The part that makes it a bit tricky in the generator is that we can only do this static allocation trick for arguments that are small sequences representing dimensions -- it wouldn't work in the general case of any list of ints. So we may need to add metadata about these arguments where this optimization can be applied.
Using the
Py_bufferinterface (rejected)I also experimented with using the
Py_bufferinterface for these small arrays. The idea being that rather than passing in a list of special uint32_t objects, you would use something exposing the buffer interface like a stdlibarray.arrayor Numpy array. Theoretically, it could be faster because all elements are guaranteed to be the same (so you don't have to do a linear scan to typecheck), and in the common case it boils down to a raw pointer to contiguous memory so no copying is required. I implemented this and saw no measurable difference from the solution above, so it is definitely not worth that backward-incompatibility downside. Were these arrays larger, we might see benefit, but with these < 5 element things it seems to be a wash.leofang commentedon Feb 2, 2026
Yes, this makes sense because TMA currently only supports up to 5D. FWIW, Cython's default for memoryview is 8D.
mdboom commentedon Feb 5, 2026
@lucifer1004: With the 2 low-hanging fruit fixes merged, it would be great to hear how this affects your measurements on your hardware when you have a chance.
lucifer1004 commentedon Feb 7, 2026
Is there nightly build I can use for benchmarking?
mdboom commentedon Feb 18, 2026
Sorry for the delayed response. You can download a recent build from any of our CI actions here: https://github.com/NVIDIA/cuda-python/actions/workflows/ci.yml
leofang commentedon Apr 8, 2026
Closing this issue since the majority of perf improvement work is already done. See the detailed breakdown (and remaining tasks) in #1645.