Repository: numba/numba

Issue:
Retrieve The Oldest and Newest Supported CUDA Versions
## Feature request

The STUMPY Python package depends on `numba` and, when `cuda` is available, it imports submodules that rely on cuda. It checks this by doing something like:

```
from numba import cuda

if cuda.is_available():  # Only import if CUDA is available
    from stumpy import gpu_stump
```

However, `numba` does not currently support CUDA 11.2 and if CUDA 11.2 is installed then the above `if` condition still returns `True`. It would be nice to have function that checks if the available CUDA version is compatible with `numba` like:

```
if cuda.is_available() and cuda.version_is_supported():
    from stumpy import gpu_stump
```

Or, as discussed with @stuartarchibald, maybe it makes more sense to add max/min supported versions enumerated and then make that part of the `cuda.is_available()` check. 


Discussion:
I would also agree with the approach of combining the version check with the driver check in `cuda.is_available()` to keep things simple for downstream libraries.
Thanks for writing this up @seanlaw, original discussion is here: https://gitter.im/numba/numba?at=6001c07114cec811ec9e267a, as noted in that, CUDA `version++` has not in my memory broken Numba support before which is why sadly these support querying features don't exist. I think this needs to go into 0.53.0 to make sure there's a way to do this for the future.
CC @gmarkall @testhound 

Resolving patch:
diff --git a/numba/cuda/cudadrv/runtime.py b/numba/cuda/cudadrv/runtime.py
--- a/numba/cuda/cudadrv/runtime.py
+++ b/numba/cuda/cudadrv/runtime.py
@@ -7,6 +7,7 @@
 
 import ctypes
 import functools
+import sys
 
 from numba.core import config
 from numba.cuda.cudadrv.driver import ERROR_MAP, make_logger
@@ -113,5 +114,23 @@ def get_version(self):
         minor = (rtver.value - (major * 1000)) // 10
         return (major, minor)
 
+    def is_supported_version(self):
+        """
+        Returns True if the CUDA Runtime is a supported version.
+        """
+
+        return self.get_version() in self.supported_versions
+
+    @property
+    def supported_versions(self):
+        """A tuple of all supported CUDA toolkit versions. Versions are given in
+        the form ``(major_version, minor_version)``."""
+        if sys.platform not in ('linux', 'win32') or config.MACHINE_BITS != 64:
+            # Only 64-bit Linux and Windows are supported
+            return ()
+        return ((9, 0), (9, 1), (9, 2),
+                (10, 0), (10, 1), (10, 2),
+                (11, 0), (11, 1), (11, 2))
+
 
 runtime = Runtime()
diff --git a/numba/cuda/device_init.py b/numba/cuda/device_init.py
--- a/numba/cuda/device_init.py
+++ b/numba/cuda/device_init.py
@@ -51,6 +51,23 @@ def is_available():
     return driver_is_available and nvvm.is_available()
 
 
+def is_supported_version():
+    """Returns True if the CUDA Runtime is a supported version.
+
+    Unsupported versions (e.g. newer versions than those known to Numba)
+    may still work; this function provides a facility to check whether the
+    current Numba version is tested and known to work with the current
+    runtime version. If the current version is unsupported, the caller can
+    decide how to act. Options include:
+
+    - Continuing silently,
+    - Emitting a warning,
+    - Generating an error or otherwise preventing the use of CUDA.
+    """
+
+    return runtime.is_supported_version()
+
+
 def cuda_error():
     """Returns None or an exception if the CUDA driver fails to initialize.
     """
diff --git a/numba/cuda/simulator/cudadrv/runtime.py b/numba/cuda/simulator/cudadrv/runtime.py
--- a/numba/cuda/simulator/cudadrv/runtime.py
+++ b/numba/cuda/simulator/cudadrv/runtime.py
@@ -8,5 +8,12 @@ class FakeRuntime(object):
     def get_version(self):
         return (-1, -1)
 
+    def is_supported_version(self):
+        return True
+
+    @property
+    def supported_versions(self):
+        return (-1, -1),
+
 
 runtime = FakeRuntime()


Test patch:
diff --git a/numba/cuda/tests/cudadrv/test_runtime.py b/numba/cuda/tests/cudadrv/test_runtime.py
--- a/numba/cuda/tests/cudadrv/test_runtime.py
+++ b/numba/cuda/tests/cudadrv/test_runtime.py
@@ -2,7 +2,8 @@
 import os
 from numba.core import config
 from numba.cuda.cudadrv.runtime import runtime
-from numba.cuda.testing import unittest, SerialMixin
+from numba.cuda.testing import unittest, SerialMixin, skip_on_cudasim
+from unittest.mock import patch
 
 
 def set_visible_devices_and_check(q):
@@ -17,14 +18,33 @@ def set_visible_devices_and_check(q):
         q.put(-1)
 
 
+if config.ENABLE_CUDASIM:
+    SUPPORTED_VERSIONS = (-1, -1),
+else:
+    SUPPORTED_VERSIONS = ((9, 0), (9, 1), (9, 2),
+                          (10, 0), (10, 1), (10, 2),
+                          (11, 0), (11, 1), (11, 2))
+
+
 class TestRuntime(unittest.TestCase):
     def test_get_version(self):
-        if config.ENABLE_CUDASIM:
-            supported_versions = (-1, -1),
-        else:
-            supported_versions = ((9, 0), (9, 1), (9, 2), (10, 0),
-                                  (10, 1), (10, 2), (11, 0), (11, 1), (11, 2))
-        self.assertIn(runtime.get_version(), supported_versions)
+        self.assertIn(runtime.get_version(), SUPPORTED_VERSIONS)
+
+    def test_is_supported_version_true(self):
+        for v in SUPPORTED_VERSIONS:
+            with patch.object(runtime, 'get_version', return_value=v):
+                self.assertTrue(runtime.is_supported_version())
+
+    @skip_on_cudasim('The simulator always simulates a supported runtime')
+    def test_is_supported_version_false(self):
+        # Check with an old unsupported version and some potential future
+        # versions
+        for v in ((8, 0), (11, 3), (12, 0)):
+            with patch.object(runtime, 'get_version', return_value=v):
+                self.assertFalse(runtime.is_supported_version())
+
+    def test_supported_versions(self):
+        self.assertEqual(SUPPORTED_VERSIONS, runtime.supported_versions)
 
 
 class TestVisibleDevices(unittest.TestCase, SerialMixin):
diff --git a/numba/cuda/tests/cudapy/test_constmem.py b/numba/cuda/tests/cudapy/test_constmem.py
--- a/numba/cuda/tests/cudapy/test_constmem.py
+++ b/numba/cuda/tests/cudapy/test_constmem.py
@@ -138,7 +138,7 @@ def test_const_array_3d(self):
             # and > 11.1 use a vector of 2x f32. The root cause of these
             # codegen differences is not known, but must be accounted for in
             # this test.
-            if cuda.runtime.get_version() in ((8, 0), (9, 0), (9, 1), (11, 2)):
+            if cuda.runtime.get_version() in ((9, 0), (9, 1), (11, 2)):
                 complex_load = 'ld.const.v2.f32'
                 description = 'Load the complex as a vector of 2x f32'
             else: