Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 56 additions & 8 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ jobs:
- name: Build the universal CPU wheel
env:
SFA_BUILD_CUDA: "0"
SFA_PACKAGE_NAME: "sfa"
run: |
python -m pip install --upgrade pip build
python -m build --wheel
Expand All @@ -72,7 +71,6 @@ jobs:
- name: Build sdist
env:
SFA_BUILD_CUDA: "0"
SFA_PACKAGE_NAME: "sfa"
run: |
python -m pip install --upgrade pip build
python -m build --sdist
Expand Down Expand Up @@ -221,6 +219,22 @@ jobs:
echo "--- $CUDA_HOME/lib64 (cublas only) ---"
ls -la "$CUDA_HOME/lib64/" | grep -E 'cublas|nvrtc' || true

- name: Rename wheel project to ${{ matrix.cuda.pkg }}
# PEP 621 forbids `name` from being declared dynamic in
# pyproject.toml, so the same source tree's static
# `name = "sfa"` must be sed-replaced in place per CUDA cell
# to produce the per-CUDA wheel names (sfa-cu128 / sfa-cu132).
# The CPU and sdist jobs leave this line untouched and ship
# under the default `sfa` name. Runs on both Linux and
# Windows runners via Git Bash; cibuildwheel afterwards reads
# the modified pyproject.toml inside its build sandbox.
shell: bash
run: |
set -euo pipefail
sed -i 's/^name = "sfa"$/name = "${{ matrix.cuda.pkg }}"/' pyproject.toml
echo "--- name line after rename ---"
grep '^name' pyproject.toml

- name: Build wheels
uses: pypa/cibuildwheel@v2.21
env:
Expand All @@ -238,9 +252,10 @@ jobs:
CIBW_CONTAINER_ENGINE: >-
docker; create_args: -v /usr/local/cuda:/usr/local/cuda:ro
# Linux-only env: bind-mounted CUDA path + PATH update.
# The wheel project name is set by the sed step above
# (pyproject.toml's `[project] name`), not by an env var.
CIBW_ENVIRONMENT_LINUX: >-
SFA_BUILD_CUDA=1
SFA_PACKAGE_NAME=${{ matrix.cuda.pkg }}
SFA_CUDA_ARCH="${{ matrix.cuda.archs }}"
SFA_CUDA_RUNTIME_REQUIRES="${{ matrix.cuda.runtime_requires }}"
CUDA_PATH=/usr/local/cuda
Expand All @@ -250,7 +265,6 @@ jobs:
# subprocess automatically.
CIBW_ENVIRONMENT_WINDOWS: >-
SFA_BUILD_CUDA=1
SFA_PACKAGE_NAME=${{ matrix.cuda.pkg }}
SFA_CUDA_ARCH="${{ matrix.cuda.archs }}"
SFA_CUDA_RUNTIME_REQUIRES="${{ matrix.cuda.runtime_requires }}"
# GitHub runners have no NVIDIA GPU, so the CUDA-gated pytest
Expand Down Expand Up @@ -280,17 +294,51 @@ jobs:
name: cuda-wheels-${{ matrix.cuda.pkg }}-${{ matrix.os }}
path: ./wheelhouse/*.whl

# ---------------------------------------------------------------------------
# Publish all build artifacts (CPU universal wheel, sdist, and every
# CUDA wheel produced by the matrix) to the GitHub Release that the
# `v*` tag triggers. End users install from these URLs directly:
#
# pip install https://github.com/dwgoon/sfa/releases/download/v0.2.0/<wheel-name>
#
# This is the primary distribution channel while the PyPI publish job
# below is disabled.
# ---------------------------------------------------------------------------
publish_github_release:
name: publish-github-release
needs: [build_cpu_wheel, build_sdist, build_cuda_wheels]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: List collected artifacts
run: ls -la dist/
- uses: softprops/action-gh-release@v2
with:
files: dist/*
generate_release_notes: true
prerelease: false
fail_on_unmatched_files: true

# ---------------------------------------------------------------------------
# Optional: publish to PyPI on tag pushes. Requires the OIDC trusted-
# publisher relationship to be configured at https://pypi.org for each
# of `sfa`, `sfa-cu128`, `sfa-cu132`, `sfa-cu133`. Disabled by default;
# change the `if:` guard to enable.
# of `sfa`, `sfa-cu128`, `sfa-cu132`. Currently disabled because the
# maintainer's PyPI account is unavailable; re-enable by flipping the
# `if:` guard back to `startsWith(github.ref, 'refs/tags/v')` once
# trusted publishers are in place. Existing GitHub Release artifacts
# are unaffected by this gate.
# ---------------------------------------------------------------------------
publish:
publish_pypi:
name: publish-to-pypi
needs: [build_cpu_wheel, build_sdist, build_cuda_wheels]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
if: false
permissions:
id-token: write
steps:
Expand Down
59 changes: 59 additions & 0 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ runtime they link against.
This file is a quick reference. See
[doc/install.md](doc/install.md) for the full guide.

> [!NOTE]
> For the **0.2.0** line, the primary distribution channel is the
> [GitHub Releases page](https://github.com/dwgoon/sfa/releases),
> not PyPI. The `pip install sfa-cuXYZ` shortcuts shown below assume
> the wheels are on PyPI; until they land there, use the explicit
> release URLs in the "Install from GitHub Releases" section. The
> 0.1.0 `sfa` CPU package remains installable from PyPI through the
> `pip install sfa` snippet.

## CPU (any OS)

```bash
Expand Down Expand Up @@ -53,6 +62,56 @@ NVIDIA driver development in 2019.
> Install **only one** `sfa-cuXYZ` per environment; mixing them causes
> import conflicts.

## Install from GitHub Releases

When a `v*` tag is pushed, `wheels.yml` attaches every artifact (one
universal CPU wheel, the sdist, and a CUDA wheel for every supported
Python + OS + CUDA combination) to the corresponding GitHub Release.
`pip` can install those wheels directly:

```bash
# CPU (universal; any OS, any Python from 3.10 to 3.13).
pip install https://github.com/dwgoon/sfa/releases/download/v0.2.0/sfa-0.2.0-py3-none-any.whl
```

For CUDA wheels, the filename encodes Python version, OS, and CUDA
major.minor. The pattern is

```
sfa_cuXYZ-VER-cpPP-cpPP-PLAT.whl
```

with

| Token | Meaning | Possible values |
|--------|------------------------------------------------------|--------------------------------------------------|
| `XYZ` | CUDA major.minor (no dot) | `128` (CUDA 12.8) or `132` (CUDA 13.2) |
| `VER` | sfa version (matches the tag without the `v` prefix) | e.g. `0.2.0` |
| `PP` | CPython major+minor (no dot) | `310`, `311`, `312`, `313` |
| `PLAT` | Wheel platform tag | `manylinux_2_28_x86_64` (Linux) / `win_amd64` (Windows) |

Examples:

```bash
# Linux + Python 3.12 + CUDA 13.2
pip install https://github.com/dwgoon/sfa/releases/download/v0.2.0/sfa_cu132-0.2.0-cp312-cp312-manylinux_2_28_x86_64.whl

# Windows + Python 3.10 + CUDA 12.8
pip install https://github.com/dwgoon/sfa/releases/download/v0.2.0/sfa_cu128-0.2.0-cp310-cp310-win_amd64.whl
```

If none of the prebuilt wheels match your environment, install the
sdist or a tagged git source instead:

```bash
# sdist (compiles a pure-Python install; no CUDA extension).
pip install https://github.com/dwgoon/sfa/releases/download/v0.2.0/sfa-0.2.0.tar.gz

# git source at the v0.2.0 tag (set SFA_BUILD_CUDA=1 + have nvcc to
# build the CUDA extension; otherwise a pure-Python install is made).
pip install git+https://github.com/dwgoon/sfa.git@v0.2.0
```

## Optional extras

Matplotlib-based plotting helpers in `sfa.plot`:
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,25 @@ pip install sfa-cu132
> `sfa-cuXYZ` share the `sfa` Python namespace and will conflict if
> stacked.

### Install from GitHub Releases (current 0.2.0 primary channel)

The 0.2.0 line is distributed through the project's
[GitHub Releases page](https://github.com/dwgoon/sfa/releases) until
the new CUDA wheels land on PyPI. Each `v*` tag attaches one universal
CPU wheel, the sdist, and a per-Python / per-OS / per-CUDA wheel for
each `sfa-cuXYZ` variant. Examples:

```bash
# CPU (universal, any OS / Python 3.10 - 3.13)
pip install https://github.com/dwgoon/sfa/releases/download/v0.2.0/sfa-0.2.0-py3-none-any.whl

# CUDA 13.2, Linux, Python 3.12
pip install https://github.com/dwgoon/sfa/releases/download/v0.2.0/sfa_cu132-0.2.0-cp312-cp312-manylinux_2_28_x86_64.whl
```

See [INSTALL.md](INSTALL.md#install-from-github-releases) for the
full wheel-filename pattern and Windows / older-Python URLs.

### Build from source

For a new CUDA major version, a custom GPU architecture, or
Expand Down
27 changes: 15 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@
requires = ["setuptools>=68", "wheel", "pybind11>=2.13", "numpy>=2.0"]
build-backend = "setuptools.build_meta"

# The project `name` is set dynamically from setup.py so the same source
# tree can produce both "sfa" (CPU-only) and "sfa-cu130" (CUDA-enabled)
# wheels under the SFA_PACKAGE_NAME env var.
# PEP 621 requires `name` to be a static string here (it explicitly
# forbids name from being dynamic). The CPU and sdist builds use this
# default `sfa`; CUDA build cells in `wheels.yml` overwrite this line
# in-place with `sed` so the same source tree produces sfa-cu128 /
# sfa-cu132 wheels under their own PyPI names.
#
# `dependencies` is left dynamic so setup.py can append the per-CUDA
# nvidia-cublas-cuXX / nvidia-cuda-runtime-cuXX runtime packages via
# the SFA_CUDA_RUNTIME_REQUIRES env var. With no entry under
# [tool.setuptools.dynamic], setuptools reads the resolved value from
# setup.py's `install_requires=` call.
[project]
name = "sfa"
version = "0.2.0"
Expand All @@ -13,13 +21,7 @@ readme = "README.md"
license = { text = "MIT" }
authors = [{ name = "Daewon Lee", email = "daewon4you@gmail.com" }]
requires-python = ">=3.10"
dependencies = [
"numpy",
"scipy",
"pandas",
"networkx",
"threadpoolctl",
]
dynamic = ["dependencies"]

[project.optional-dependencies]
cuda = [] # toolchain provided by conda env (environment-cuda.yml)
Expand All @@ -40,8 +42,9 @@ include = ["sfa*"]
# Builds CPU-only wheels by default. To produce CUDA wheels, set
# environment variables in the GitHub Actions workflow:
# SFA_BUILD_CUDA=1
# SFA_PACKAGE_NAME=sfa-cu130
# SFA_CUDA_ARCH=sm_70;sm_75;sm_80;sm_86;sm_89;sm_90 (AOT fat binary)
# SFA_CUDA_ARCH=sm_75;sm_80;sm_86;sm_89;sm_90;sm_100;sm_120
# and rewrite the static `[project] name` line above to the target
# wheel name (sfa-cu128 / sfa-cu132) via a sed step before running.
# ---------------------------------------------------------------------------
[tool.cibuildwheel]
build = "cp310-* cp311-* cp312-* cp313-*"
Expand Down
29 changes: 6 additions & 23 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,46 +253,29 @@ def _cuda_extension() -> Optional[Extension]:
ext_modules.append(cuda_ext)


# PyPI package name. Default is the cross-platform CPU-only package "sfa".
# CUDA-enabled wheels are published under per-CUDA-version names
# (sfa-cu128, sfa-cu132, sfa-cu133) so users can pick the variant that
# matches their NVIDIA driver. Override at build time:
# SFA_PACKAGE_NAME=sfa-cu132 SFA_BUILD_CUDA=1 python -m build --wheel
_pkg_name = os.environ.get("SFA_PACKAGE_NAME", "sfa")


# Per-build extra install_requires fed in by the CI matrix. Used to pin
# the NVIDIA runtime PyPI packages (nvidia-cublas-cuXX,
# nvidia-cuda-runtime-cuXX) that match the CUDA major version we are
# building against. The value is a whitespace-separated list of pip
# requirement specifiers, e.g.
# SFA_CUDA_RUNTIME_REQUIRES="nvidia-cublas-cu13>=13.2,<13.3 \
# nvidia-cuda-runtime-cu13>=13.2,<13.3"
#
# The wheel's static metadata (name, version, description, author, etc.)
# lives in pyproject.toml's [project] table; setup.py only supplies what
# isn't there: the CUDA build extension, the dynamic install_requires
# composed from the base list plus _extra_runtime, and the
# build-extension command class.
_extra_runtime = [
spec for spec in os.environ.get("SFA_CUDA_RUNTIME_REQUIRES", "").split()
if spec.strip()
]


setup(
name=_pkg_name,
version="0.2.0.dev0",
description="Signal flow analysis",
url="http://github.com/dwgoon/sfa",
author="Daewon Lee",
author_email="daewon4you@gmail.com",
license="MIT",
packages=find_packages(),
package_data={"": ["*.tsv", "*.sif", "*.json"]},
python_requires=">=3.10",
install_requires=(["numpy", "scipy", "pandas", "networkx",
"threadpoolctl"]
+ _extra_runtime),
extras_require={
"plot": ["matplotlib", "seaborn"],
"cuda": [], # toolchain provided by conda env (environment-cuda.yml)
"test": ["pytest"],
},
ext_modules=ext_modules,
cmdclass={"build_ext": CudaBuildExt} if ext_modules else {},
zip_safe=False,
Expand Down
Loading
Loading