8388283: Add opt-in NVRTC compilation for CUDA backend - #1105
Conversation
HAT generates CUDA source at runtime and immediately compiles it before
launching the generated kernel. This flow is a good match for NVRTC
because it avoids spawning an external compiler process for each
generated CUDA source and can significantly reduce runtime compilation
overhead.
Add an opt-in NVRTC compilation path for the HAT CUDA backend while
keeping NVCC as the default compiler. Users can select the NVRTC path
with:
HAT_CUDA_COMPILER=nvrtc java @.ffi-cuda-example matmul.Main --kernel=1D
NVRTC is loaded lazily with dlopen/dlsym, so the CUDA backend does not
gain a link-time dependency on libnvrtc and existing NVCC/PTX flows can
still run without loading NVRTC. The generated CUDA/PTX artifacts are
kept under var/cuda for parity with the NVCC path.
Performance gains measured with matmul.Main 2DTILING on Grace Hopper:
CUDA source compilation:
NVRTC: 50.9 ms
NVCC: 213.1 ms
Speedup: ~4x
First execution including compilation:
NVRTC: 348.68 ms
NVCC: 544.35 ms
Change-Id: I33648442709b84070fcc8e9df58eb2b1ab050363
|
👋 Welcome back xyyNicole! A progress list of the required criteria for merging this PR into |
|
@xyyNicole This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be: You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been no new commits pushed to the As you do not have Committer status in this project an existing Committer must agree to sponsor your change. ➡️ To flag this PR as ready for integration with the above commit message, type |
|
This is interesting. I was not aware of this option. Rather than a separate HAT_CUDA_COMPILER I would suggest we allocate a 'bit' for this in hat.Config. We reserve a 32 int (bits) for comms between java side and FFI native side. I suspect we can free up a bit for this. |
|
I should have added an example, so we could hopefully use See https://github.com/openjdk/babylon/blob/code-reflection/hat/core/src/main/java/hat/Config.java |
Thanks for the review and the concrete example. From the perspective of HAT's existing configuration model, I agree that One concern is that the current 32-bit config word appears to have no free bits left. I am not fully familiar with the intended use of every existing field, so I would appreciate your advice on whether any bit could be freed or reused. For Another option would be to extend the config word from |
|
Ah Juan and I just chatted about this is slack. We will find a way to free up some bits ;) I think we have some dead/unused bits. Also we recognize that some bits are FFI only (so passed to native backend), we should have separated these flags/bits into Java only and FFI only. So Config itself may use two banks of 32 bits. Since this nvrtc bit is likely FFI only we should have plenty once we separate them out. In the short term ;) We will let this PR use it's own env var, then later will repack the bits and then refactor this code to use a freed up slot. To avoid merge issues. |
Thanks, that sounds good. I will keep the separate environment variable in this patch to avoid conflicts, and the NVRTC compiler selection can be migrated to an FFI config bit after the Config bits are repacked. |
|
One issue worth noting is that native object ownership in the CUDA backend appears to cause memory leaks. For example, Fixing this properly requires a broader refactoring to define the lifetimes of |
|
Thanks @xyyNicole for this PR. I could check a benefit of 60-70% faster for compilation. When running the unit-tests as follows: HAT_CUDA_COMPILER=nvrtc java @.test-suite ffi-cuda Many unit-tests failed to run: Global passed: 34, failed: 0, unsupported: 0, precision-errors: 1, pass-rate: 97.14
[REPORT] Test failed. Some tests were not launched. Common reasons: seg-faults, driver-issues. Please, check again.
- Expected to run: 138. But only launched 35For example: HAT Engine Testing Framework. Backend: hat.backend.ffi.CudaBackend
failed to run test TestBFloat16Type
HAT Engine Testing Framework. Backend: hat.backend.ffi.CudaBackend
failed to run test TestF16Type
HAT Engine Testing Framework. Backend: hat.backend.ffi.CudaBackend
failed to run test TestFlashAttention
HAT Engine Testing Framework. Backend: hat.backend.ffi.CudaBackend
failed to run test TestFloat2
HAT Engine Testing Framework. Backend: hat.backend.ffi.CudaBackend
failed to run test TestHATMathLib
HAT Engine Testing Framework. Backend: hat.backend.ffi.CudaBackend
failed to run test TestTensors
HAT Engine Testing Framework. Backend: hat.backend.ffi.CudaBackend
failed to run test TestVectorArrayView
HAT Engine Testing Framework. Backend: hat.backend.ffi.CudaBackend
failed to run test TestVectorTypesI tracked down one of them ( $ export HAT_CUDA_COMPILER=nvrtc
$ HAT=CHECK_SSA_LOWERING,INFO,SHOW_CODE java @.ffi-cuda-test hat.test.TestTensors#testTensor03
...
> NVRTC log:
./var/cuda/tmp_1784724386117.cu(71): error: unrecognized token
unsigned char pad$lNwuQ[12];
^
./var/cuda/tmp_1784724386117.cu(71): error: expected a ";"
unsigned char pad$lNwuQ[12];
^
2 errors detected in the compilation of "./var/cuda/tmp_1784724386117.cu".We insert this symbol when we insert padding automatically, or construct new symbols at runtime (during codegen). Is there any way to bypass this in Using |
|
BTW, I am using CUDA 13.3 and NVIDIA |
|
Looks like the cuda compiler no longer accepts '$' as a valid identifier char. I was always suprised we got away with this in CUDA and OpenCL. |
|
WRT [INFO] CUDA source compiler: NVRTC
NVRTC compilation failed: NVRTC_ERROR_COMPILATION. CUDA source saved to ./var/cuda/tmp_1784724931385.cu
> NVRTC log:
./var/cuda/tmp_1784724931385.cu(68): error: expected an expression
F16_t result = (F16_t){(ha->value + hb->value)};
^
1 error detected in the compilation of "./var/cuda/tmp_1784724931385.cu".nvrtc might be more restricted compared to nvcc. Happy to work on a work-around that we can improve HAT. |
|
Thanks, @jjfumero, for investigating and providing these details. Sorry that I did not run the full CUDA test suite before submitting the patch; I will do so for future changes. |
|
The failures appear to come from generated CUDA code that relies on extensions accepted by NVCC but rejected by NVRTC. Although both compile CUDA C++, their frontends do not accept exactly the same set of extensions. In particular, the generated I do not see an NVRTC option that would reliably bypass either issue, so fixing the generated CUDA syntax seems to be the appropriate solution. Do you already plan to address these code-generation issues? If not, I can work on them in a follow-up. |
|
BTW, I am fine with either merging this opt-in NVRTC support first and fixing the codegen compatibility separately, or waiting until the affected tests pass, depending on your preference ;) |
|
Thanks @xyyNicole for the feedback. So, chatting with Gary, we think the following:
Question: given these differences between Note: The two discovered issues I found were just by checking a few tests. Probably there are also other minor differences to cover. |
|
Thanks, @jjfumero. I agree with this approach and will address the codegen issues in this PR, while preserving common patterns across CUDA and OpenCL where practical. As you noted, the required changes may go beyond the two issues identified so far. I will run the full CUDA unit-test suite with NVRTC and also review the generated source paths to make the CUDA codegen consistently produce syntax For the same CUDA C++ source, target architecture, and optimization settings, NVCC and NVRTC should generally produce kernels with similar runtime performance because they share many compilation stages, although identical results are not guaranteed. Once the codegen fixes are complete, we can compare both compilation time and kernel runtime performance. One expectation to set: my progress on the codegen work may be incremental, as my bandwidth is currently limited. Thanks for your understanding. |
|
Thank you for the update @xyyNicole.
Sure, no problem. Please, don't hesitate to contact us if you have questions for the codegen. |
HAT generates CUDA source at runtime and immediately compiles it before launching the generated kernel. This flow is a good match for NVRTC because it avoids spawning an external compiler process for each generated CUDA source and can significantly reduce runtime compilation overhead.
Add an opt-in NVRTC compilation path for the HAT CUDA backend while keeping NVCC as the default compiler. Users can select the NVRTC path with:
NVRTC is loaded lazily with
dlopen/dlsym, so the CUDA backend does not gain a link-time dependency on libnvrtc and existing NVCC/PTX flows can still run without loading NVRTC. The generated CUDA/PTX artifacts are kept under./var/cudafor parity with the NVCC path.Performance gains measured with matmul.Main 2DTILING on Grace Hopper:
CUDA source compilation
NVRTC: 50.9 ms
NVCC: 213.1 ms
Speedup: ~4x
First execution including compilation
NVRTC: 348.68 ms
NVCC: 544.35 ms
Progress
Issue
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/babylon.git pull/1105/head:pull/1105$ git checkout pull/1105Update a local copy of the PR:
$ git checkout pull/1105$ git pull https://git.openjdk.org/babylon.git pull/1105/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 1105View PR using the GUI difftool:
$ git pr show -t 1105Using diff file
Download this PR as a diff file:
https://git.openjdk.org/babylon/pull/1105.diff
Using Webrev
Link to Webrev Comment