You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Querying current device is slow compared to CuPy #439
Are there tricks (e.g., caching) CuPy is employing here that cuda.core can use as well? Alternately, is there another way for me to use cuda.core or cuda.bindings to get this information quickly? Note that for my use case, I'm not super concerned about the first call to Device(), but I do want subsequent calls to be trivially inexpensive if the current device hasn't changed.
Using the low-level cuda.bindings is also not quite as fast:
In [11]: defget_cc():
...: dev=runtime.cudaGetDevice()[1]
...: returndriver.cuDeviceComputeCapability(dev)
...:
In [12]: get_cc()
Out[12]: (<CUresult.CUDA_SUCCESS: 0>, 7, 5)
In [13]: %timeitget_cc()
597ns ± 0.494nsperloop (mean ± std. dev. of7runs, 1,000,000loopseach)
I did some refactoring of Device.__new__() to replace cudart APIs by driver APIs, and found the perf gets even worse. Out of curiosity, I did this quick profiling and got very surprised: (the following already has the primary context set to current)
Version 1. This is a minimized version of cuCtxGetDevice to judge the speed of light. CUdevice is created by user and we skip returning the error code:
Wow! Great findings Vlad! It is insane how slow IntEnum (or any Enum-subclasses from the standard library) is...
I wonder if it makes sense to build an internal cache ourselves? The built-in dict lookup is very fast (>10x). Something like
_m =dict(((int(v), v) for k, v in driver.CUresult.__members__.items()))
defcuCtxGetDevice():
cdef CUdevice device = CUdevice()
err =int(cydriver.cuCtxGetDevice(<cydriver.CUdevice*>device._pvt_ptr))
return (_m(err), device)
This is reasonably fast from what I see:
In [51]: m=dict(((int(v), v) fork, vindriver.CUresult.__members__.items()))
In [52]: %timeitm[100]
20.5ns ± 0.0777nsperloop (mean ± std. dev. of7runs, 10,000,000loopseach)
In [53]: %timeitdriver.CUresult(100) # for comparison254ns ± 1.06nsperloop (mean ± std. dev. of7runs, 1,000,000loopseach)
Alternatively, we can drop the built-in IntEnum entirely, in favor of the counterpart that @oleksandr-pavlyk wrote recently (in NVIDIA/cccl#4325). Though I haven't tested its perf, and this is potentially an API breaking change (the behavior might not be fully duck-typing IntEnum). WDYT?
My take from comparing version 1 and version 2 is that we wasted 100% overhead (60->120ns) just to create a tuple... We may want to think seriously about breaking the API in the next major release.
Activity
leofang commentedon Feb 14, 2025
We'll have to cache CC on a per-
Deviceobject level to bring this down to O(10) ns level.which is also what CuPy does internally:
https://github.com/cupy/cupy/blob/1f9c9d4d1eb2edcbeb2a9294def57c2252e18b92/cupy/cuda/device.pyx#L213-L214
leofang commentedon Feb 21, 2025
I did some refactoring of
Device.__new__()to replace cudart APIs by driver APIs, and found the perf gets even worse. Out of curiosity, I did this quick profiling and got very surprised: (the following already has the primary context set to current)A simple get-device call using
cuda.bindingsis 3-4x slower than CuPy. @vzhurba01 have we seen something like this before?leofang commentedon Feb 24, 2025
Accessing
Device().compute_capabilityis being addressed in #459. Let me re-label this issue to track the remaining binding performance issue.leofang commentedon Feb 27, 2025
@rwgk reported that
cuDriverGetVersionis also sluggish when called repeatedly in a busy looprwgk commentedon Mar 12, 2025
This line accounts for about 82% of the runtime of
cuda.core.experimental.Device():cuda-python/cuda_core/cuda/core/experimental/_device.py
Line 960 in 8cda903
I figured that out by replacing that line with hard-wired
device_id = 0.rwgk commentedon Mar 12, 2025
Small update:
I made a few trivial performance changes that helped quite a bit, then compared the performance with and without this diff, all else the same:
I.e. almost the entire remaining performance difference is due to
runtime.cudaGetDevice().leofang commentedon Mar 13, 2025
Yes, see #439 (comment). Right now the problem is in cuda.bindings, not cuda.core. I had changed the issue label to reflect this status.
vzhurba01 commentedon Apr 2, 2025
Here's my investigation report.
Version 1. This is a minimized version of
cuCtxGetDeviceto judge the speed of light. CUdevice is created by user and we skip returning the error code:with results:
62.1 ns ± 0.00271 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)Version 2. Creates the CUdevice, but skips returning the error code:
with results:
128 ns ± 1.19 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)Version 3. Add return error code:
with results:
390 ns ± 9.64 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)The problem is the creation of class CUresult.... it's really really slow:
Here's one more version of
cuCtxGetDevice:Version 4. Return the error code as an integer.
With results:
153 ns ± 0.036 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)With regards to the next step... I do see that calling returning the enum directly gives better results:
Therefore I propose the following version:
Version 5. Add fast pass for API success:
With results:
150 ns ± 1.35 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)This gives our most common and important case much better performance, while the perf drop in case of an error is less significant.
leofang commentedon Apr 3, 2025
Wow! Great findings Vlad! It is insane how slow
IntEnum(or anyEnum-subclasses from the standard library) is...I wonder if it makes sense to build an internal cache ourselves? The built-in dict lookup is very fast (>10x). Something like
This is reasonably fast from what I see:
Alternatively, we can drop the built-in
IntEnumentirely, in favor of the counterpart that @oleksandr-pavlyk wrote recently (in NVIDIA/cccl#4325). Though I haven't tested its perf, and this is potentially an API breaking change (the behavior might not be fully duck-typingIntEnum). WDYT?leofang commentedon Apr 3, 2025
(Your fast path is also reasonable FWIW, just wonder if this is worth our efforts.)
leofang commentedon Apr 3, 2025
My take from comparing version 1 and version 2 is that we wasted 100% overhead (60->120ns) just to create a tuple... We may want to think seriously about breaking the API in the next major release.
vzhurba01 commentedon Apr 3, 2025
Here's one more version:
Version 5. No tuples, but create a new CUdevice
With results:
115 ns ± 0.71 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)Output arguments also add overhead.
leofang commentedon Apr 3, 2025
I read it wrong. Creating the return tuple is reasonable (~10 ns).