-
Notifications
You must be signed in to change notification settings - Fork 272
[NVVM] Expose nvvm version detection in cuda.bindings.utils. #1837
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
65fb587
10943c8
a7ef552
4f3b442
686c62a
3c5043c
b1747af
d19960e
4a7c3e9
c13c860
e2afa85
8239ee6
5c5024c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE | ||
|
|
||
| from typing import Sequence | ||
|
|
||
| _PRECHECK_NVVM_IR = """target triple = "nvptx64-unknown-cuda" | ||
| target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-i128:128:128-f32:32:32-f64:64:64-v16:16:16-v32:32:32-v64:64:64-v128:128:128-n16:32:64" | ||
|
|
||
| define void @dummy_kernel() {{ | ||
| entry: | ||
| ret void | ||
| }} | ||
|
|
||
| !nvvm.annotations = !{{!0}} | ||
| !0 = !{{void ()* @dummy_kernel, !"kernel", i32 1}} | ||
|
|
||
| !nvvmir.version = !{{!1}} | ||
| !1 = !{{i32 {major}, i32 {minor}, i32 {debug_major}, i32 {debug_minor}}} | ||
| """ | ||
|
|
||
|
|
||
| def check_nvvm_compiler_options(options: Sequence[str]) -> bool: | ||
| """ | ||
| Abstracted from https://github.com/NVIDIA/numba-cuda/pull/681 | ||
|
|
||
| Check if the specified options are supported by the current libNVVM version. | ||
|
|
||
| The options are a list of strings, each representing a compiler option. | ||
|
|
||
| If the test program fails to compile, the options are not supported and False | ||
| is returned. | ||
|
|
||
| If the test program compiles successfully, True is returned. | ||
|
|
||
| cuda.bindings.nvvm returns exceptions instead of return codes. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| options : Sequence[bytes] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is still |
||
| List of compiler options as strings (e.g., ["-arch=compute_90", "-g"]). | ||
|
|
||
| Returns | ||
| ------- | ||
| bool | ||
| True if the options are supported, False otherwise. | ||
|
|
||
| Examples | ||
| -------- | ||
| >>> from cuda.bindings.utils import check_nvvm_options | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I overlooked before: this should be |
||
| >>> check_nvvm_options(["-arch=compute_90", "-g"]) | ||
| True | ||
| """ | ||
| try: | ||
| from cuda.bindings import nvvm | ||
| except ModuleNotFoundError as exc: | ||
| if exc.name == "nvvm": | ||
| return False | ||
| raise | ||
|
|
||
| from cuda.bindings._internal.nvvm import _inspect_function_pointer | ||
|
|
||
| if _inspect_function_pointer("__nvvmCreateProgram") == 0: | ||
| return False | ||
|
|
||
| program = nvvm.create_program() | ||
| try: | ||
| major, minor, debug_major, debug_minor = nvvm.ir_version() | ||
| precheck_ir = _PRECHECK_NVVM_IR.format( | ||
| major=major, | ||
| minor=minor, | ||
| debug_major=debug_major, | ||
| debug_minor=debug_minor, | ||
| ) | ||
| precheck_ir_bytes = precheck_ir.encode("utf-8") | ||
| nvvm.add_module_to_program( | ||
| program, | ||
| precheck_ir_bytes, | ||
| len(precheck_ir_bytes), | ||
| "precheck.ll", | ||
| ) | ||
| try: | ||
| nvvm.compile_program(program, len(options), options) | ||
| except nvvm.nvvmError as e: | ||
| if e.status == nvvm.Result.ERROR_INVALID_OPTION: | ||
| return False | ||
| raise | ||
| finally: | ||
| nvvm.destroy_program(program) | ||
| return True | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,10 +11,28 @@ | |
|
|
||
| from cuda.bindings import driver, runtime | ||
| from cuda.bindings._internal.utils import get_c_compiler | ||
| from cuda.bindings.utils import get_cuda_native_handle, get_minimal_required_cuda_ver_from_ptx_ver, get_ptx_ver | ||
| from cuda.bindings.utils import ( | ||
| check_nvvm_compiler_options, | ||
| get_cuda_native_handle, | ||
| get_minimal_required_cuda_ver_from_ptx_ver, | ||
| get_ptx_ver, | ||
| ) | ||
|
|
||
| have_cufile = importlib.util.find_spec("cuda.bindings.cufile") is not None | ||
|
|
||
|
|
||
| def _is_libnvvm_available() -> bool: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Since The only expected exception is Suggested change: def _is_libnvvm_available() -> bool:
from cuda.bindings._internal.nvvm import _inspect_function_pointer
from cuda.pathfinder import DynamicLibNotFoundError
try:
return _inspect_function_pointer("__nvvmCreateProgram") != 0
except DynamicLibNotFoundError:
return False |
||
| try: | ||
| from cuda.bindings._internal.nvvm import _inspect_function_pointer | ||
|
|
||
| return _inspect_function_pointer("__nvvmCreateProgram") != 0 | ||
| except Exception: | ||
| return False | ||
|
|
||
|
|
||
| _libnvvm_available = _is_libnvvm_available() | ||
| _skip_no_libnvvm = pytest.mark.skipif(not _libnvvm_available, reason="libNVVM not available") | ||
|
|
||
| ptx_88_kernel = r""" | ||
| .version 8.8 | ||
| .target sm_75 | ||
|
|
@@ -118,3 +136,35 @@ def test_get_c_compiler(): | |
| c_compiler = get_c_compiler() | ||
| prefix = ("GCC", "Clang", "MSVC", "Unknown") | ||
| assert sum(c_compiler.startswith(p) for p in prefix) == 1 | ||
|
|
||
|
|
||
| @_skip_no_libnvvm | ||
| def test_check_nvvm_compiler_options_valid(): | ||
| assert check_nvvm_compiler_options(["-arch=compute_90"]) is True | ||
|
|
||
|
|
||
| @_skip_no_libnvvm | ||
| def test_check_nvvm_compiler_options_invalid(): | ||
| assert check_nvvm_compiler_options(["--this-is-not-a-valid-option"]) is False | ||
|
|
||
|
|
||
| @_skip_no_libnvvm | ||
| def test_check_nvvm_compiler_options_empty(): | ||
| assert check_nvvm_compiler_options([]) is True | ||
|
|
||
|
|
||
| @_skip_no_libnvvm | ||
| def test_check_nvvm_compiler_options_multiple_valid(): | ||
| assert check_nvvm_compiler_options(["-arch=compute_90", "-opt=3", "-g"]) is True | ||
|
|
||
|
|
||
| @_skip_no_libnvvm | ||
| def test_check_nvvm_compiler_options_arch_detection(): | ||
| assert check_nvvm_compiler_options(["-arch=compute_90"]) is True | ||
| assert check_nvvm_compiler_options(["-arch=compute_99999"]) is False | ||
|
|
||
|
|
||
| def test_check_nvvm_compiler_options_no_libnvvm(): | ||
| if _libnvvm_available: | ||
| pytest.skip("libNVVM is available; this test targets the fallback path") | ||
| assert check_nvvm_compiler_options(["-arch=compute_90"]) is False | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,81 +70,26 @@ def _has_nvrtc_pch_apis_for_tests(): | |
| ) | ||
|
|
||
|
|
||
| _libnvvm_version = None | ||
| _libnvvm_version_attempted = False | ||
|
|
||
| precheck_nvvm_ir = """target triple = "nvptx64-unknown-cuda" | ||
| target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-i128:128:128-f32:32:32-f64:64:64-v16:16:16-v32:32:32-v64:64:64-v128:128:128-n16:32:64" | ||
|
|
||
| define void @dummy_kernel() {{ | ||
| entry: | ||
| ret void | ||
| }} | ||
|
|
||
| !nvvm.annotations = !{{!0}} | ||
| !0 = !{{void ()* @dummy_kernel, !"kernel", i32 1}} | ||
|
|
||
| !nvvmir.version = !{{!1}} | ||
| !1 = !{{i32 {major}, i32 {minor}, i32 {debug_major}, i32 {debug_minor}}} | ||
| """ | ||
|
|
||
|
|
||
| def _get_libnvvm_version_for_tests(): | ||
| """ | ||
| Detect libNVVM version by compiling dummy IR and analyzing the PTX output. | ||
| def _has_check_nvvm_compiler_options(): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be made slightly more explicit and tighter by separating the module import from the attribute check: def _has_check_nvvm_compiler_options():
try:
import cuda.bindings.utils as utils
except ModuleNotFoundError:
return False
return hasattr(utils, "check_nvvm_compiler_options")This makes the intent clearer: first check if the module exists, then check if the function is available. The |
||
| try: | ||
| from cuda.bindings.utils import check_nvvm_compiler_options # noqa: F401 | ||
|
|
||
| Workaround for the lack of direct libNVVM version API (nvbugs 5312315). | ||
| The approach: | ||
| - Compile a small dummy NVVM IR to PTX | ||
| - Use PTX version analysis APIs if available to infer libNVVM version | ||
| - Cache the result for future use | ||
| """ | ||
| global _libnvvm_version, _libnvvm_version_attempted | ||
| return True | ||
| except ImportError: | ||
| return False | ||
|
|
||
| if _libnvvm_version_attempted: | ||
| return _libnvvm_version | ||
|
|
||
| _libnvvm_version_attempted = True | ||
| has_nvvm_option_checker = pytest.mark.skipif( | ||
| not _has_check_nvvm_compiler_options(), | ||
| reason="cuda.bindings.utils.check_nvvm_compiler_options not available (cuda-bindings too old?)", | ||
| ) | ||
|
|
||
| try: | ||
| from cuda.core._program import _get_nvvm_module | ||
|
|
||
| nvvm = _get_nvvm_module() | ||
|
|
||
| try: | ||
| from cuda.bindings.utils import get_minimal_required_cuda_ver_from_ptx_ver, get_ptx_ver | ||
| except ImportError: | ||
| _libnvvm_version = None | ||
| return _libnvvm_version | ||
|
|
||
| program = nvvm.create_program() | ||
| try: | ||
| major, minor, debug_major, debug_minor = nvvm.ir_version() | ||
| global precheck_nvvm_ir | ||
| precheck_nvvm_ir = precheck_nvvm_ir.format( | ||
| major=major, minor=minor, debug_major=debug_major, debug_minor=debug_minor | ||
| ) | ||
| precheck_ir_bytes = precheck_nvvm_ir.encode("utf-8") | ||
| nvvm.add_module_to_program(program, precheck_ir_bytes, len(precheck_ir_bytes), "precheck.ll") | ||
|
|
||
| options = ["-arch=compute_90"] | ||
| nvvm.verify_program(program, len(options), options) | ||
| nvvm.compile_program(program, len(options), options) | ||
|
|
||
| ptx_size = nvvm.get_compiled_result_size(program) | ||
| ptx_data = bytearray(ptx_size) | ||
| nvvm.get_compiled_result(program, ptx_data) | ||
| ptx_str = ptx_data.decode("utf-8") | ||
| ptx_version = get_ptx_ver(ptx_str) | ||
| cuda_version = get_minimal_required_cuda_ver_from_ptx_ver(ptx_version) | ||
| _libnvvm_version = cuda_version | ||
| return _libnvvm_version | ||
| finally: | ||
| nvvm.destroy_program(program) | ||
| def _check_nvvm_arch(arch: str) -> bool: | ||
| """Check if the given NVVM arch is supported by the installed libNVVM.""" | ||
| from cuda.bindings.utils import check_nvvm_compiler_options | ||
|
|
||
| except Exception: | ||
| _libnvvm_version = None | ||
| return _libnvvm_version | ||
| return check_nvvm_compiler_options([f"-arch={arch}"]) | ||
|
|
||
|
|
||
| @pytest.fixture(scope="session") | ||
|
|
@@ -524,10 +469,13 @@ def test_nvvm_compile_invalid_ir(): | |
| ), | ||
| pytest.param( | ||
| ProgramOptions(name="test_sm110_1", arch="sm_110", device_code_optimize=False), | ||
| marks=pytest.mark.skipif( | ||
| (_get_libnvvm_version_for_tests() or 0) < 13000, | ||
| reason="Compute capability 110 requires libNVVM >= 13.0", | ||
| ), | ||
| marks=[ | ||
| has_nvvm_option_checker, | ||
| pytest.mark.skipif( | ||
| _has_check_nvvm_compiler_options() and not _check_nvvm_arch("compute_110"), | ||
| reason="Compute capability 110 not supported by installed libNVVM", | ||
| ), | ||
| ], | ||
| ), | ||
| pytest.param( | ||
| ProgramOptions( | ||
|
|
@@ -539,17 +487,23 @@ def test_nvvm_compile_invalid_ir(): | |
| fma=True, | ||
| device_code_optimize=True, | ||
| ), | ||
| marks=pytest.mark.skipif( | ||
| (_get_libnvvm_version_for_tests() or 0) < 13000, | ||
| reason="Compute capability 110 requires libNVVM >= 13.0", | ||
| ), | ||
| marks=[ | ||
| has_nvvm_option_checker, | ||
| pytest.mark.skipif( | ||
| _has_check_nvvm_compiler_options() and not _check_nvvm_arch("compute_110"), | ||
| reason="Compute capability 110 not supported by installed libNVVM", | ||
| ), | ||
| ], | ||
| ), | ||
| pytest.param( | ||
| ProgramOptions(name="test_sm110_3", arch="sm_110", link_time_optimization=True), | ||
| marks=pytest.mark.skipif( | ||
| (_get_libnvvm_version_for_tests() or 0) < 13000, | ||
| reason="Compute capability 110 requires libNVVM >= 13.0", | ||
| ), | ||
| marks=[ | ||
| has_nvvm_option_checker, | ||
| pytest.mark.skipif( | ||
| _has_check_nvvm_compiler_options() and not _check_nvvm_arch("compute_110"), | ||
| reason="Compute capability 110 not supported by installed libNVVM", | ||
| ), | ||
| ], | ||
| ), | ||
| ], | ||
| ) | ||
|
|
@@ -729,12 +683,8 @@ def test_program_options_as_bytes_nvrtc(): | |
| """Test ProgramOptions.as_bytes() for NVRTC backend""" | ||
| options = ProgramOptions(arch="sm_80", debug=True, lineinfo=True, ftz=True) | ||
| nvrtc_options = options.as_bytes("nvrtc") | ||
|
|
||
| # Should return list of bytes | ||
| assert isinstance(nvrtc_options, list) | ||
| assert all(isinstance(opt, bytes) for opt in nvrtc_options) | ||
|
|
||
| # Decode to check content | ||
| options_str = [opt.decode() for opt in nvrtc_options] | ||
| assert "-arch=sm_80" in options_str | ||
| assert "--device-debug" in options_str | ||
|
|
@@ -747,12 +697,8 @@ def test_program_options_as_bytes_nvvm(): | |
| """Test ProgramOptions.as_bytes() for NVVM backend""" | ||
| options = ProgramOptions(arch="sm_80", debug=True, ftz=True, device_code_optimize=True) | ||
| nvvm_options = options.as_bytes("nvvm") | ||
|
|
||
| # Should return list of bytes (same as other backends) | ||
| assert isinstance(nvvm_options, list) | ||
| assert all(isinstance(opt, bytes) for opt in nvvm_options) | ||
|
|
||
| # Decode to check content | ||
| options_str = [opt.decode() for opt in nvvm_options] | ||
| assert "-arch=compute_80" in options_str | ||
| assert "-g" in options_str | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.