Skip to content

Commit 9f9080f

Browse files
committed
Dynamic installation of PyCUDA with Nox
1 parent ec2f3ca commit 9f9080f

File tree

2 files changed

+44
-23
lines changed

2 files changed

+44
-23
lines changed

CONTRIBUTING.rst

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ Steps with :bash:`sudo` access (e.g. on a local device):
5555
* Install the Python versions with: :bash:`pyenv install 3.8 3.9 3.10 3.11`
5656
#. Setup a local virtual environment in the folder: :bash:`pyenv virtualenv kerneltuner` (or whatever environment name you prefer).
5757
#. Set the Python versions so they can be found: :bash:`pyenv local 3.8 3.9 3.10 3.11` (replace :bash:`local` with :bash:`global` when not using the virtualenv).
58-
#. `Install Poetry <https://python-poetry.org/docs/#installing-with-the-official-installer>`__: :bash:`curl -sSL https://install.python-poetry.org | python3 -`. Make sure to add it to :bash:`PATH` as instructed at the end of the installation.
58+
#. `Install Poetry <https://python-poetry.org/docs/#installing-with-the-official-installer>`__.
59+
* Use :bash:`curl -sSL https://install.python-poetry.org | python3 -` to install Poetry.
60+
* Make sure to add Poetry to :bash:`PATH` as instructed at the end of the installation.
61+
* Add the poetry export plugin with :bash:`poetry self add poetry-plugin-export`.
5962
#. Make sure that non-Python dependencies are installed if applicable, such as CUDA, OpenCL or HIP. This is described in :ref:`Installation <installation>`.
6063
#. Re-open the shell for changes to take effect. Activate the environment with :bash:`pyenv activate kerneltuner`.
6164
#. Install the project, dependencies and extras: :bash:`poetry install --with test,docs -E cuda -E opencl -E hip`, leaving out :bash:`-E cuda`, :bash:`-E opencl` or :bash:`-E hip` if this does not apply on your system. To go all-out, use :bash:`--all-extras`
@@ -87,7 +90,9 @@ Steps without :bash:`sudo` access (e.g. on a cluster):
8790
#. Make sure that non-Python dependencies are loaded if applicable, such as CUDA, OpenCL or HIP. On most clusters it is possible to load (or unload) modules (e.g. CUDA, OpenCL / ROCM). For more information, see :ref:`Installation <installation>`.
8891
* Do not forget to make sure the paths are set correctly. If you're using CUDA, the desired CUDA version should be in :bash:`$PATH`, :bash:`$LD_LIBARY_PATH` and :bash:`$CPATH`.
8992
* [Optional] the loading of modules and setting of paths is likely convenient to put in your :bash:`.bash_profile` or :bash:`.bashrc`.
90-
#. `Install Poetry <https://python-poetry.org/docs/#installing-with-the-official-installer>`__: :bash:`curl -sSL https://install.python-poetry.org | python3 -`.
93+
#. `Install Poetry <https://python-poetry.org/docs/#installing-with-the-official-installer>`__.
94+
* Use :bash:`curl -sSL https://install.python-poetry.org | python3 -` to install Poetry.
95+
* Add the poetry export plugin with :bash:`poetry self add poetry-plugin-export`.
9196
#. Install the project, dependencies and extras: :bash:`poetry install --with test,docs -E cuda -E opencl -E hip`, leaving out :bash:`-E cuda`, :bash:`-E opencl` or :bash:`-E hip` if this does not apply on your system. To go all-out, use :bash:`--all-extras`.
9297
* If you run into "keyring" or other seemingly weird issues, this is a known issue with Poetry on some systems. Do: :bash:`pip install keyring`, :bash:`python3 -m keyring --disable`.
9398
* Depending on the environment, it may be necessary or convenient to install extra packages such as :bash:`cupy-cuda11x` / :bash:`cupy-cuda12x`, and :bash:`cuda-python`. These are currently not defined as dependencies for kernel-tuner, but can be part of tests.

noxfile.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from nox_poetry import Session, session
1515

1616
# set the test parameters
17+
verbose = False
1718
python_versions_to_test = ["3.8", "3.9", "3.10", "3.11"]
1819
nox.options.stop_on_first_error = True
1920
nox.options.error_on_missing_interpreters = True
@@ -23,6 +24,7 @@
2324
settings_file_path = Path("./noxsettings.toml")
2425
venvbackend_values = ('none', 'virtualenv', 'conda', 'mamba', 'venv') # from https://nox.thea.codes/en/stable/usage.html#changing-the-sessions-default-backend
2526

27+
# TODO remove this from a session function, session is only needed to receive trigger argument
2628
@session # to only run on the current python interpreter
2729
def create_settings(session: Session) -> None:
2830
"""One-time creation of noxsettings.toml."""
@@ -58,6 +60,7 @@ def create_settings(session: Session) -> None:
5860
envdir = nox_settings['envdir']
5961
assert venvbackend in venvbackend_values, f"File '{settings_file_path}' has {venvbackend=}, must be one of {','.join(venvbackend_values)}"
6062
nox.options.default_venv_backend = venvbackend
63+
nox.options.venvbackend = venvbackend
6164
if envdir is not None and len(envdir) > 0:
6265
nox.options.envdir = envdir
6366

@@ -97,6 +100,7 @@ def check_development_environment(session: Session) -> None:
97100
# do not forget check / set the versions with `pyenv global`, or `pyenv local` in case of virtual environment
98101
def tests(session: Session) -> None:
99102
"""Run the tests for the specified Python versions."""
103+
session.log(f"Testing on Python {session.python}")
100104
# check if optional dependencies have been disabled by user arguments (e.g. `nox -- skip-gpu`, `nox -- skip-cuda`)
101105
install_cuda = True
102106
install_hip = True
@@ -162,16 +166,38 @@ def tests(session: Session) -> None:
162166
install_warning = """Installation failed, this likely means that the required hardware or drivers are missing.
163167
Run with `-- skip-gpu` or one of the more specific options (e.g. `-- skip-cuda`) to avoid this."""
164168
if install_cuda:
165-
# if we need to install the CUDA extras, first install pycuda seperately.
169+
# use NVCC to get the CUDA version
170+
import re
171+
nvcc_output: str = session.run("nvcc", "--version", silent=True, external=True)
172+
nvcc_output = "".join(nvcc_output.splitlines()) # convert to single string for easier REGEX
173+
cuda_version = re.match(r"^.*release ([0-9]+.[0-9]+).*$", nvcc_output, flags=re.IGNORECASE).group(1).strip()
174+
session.warn(f"Detected CUDA version: {cuda_version}")
175+
# if we need to install the CUDA extras, first install pycuda seperately, reason:
166176
# since version 2022.2 it has `oldest-supported-numpy` as a build dependency which doesn't work with Poetry
167-
try:
168-
session.install("pycuda") # Attention: if changed, check `pycuda` in pyproject.toml as well
169-
except Exception as error:
170-
session.log(error)
171-
session.warn(install_warning)
177+
if " not found: " in session.run("pip", "show", "pycuda", external=True, silent=True, success_codes=[0,1]):
178+
# if PyCUDA is not installed, install it
179+
session.warn("PyCUDA not installed")
180+
try:
181+
session.install("pycuda", "--no-cache-dir", "--force-reinstall") # Attention: if changed, check `pycuda` in pyproject.toml as well
182+
except Exception as error:
183+
session.log(error)
184+
session.warn(install_warning)
185+
else:
186+
session.warn("PyCUDA installed")
187+
# if PyCUDA is already installed, check whether the CUDA version PyCUDA was installed with matches the current CUDA version
188+
session.install("numpy") # required by pycuda.driver
189+
pycuda_version = session.run("python", "-c", "import pycuda.driver as drv; drv.init(); print('.'.join(list(str(d) for d in drv.get_version())))", silent=True)
190+
shortest_string, longest_string = (pycuda_version, cuda_version) if len(pycuda_version) < len(cuda_version) else (cuda_version, pycuda_version)
191+
if longest_string[:len(shortest_string)] != shortest_string:
192+
session.warn(f"PyCUDA was compiled with a version of CUDA ({pycuda_version}) that does not match the current version ({cuda_version}). Re-installing.")
193+
try:
194+
session.install("pycuda", "--no-cache-dir", "--force-reinstall") # Attention: if changed, check `pycuda` in pyproject.toml as well
195+
except Exception as error:
196+
session.log(error)
197+
session.warn(install_warning)
172198
if install_opencl and session.python == "3.8":
173-
# if we need to install the OpenCL extras, first install pyopencl seperately.
174-
# it has `oldest-supported-numpy` as a build dependency which doesn't work with Poetry, but only for Python<3.9
199+
# if we need to install the OpenCL extras, first install pyopencl seperately, reason:
200+
# it has `oldest-supported-numpy` as a build dependency which doesn't work with Poetry, but only for Python<3.9
175201
try:
176202
session.install("pyopencl") # Attention: if changed, check `pyopencl` in pyproject.toml as well
177203
except Exception as error:
@@ -180,7 +206,7 @@ def tests(session: Session) -> None:
180206

181207
# finally, install the dependencies, optional dependencies and the package itself
182208
try:
183-
session.run_always("poetry", "install", "--with", "test", *extras_args, external=True)
209+
session.run_always("poetry", "install", "--with", "test", *extras_args, external=True, silent=False)
184210
except Exception as error:
185211
session.warn(install_warning)
186212
raise error
@@ -190,18 +216,11 @@ def tests(session: Session) -> None:
190216
install_additional_warning = """
191217
Installation failed, this likely means that the required hardware or drivers are missing.
192218
Run without `-- additional-tests` to avoid this."""
193-
import re
194219
try:
195220
session.install("cuda-python")
196221
except Exception as error:
197222
session.log(error)
198223
session.warn(install_additional_warning)
199-
try:
200-
# use NVCC to get the CUDA version
201-
nvcc_output: str = session.run("nvcc", "--version", silent=True)
202-
nvcc_output = "".join(nvcc_output.splitlines()) # convert to single string for easier REGEX
203-
cuda_version = re.match(r"^.*release ([0-9]+.[0-9]+).*$", nvcc_output, flags=re.IGNORECASE).group(1).strip()
204-
session.warn(f"Detected CUDA version: {cuda_version}")
205224
try:
206225
try:
207226
# based on the CUDA version, try installing the exact prebuilt cupy version
@@ -216,18 +235,15 @@ def tests(session: Session) -> None:
216235
# if no compatible prebuilt wheel is found, try building CuPy ourselves
217236
session.warn(f"No prebuilt CuPy found for CUDA {cuda_version}, building from source...")
218237
session.install("cupy")
219-
except Exception as error:
220-
session.log(error)
221-
session.warn(install_additional_warning)
222238

223239
# for the last Python version session if all optional dependencies are enabled:
224240
if session.python == python_versions_to_test[-1] and full_install:
225241
# run pytest on the package to generate the correct coverage report
226-
session.run("pytest")
242+
session.run("pytest", external=False)
227243
else:
228244
# for the other Python version sessions:
229245
# run pytest without coverage reporting
230-
session.run("pytest", "--no-cov")
246+
session.run("pytest", "--no-cov", external=False)
231247

232248
# warn if no coverage report
233249
if not full_install:

0 commit comments

Comments
 (0)