Repository: numba/numba

Issue:
NameError from CubinLinker with CUDA_ENABLE_MINOR_VERSION_COMPATIBILITY
## Reporting a bug

In Numba 0.57.0, the config option `CUDA_ENABLE_MINOR_VERSION_COMPATIBILITY` must be set _before_ importing `numba.cuda`. However, it's not always possible to know if minor version compatibility is needed at import time, and changing the value of `CUDA_ENABLE_MINOR_VERSION_COMPATIBILITY` can result in a `NameError`.

This minimal repro:
```python
from numba import config, cuda

# Change the MVC config after importing numba.cuda
config.CUDA_ENABLE_MINOR_VERSION_COMPATIBILITY = 1

@cuda.jit
def f():
    pass

f[1, 1, 0]()
```
gives
```
NameError: name 'CubinLinker' is not defined
```

I have a fix for this issue in #8978.


Discussion:

Resolving patch:
diff --git a/numba/cuda/cudadrv/driver.py b/numba/cuda/cudadrv/driver.py
--- a/numba/cuda/cudadrv/driver.py
+++ b/numba/cuda/cudadrv/driver.py
@@ -37,15 +37,6 @@
 from .drvapi import cu_occupancy_b2d_size, cu_stream_callback_pyobj, cu_uuid
 from numba.cuda.cudadrv import enums, drvapi, _extras
 
-if config.CUDA_ENABLE_MINOR_VERSION_COMPATIBILITY:
-    try:
-        from ptxcompiler import compile_ptx
-        from cubinlinker import CubinLinker, CubinLinkerError
-    except ImportError as ie:
-        msg = ("Minor version compatibility requires ptxcompiler and "
-               "cubinlinker packages to be available")
-        raise ImportError(msg) from ie
-
 USE_NV_BINDING = config.CUDA_USE_NVIDIA_BINDING
 
 if USE_NV_BINDING:
@@ -2670,12 +2661,23 @@ def complete(self):
         """
 
 
+_MVC_ERROR_MESSAGE = (
+    "Minor version compatibility requires ptxcompiler and cubinlinker packages "
+    "to be available"
+)
+
+
 class MVCLinker(Linker):
     """
     Linker supporting Minor Version Compatibility, backed by the cubinlinker
     package.
     """
     def __init__(self, max_registers=None, lineinfo=False, cc=None):
+        try:
+            from cubinlinker import CubinLinker
+        except ImportError as err:
+            raise ImportError(_MVC_ERROR_MESSAGE) from err
+
         if cc is None:
             raise RuntimeError("MVCLinker requires Compute Capability to be "
                                "specified, but cc is None")
@@ -2700,6 +2702,11 @@ def error_log(self):
         return self._linker.error_log
 
     def add_ptx(self, ptx, name='<cudapy-ptx>'):
+        try:
+            from ptxcompiler import compile_ptx
+            from cubinlinker import CubinLinkerError
+        except ImportError as err:
+            raise ImportError(_MVC_ERROR_MESSAGE) from err
         compile_result = compile_ptx(ptx.decode(), self.ptx_compile_options)
         try:
             self._linker.add_cubin(compile_result.compiled_program, name)
@@ -2707,6 +2714,11 @@ def add_ptx(self, ptx, name='<cudapy-ptx>'):
             raise LinkerError from e
 
     def add_file(self, path, kind):
+        try:
+            from cubinlinker import CubinLinkerError
+        except ImportError as err:
+            raise ImportError(_MVC_ERROR_MESSAGE) from err
+
         try:
             with open(path, 'rb') as f:
                 data = f.read()
@@ -2743,6 +2755,11 @@ def add_cu(self, cu, name):
         self.add_ptx(program.ptx.rstrip(b'\x00'), ptx_name)
 
     def complete(self):
+        try:
+            from cubinlinker import CubinLinkerError
+        except ImportError as err:
+            raise ImportError(_MVC_ERROR_MESSAGE) from err
+
         try:
             return self._linker.complete()
         except CubinLinkerError as e:
diff --git a/numba/misc/numba_sysinfo.py b/numba/misc/numba_sysinfo.py
--- a/numba/misc/numba_sysinfo.py
+++ b/numba/misc/numba_sysinfo.py
@@ -392,8 +392,8 @@ def get_sysinfo():
                 sys_info[_cu_mvc_available] = False
 
             sys_info[_cu_mvc_needed] = cu_rt_ver > cu_drv_ver
-            mvc_used = hasattr(cudadrv.driver, 'CubinLinker')
-            sys_info[_cu_mvc_in_use] = mvc_used
+            sys_info[_cu_mvc_in_use] = bool(
+                config.CUDA_ENABLE_MINOR_VERSION_COMPATIBILITY)
         except Exception as e:
             _warning_log.append(
                 "Warning (cuda): Probing CUDA failed "


Test patch:
diff --git a/numba/cuda/tests/cudadrv/test_mvc.py b/numba/cuda/tests/cudadrv/test_mvc.py
new file mode 100644
--- /dev/null
+++ b/numba/cuda/tests/cudadrv/test_mvc.py
@@ -0,0 +1,52 @@
+import multiprocessing as mp
+import traceback
+from numba.cuda.testing import unittest, CUDATestCase
+from numba.cuda.testing import skip_on_cudasim, skip_under_cuda_memcheck
+from numba.tests.support import linux_only
+
+
+def child_test():
+    from numba import config, cuda
+
+    # Change the MVC config after importing numba.cuda
+    config.CUDA_ENABLE_MINOR_VERSION_COMPATIBILITY = 1
+
+    @cuda.jit
+    def f():
+        pass
+
+    f[1, 1]()
+
+
+def child_test_wrapper(result_queue):
+    try:
+        output = child_test()
+        success = True
+    # Catch anything raised so it can be propagated
+    except: # noqa: E722
+        output = traceback.format_exc()
+        success = False
+
+    result_queue.put((success, output))
+
+
+@linux_only
+@skip_under_cuda_memcheck('May hang CUDA memcheck')
+@skip_on_cudasim('Simulator does not require or implement MVC')
+class TestMinorVersionCompatibility(CUDATestCase):
+    def test_mvc(self):
+        # Run test with Minor Version Compatibility enabled in a child process
+        ctx = mp.get_context('spawn')
+        result_queue = ctx.Queue()
+        proc = ctx.Process(target=child_test_wrapper, args=(result_queue,))
+        proc.start()
+        proc.join()
+        success, output = result_queue.get()
+
+        # Ensure the child process ran to completion before checking its output
+        if not success:
+            self.fail(output)
+
+
+if __name__ == '__main__':
+    unittest.main()