Thank you for your interest in contributing to CUDA Python! Based on the type of contribution, it will fall into two categories:
- You want to report a bug, feature request, or documentation issue:
- File an issue describing what you encountered or what you want to see changed.
- The NVIDIA team will evaluate the issues and triage them, scheduling them for a release. If you believe the issue needs priority attention comment on the issue to notify the team.
- You want to implement a feature, improvement, or bug fix:
- Before starting work on an existing issue, please comment on the issue to express your interest and wait to be assigned by a maintainer. This helps avoid redundant effort in case the issue is already being worked on by another contributor or an NVIDIA team member.
- Please refer to each component's guideline:
- Contributing to CUDA Python
Windows contributors (not WSL): source builds and tests work with
core.symlinks=false. Git symlink support is optional and only affects the remaining documentation and metadata links (CLAUDE.md,cuda_python/README.md, and.git_archival.txt). See Enabling git symlinks if you want those links materialized.
Every package in this repository derives its version from git tags using
setuptools-scm, so how you clone
determines whether you can build at all, and whether the version you build is
correct. Each package matches its own tag prefix:
| Package | Tag pattern |
|---|---|
cuda-bindings, cuda-python |
v* (e.g. v13.4.2) |
cuda-core |
cuda-core-v* (e.g. cuda-core-v1.1.0) |
cuda-pathfinder |
cuda-pathfinder-v* (e.g. cuda-pathfinder-v1.6.0) |
Each package sets root = ".." in its [tool.setuptools_scm] table, meaning the
version is read from the repository root rather than the package directory. A
working build therefore needs all of the following:
- A real git clone. Source zips and GitHub "Download ZIP" archives have no
git metadata and the build fails outright. Tarballs produced by
git archivestill resolve a version via the.git_archival.txtsubstitutions in.gitattributes, but they do not include submodule contents, so they cannot compilecuda.core. - The full repository, not just the package subdirectory, because the version lookup walks up to the repository root.
- Tags, reaching back at least as far as the most recent tag matching the
package you are building.
git describeneeds to find that tag; the history between it and your checkout must be present too. - The CCCL submodule when building
cuda.core. That package compiles against the headers pinned incuda_core/third_party/cccl.
The default git clone plus the submodules gives you everything you need:
$ git clone --recurse-submodules https://github.com/NVIDIA/cuda-python.gitIf you already have a shallow clone:
$ git fetch --unshallow --tagsIf you cloned without submodules:
$ git submodule update --init cuda_core/third_party/ccclIf you are working from a personal fork, your fork's tags stop tracking upstream the moment new releases are cut, which silently yields a stale version. Fetch tags from upstream directly:
$ git remote add upstream https://github.com/NVIDIA/cuda-python.git
$ git fetch --tags upstreamKeep doing this periodically — a fork that was correct when you created it will drift.
Only case 3 below reports an error. The first two fail silently, producing a wrong version that surfaces much later as a confusing dependency-resolution or version-check failure:
- No tags reachable. The build succeeds and produces a version starting at
0.1.dev: a--depth 1clone yields0.1.dev1+g0d22cb444, a full clone made with--no-tagsyields0.1.dev2114+g0d22cb444. Installingcuda-pythonbuilt this way then fails, because itsinstall_requirespinscuda-bindingsto that same bogus version. - Stale tags (a fork that has not fetched upstream in a while): you get a
plausible-looking but wrong version, e.g.
13.0.4.dev650+g0d22cb44when the real latest tag isv13.4.2. Nothing warns you. Note there is no leadingv—setuptools-scmstrips the tag prefix. - No git metadata (source zip): the build fails with
LookupError: setuptools-scm was unable to detect version.
As a last resort — for example when building inside a container that has no git history — you can bypass the lookup entirely:
$ SETUPTOOLS_SCM_PRETEND_VERSION_FOR_CUDA_CORE=1.1.0 pip install ./cuda_coreThe environment variable is suffixed with the distribution name, uppercased with
hyphens replaced by underscores: ..._FOR_CUDA_BINDINGS, ..._FOR_CUDA_CORE,
..._FOR_CUDA_PATHFINDER, ..._FOR_CUDA_PYTHON. Use this only when you
genuinely cannot provide tags; it is not a substitute for a correct clone.
This section collects Windows-specific guidance for contributors working outside of WSL. WSL contributors can follow the Linux flow in the rest of this document.
The repository still contains documentation and metadata symlinks, including
CLAUDE.md, cuda_python/README.md, and the per-package .git_archival.txt
files. With core.symlinks=false, Git checks them out as plain text files
containing the target path. This does not affect source builds or tests.
If you want these links materialized as symlinks:
-
Activate Developer Mode so Git can create symlinks without Administrator privileges.
-
Enable Git symlink support globally so newly-cloned repositories inherit the setting:
$ git config --global core.symlinks true
Then clone as usual (see Cloning the repository).
If you already cloned with core.symlinks=false, the repository-local
setting overrides the global setting. Enable it locally, then rematerialize
all tracked symlinks. For example:
$ git config core.symlinks true
$ git rm --cached cuda_python/README.md
$ git checkout HEAD -- cuda_python/README.mdRepeat the last two commands for every other tracked symlink. Re-cloning after enabling Developer Mode and setting the global option is simpler if you want all links restored.
For development on Windows (not WSL), the lychee pre-commit task will not
work when running pre-commit run --all-files. This problem does not occur
if you install the pre-commit hook and run it automatically as part of your
git commit workflow. To resolve this, you can either:
-
Run
pre-commitin Git Bash, rather than directly in PowerShell or cmd -
Skip it by setting the environment variable
SKIPtolychee. This would be$env:SKIP = "lychee"in PowerShell orset SKIP=lycheein cmd.
cuda.core is a PEP 561-compliant package: it ships a py.typed marker and
.pyi stub files alongside the Cython extensions. The stubs
are checked into the repository.
You do not need to run stubgen-pyx manually. A pre-commit hook
regenerates the corresponding .pyi files automatically when you commit.
The results are then also tested with mypy.
A few things to keep in mind:
- Do not edit
.pyifiles by hand. They are regenerated from the Cython sources on every commit that touches those sources; manual edits will be overwritten. - Type annotations belong in the
.pyx/.pxdsource. stubgen-pyx reads Cython type annotations and docstrings to build the stubs, so keeping the source well-annotated is the right way to improve stub quality. - To run mypy manually (outside of pre-commit): `python -m mypy --config-file cuda_core/pyproject.toml
This project uses pre-commit.ci with GitHub Actions. All pull requests are automatically checked for pre-commit compliance, and any pre-commit failures will block merging until resolved.
To set yourself up for running pre-commit checks locally and to catch issues before pushing your changes, follow these steps:
- Install pre-commit with:
pip install pre-commit - Run this once per checkout:
pre-commit install - You can manually check all files at any time by running:
pre-commit run --all-files
This command runs all configured hooks (such as linters and formatters) across your repository, letting you review and address issues before committing.
Installing the hook is required, not optional. Some of the automated checks
(the SPDX header updater and the .pyi stub generator for cuda_core) only
keep the tree consistent if they run on every commit. Relying on manual
pre-commit run --all-files invocations means these checks can be skipped
between commits, leaving stale headers or out-of-date stubs in the history.
If the hook isn't installed, pre-commit run (and CI) will print a visible
warning reminding you to run pre-commit install.
Windows contributors: see Pre-commit lychee workaround under Development on Windows.
The repository checks in a pixi.lock next to each pixi.toml. Those lockfiles
pin the solved dependency graph used by local pixi workflows and by CI, so they
must stay in sync with their manifests and easy to review.
Contributor expectations:
-
If a PR changes a
pixi.toml, update the correspondingpixi.lockin the same PR. Regenerating one lockfile:$ pixi lock --manifest-path cuda_coreUse
--manifest-path .for the repository-root environment. Regenerate with the pixi version pinned inci/pixi-version.env: different pixi versions write different canonical forms, such as thepixi.lockformat version or the generated platform alias names, and CI requires the committed bytes to match what the pinned version produces. The pin must remain at least 0.71.0, which supplies the content-addressed source-build cache used by CI and writes the repository's version 7 lockfiles. -
If a PR does not intentionally change pixi dependencies or metadata, do not include unrelated lockfile churn.
pixi runcan refresh a stale lockfile implicitly; revert that noise unless the refresh is the point of the change. -
If a lockfile changes, the PR description should briefly say why.
-
Isolate large dependency refreshes from feature work when possible. Prefer a dedicated lockfile-only PR over mixing solver churn into an unrelated change.
CI enforces the contract: pixi lock --check fails when a committed lockfile is
stale, and pixi source-build jobs run with PIXI_LOCKED=true so they install from
the committed lock rather than updating it during the job. If either check
fails, regenerate and commit the affected lockfile.
The freshness check additionally fails when the check itself rewrote a lockfile.
pixi lock --check accepts a lock whose solution is current but whose bytes are
not canonical for the pinned pixi version, quietly normalizing the file instead,
which leaves every later pixi run rewriting the committed lockfile.
A scheduled workflow (CI: pixi lockfile refresh) runs
pixi update --no-install for every workspace in one job and opens one PR with
all changed lockfiles, so broad dependency churn is reviewed as maintenance
rather than landing inside unrelated feature work. The workflow can also be
dispatched manually. Both lockfile workflows resolve their workspace lists
through ci/tools/list_pixi_workspaces.py, which derives the inventory from the
committed manifests, so a newly added workspace is picked up without editing a
workflow.
Refresh PRs use GITHUB_TOKEN. After one opens, a maintainer with write access
must first select Approve workflows to run in the merge box, then assign
themselves to the PR. Approval starts the queued pull_request runs; the
human-generated assigned event creates the required
PR has assignee, labels, and milestone pull_request_target check and gives
the PR a clear owner.
A future GitHub App integration could trigger both pull_request and
pull_request_target workflows automatically.
See GitHub's
token event documentation
for the current behavior.
The secret-scan-trufflehog pre-commit hook scans staged files and installs TruffleHog into its own environment on first run, on Linux, macOS, and Windows. If it flags a secret, remove it before committing, or contact a maintainer if it's a false positive. Secrets are also scanned server-side in CI.
Contributions to files licensed under Apache 2.0 must be certified under the
Developer Certificate of Origin (DCO).
Sign off every commit with the -s option:
git commit -s -m "Describe your change"Git uses your configured name and email address to add a trailer like this to the commit message:
Signed-off-by: Your Name <your.email@example.com>
Use your real name and an email address associated with your contribution. The sign-off certifies that you have the right to submit the contribution under the DCO below. DCO sign-off is separate from the cryptographic commit signing described in the next section; both requirements apply.
This repository implements a security check to prevent the CI system from running untrusted code. A part of the security check consists of checking if the git commits are signed. Please ensure that your commits are signed following GitHub’s instruction.
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
The CUDA Python project uses a comprehensive CI pipeline that builds, tests, and releases multiple components across different platforms. This section provides a visual overview of our CI infrastructure to help contributors understand the build and release process.
The CI pipeline diagram is maintained as Mermaid source in ci/ci-pipeline.mmd.
Parallel Execution: The CI pipeline leverages parallel execution to optimize build and test times:
- Build Stage: Different architectures/operating systems (linux-64, linux-aarch64, win-64) are built in parallel across their respective runners
- Test Stage: Different architectures/operating systems/CUDA versions are tested in parallel; documentation preview is also built in parallel with testing
- Build → Test → Documentation → Potential Release
- Artifacts stored as
{component}-python{version}-{platform}-{sha} - Full test coverage across all platforms and CUDA versions
- Artifact flow out:
cuda-pathfinderartifacts → backport branches
- Build → Test → Backport PR Creation
- Artifacts used for validation before creating backport pull requests
- Maintains compatibility with older CUDA versions
- Artifact flow in:
cuda-pathfinderartifacts ← main branch - Artifact flow out: older
cuda-bindingsartifacts → main branch
- Self-hosted runners: Used for Linux builds and GPU testing (more resources, faster builds)
- GitHub-hosted runners: Used for Windows builds and general tasks
- Artifact retention: 30 days for GitHub Artifacts (wheels, docs, tests)
- Cache retention: GitHub Cache for build dependencies and environments
- Security: All commits must be signed, untrusted code blocked
- Parallel execution: Matrix builds across Python versions and platforms
- Component isolation: Each component (core, bindings, pathfinder, python) can be built/released independently
Code coverage reports are produced nightly and posted to GitHub Pages.
Known limitations: Code coverage is only run on Linux x86_64 with an a100 GPU. We plan to add more platform and GPU coverage in the future.
1: The cuda-python meta package shares the same license and the contributing guidelines as those of cuda-bindings.