diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8f875f2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,66 @@ +name: release + +# Publishes to PyPI when a GitHub Release is published. +# +# Authentication is PyPI Trusted Publishing (OIDC): GitHub mints a short-lived +# token for this workflow and PyPI verifies it. No API token is stored in the +# repository, so there is nothing to leak or rotate. +on: + release: + types: [published] + workflow_dispatch: + +jobs: + sdist: + name: Build sdist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Build + run: | + python -m pip install --upgrade build twine + python -m build --sdist + + - name: Check metadata renders on PyPI + run: python -m twine check --strict dist/* + + - name: Verify the sdist actually carries the sources + # cuLOF ships source-only: pip compiles the CUDA extension on the + # target machine. That makes a complete sdist load-bearing, and the + # 0.1.0 release on PyPI shipped without a single .cu file, so it could + # never build. Fail the release rather than publish that again. + run: | + set -euo pipefail + for f in CMakeLists.txt src/cuda/knn.cu src/cuda/lof.cu \ + src/cuda/pipeline.cu src/cuda/preprocess.cu \ + src/cuda/binding.cpp include/culof.h \ + include/culof_internal.cuh src/culof/__init__.py; do + tar tzf dist/*.tar.gz | grep -q "/$f$" \ + || { echo "::error::sdist is missing $f"; exit 1; } + done + echo "sdist contains every file needed to build" + + - uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/*.tar.gz + + publish: + name: Publish to PyPI + needs: sdist + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write # required for trusted publishing + steps: + - uses: actions/download-artifact@v4 + with: + name: sdist + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/CMakeLists.txt b/CMakeLists.txt index a978fd2..1a4010f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,24 @@ cmake_minimum_required(VERSION 3.18) + +# --------------------------------------------------------------------------- +# Find nvcc before the CUDA language is enabled. +# +# CMake searches only PATH for nvcc. The usual Linux install puts the toolkit +# under /usr/local/cuda and only the driver on PATH, which would otherwise be +# indistinguishable from having no CUDA at all -- this is why `pip install` +# failed on machines that had a perfectly good toolkit. +# --------------------------------------------------------------------------- +if(NOT DEFINED CMAKE_CUDA_COMPILER AND NOT DEFINED ENV{CUDACXX}) + file(GLOB _cuda_roots /usr/local/cuda-*) + find_program(_nvcc nvcc + HINTS ENV CUDA_HOME ENV CUDA_PATH ENV CUDA_ROOT + /usr/local/cuda ${_cuda_roots} + PATH_SUFFIXES bin) + if(_nvcc) + set(CMAKE_CUDA_COMPILER "${_nvcc}") + endif() +endif() + project(culof LANGUAGES CXX CUDA) set(CMAKE_CXX_STANDARD 17) diff --git a/README.md b/README.md index 2cfa030..0dc3f07 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ answers. Changing the import is the whole migration. ## Performance -![Speedup over scikit-learn](img/benchmark_speed.png) +![Speedup over scikit-learn](https://raw.githubusercontent.com/Aminsed/cuLOF/main/img/benchmark_speed.png) | samples | scikit-learn | cuLOF | speedup | |--------:|-------------:|------:|--------:| @@ -74,7 +74,7 @@ float32 distance matrix would be 149 GiB. ## Accuracy -![Agreement with scikit-learn](img/accuracy.png) +![Agreement with scikit-learn](https://raw.githubusercontent.com/Aminsed/cuLOF/main/img/accuracy.png) cuLOF computes distances in float32 and scikit-learn in float64, so agreement is measured rather than assumed. At n = 20,000, d = 8, k = 20: **median relative @@ -89,7 +89,7 @@ which points were flagged. [`test_near_ties_are_the_only_disagreement`](tests/python/test_parity.py) asserts exactly this. -![Detection on three datasets](img/detection.png) +![Detection on three datasets](https://raw.githubusercontent.com/Aminsed/cuLOF/main/img/detection.png) ## Install diff --git a/src/culof/_version.py b/src/culof/_version.py index 517b022..48b0fe2 100644 --- a/src/culof/_version.py +++ b/src/culof/_version.py @@ -1,6 +1,7 @@ """Single source of truth for the package version. -pyproject.toml reads it via `tool.setuptools.dynamic`, and `culof.__version__` +pyproject.toml reads it back through the scikit-build-core regex +metadata provider, and `culof.__version__` re-exports it, so there is exactly one place to bump. """