diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f4d3e8e15..bed4a35a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -683,13 +683,16 @@ Before contributing, run `mise run format` and `mise run check`. See `AGENTS.md` ## Releasing -Releases are published to PyPI via the **Release NeMo Safe Synthesizer** GitHub Actions workflow. The workflow builds the wheel from a git tag, publishes to Test PyPI as a pre-flight check, publishes to the real PyPI, and creates a GitHub release. +Pushing a `v*` tag starts two workflows. [`release.yml`](.github/workflows/release.yml) +publishes the wheel to Test PyPI and PyPI, creates a GitHub release, and +publishes versioned documentation for stable releases. +[`container-build.yml`](.github/workflows/container-build.yml) publishes the +CUDA image to GitHub Container Registry (GHCR). -### 1. Create and push a tag - -Release versions follow [PEP440](https://peps.python.org/pep-0440/) with major, minor, and patch release numbers. -This project uses stable releases and release candidates only; prerelease versions append the suffix rcN (no dash, as specified by PEP440). -The GitHub tag always starts with a `v` prefix. +Release versions follow [PEP 440](https://peps.python.org/pep-0440/) with major, +minor, and patch release numbers. This project uses stable releases and release +candidates only; prerelease versions append `rcN` without a dash. The GitHub tag +always starts with a `v` prefix. Examples: @@ -704,21 +707,93 @@ Examples: | `v0.0.7-rc4` | | ❌ Dash before rc suffix | | `v0.1.3a1` | | ❌ Alpha prereleases are not used; use rcN only | -To create and push tags: +### Release Checklist + +#### Before Publishing + +- Fetch `origin/main` and tags, choose the exact release commit, and confirm its + normal CI and manually dispatched GPU Tests run passed. +- Choose unused candidate and stable tags, then record the candidate's exact + `origin/main` SHA. +- Decide whether GHCR visibility or a separate nSpect or Pulse scan blocks the + release. Those scans are not part of the GitHub release workflows. + +#### Tag Trigger + +Create the candidate tag at the recorded SHA and push it. This automatically +starts the package and container workflows described above. + +```bash +RC_TAG=v0.1.0rc0 # Replace with the intended candidate tag. +RELEASE_SHA="$(git rev-parse 'origin/main^{commit}')" +git tag "${RC_TAG}" "${RELEASE_SHA}" +git push origin "refs/tags/${RC_TAG}" +``` + +Candidate tags also move the mutable GHCR `cu129` and `latest-cu129` aliases. +During candidate validation, identify the image by its immutable +`sha--cu129` tag and do not treat those aliases as stable. + +Never move a published tag. If code changes, create and validate the next +`rcN`. + +#### Verify and Promote + +- Verify the candidate tag still resolves to the recorded SHA and both + workflows succeeded for that tag. +- Confirm the candidate exists on Test PyPI, production PyPI, and GitHub + Releases, then pull its immutable GHCR SHA tag with the intended visibility. +- Install the production-PyPI wheel outside the repository with uv project + configuration disabled. Check the dependency set, import, and CLI. + +Use the same auxiliary indexes documented in the installation guide for the +clean CUDA install. Set `NSS_VERSION` to the candidate version: ```bash -# Stable release -git tag v0.1.0 +SMOKE_DIR=/tmp/nss-release-smoke +SMOKE_VENV="${SMOKE_DIR}/.venv" +NSS_VERSION="${RC_TAG#v}" +mkdir -p "${SMOKE_DIR}" +cd "${SMOKE_DIR}" +uv --no-config venv --clear --python 3.13 "${SMOKE_VENV}" +uv --no-config pip install \ + --python "${SMOKE_VENV}/bin/python" \ + --default-index https://pypi.org/simple \ + --index https://flashinfer.ai/whl/cu129 \ + --index https://download.pytorch.org/whl/cu129 \ + --index https://wheels.vllm.ai/ee0da84ab9e04ac7610e28580af62c365e898389/cu129 \ + --index-strategy unsafe-best-match \ + "nemo-safe-synthesizer[cu129,engine]==${NSS_VERSION}" +uv --no-config pip check --python "${SMOKE_VENV}/bin/python" +"${SMOKE_VENV}/bin/python" -c 'import nemo_safe_synthesizer' +"${SMOKE_VENV}/bin/safe-synthesizer" --help +``` -# Release candidate -git tag v0.1.0rc1 +Promote only after every candidate check passes. The stable tag must point to +the same tested SHA, not a later `main` commit. -git push origin +```bash +STABLE_TAG="${RC_TAG%%rc*}" +git tag "${STABLE_TAG}" "${RELEASE_SHA}" +git push origin "refs/tags/${STABLE_TAG}" ``` -### 2. Monitor the workflow run +#### After Publishing Stable + +After publishing: -The [workflow](https://github.com/NVIDIA-NeMo/Safe-Synthesizer/actions/workflows/release.yml) to release is triggered automatically when a tag starting with `v` is pushed to GitHub. +- Verify both tag-triggered workflows passed at the tested SHA. +- Confirm Test PyPI and production PyPI contain the stable version. +- Confirm the GitHub release is not marked as a prerelease. +- Confirm versioned documentation is available at + `https://nvidia-nemo.github.io/Safe-Synthesizer//`. +- Confirm GHCR exposes the stable `X.Y.Z-cu129` and `X.Y-cu129` tags with the + intended visibility. +- Coordinate a NeMo Platform package or container pin, documentation update, + and downstream release when Platform should consume the new version. This is + not currently automated by the Safe Synthesizer release workflow. +- Announce the release only after artifacts and stable documentation pass + verification. ## NMP Integration @@ -732,7 +807,9 @@ The `publish:internal` mise task builds a wheel and uploads it to NVIDIA Artifac mise run publish:internal ``` -This requires `TWINE_REPOSITORY_URL`, `TWINE_USERNAME`, and `TWINE_PASSWORD` environment variables. CI handles this automatically on tagged releases. +This requires `TWINE_REPOSITORY_URL`, `TWINE_USERNAME`, and `TWINE_PASSWORD` +environment variables. This is a manual action; the tag-triggered release +workflow does not publish to internal Artifactory. ### Local Development with NMP diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index 1ad6865fd..a92639a76 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -88,7 +88,8 @@ indexes outside PyPI. You must pass the extra index URLs shown below. ```bash uv pip install "nemo-safe-synthesizer[cpu,engine]" \ - --index https://download.pytorch.org/whl/cpu + --index https://download.pytorch.org/whl/cpu \ + --index-strategy unsafe-best-match ``` !!! warning "Development use only" diff --git a/pyproject.toml b/pyproject.toml index d1796476c..d886d28a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,8 +113,8 @@ engine = [ cpu = [ "accelerate>=1.1.0", "bitsandbytes>=0.46.1", - "flashinfer-python==0.6.13; sys_platform=='linux'", - "flashinfer-cubin==0.6.13; sys_platform=='linux'", + "flashinfer-python==0.6.12; sys_platform=='linux'", + "flashinfer-cubin==0.6.12; sys_platform=='linux'", "gliner", "kernels>=0.12.1,<0.15", "peft>=0.18.0", @@ -137,9 +137,9 @@ cpu = [ cu129 = [ "accelerate>=1.1.0", "bitsandbytes>=0.46.1", - "flashinfer-python==0.6.13; sys_platform == 'linux'", - "flashinfer-cubin==0.6.13; sys_platform == 'linux'", - "flashinfer-jit-cache==0.6.13+cu129; sys_platform == 'linux'", + "flashinfer-python==0.6.12; sys_platform == 'linux'", + "flashinfer-cubin==0.6.12; sys_platform == 'linux'", + "flashinfer-jit-cache==0.6.12+cu129; sys_platform == 'linux'", "gliner", "kernels>=0.12.1,<0.15", "nvidia-cublas-cu12; sys_platform == 'linux'", @@ -179,11 +179,6 @@ conflicts = [ ] -override-dependencies = [ - "flashinfer-python==0.6.13; sys_platform != 'darwin'", - "flashinfer-cubin==0.6.13; sys_platform != 'darwin'", -] - environments = [ "sys_platform == 'linux' and platform_machine == 'x86_64'", "sys_platform == 'darwin' and platform_machine == 'arm64'", @@ -243,12 +238,6 @@ constraint-dependencies = [ { index = "pytorch-cpu", extra="cpu" }, { index = "pytorch-cu129", extra="cu129"}, ] - flashinfer-python = [ - { index = "flashinfer-jit-cache", marker = "sys_platform=='linux'", extra="cu129"}, - ] - flashinfer-cubin = [ - { index = "flashinfer-jit-cache", marker = "sys_platform=='linux'", extra="cu129"}, - ] flashinfer-jit-cache = [ { index = "flashinfer-jit-cache", marker = "sys_platform=='linux'", extra="cu129"}, ] diff --git a/uv.lock b/uv.lock index 7cfbcd1f7..91877140a 100644 --- a/uv.lock +++ b/uv.lock @@ -60,10 +60,6 @@ constraints = [ { name = "tornado", specifier = ">=6.5.5" }, { name = "urllib3", specifier = ">=2.7.0" }, ] -overrides = [ - { name = "flashinfer-cubin", marker = "sys_platform != 'darwin'", specifier = "==0.6.13" }, - { name = "flashinfer-python", marker = "sys_platform != 'darwin'", specifier = "==0.6.13" }, -] [[package]] name = "accelerate" @@ -1477,24 +1473,24 @@ wheels = [ [[package]] name = "flashinfer-cubin" -version = "0.6.13" +version = "0.6.12" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/43/ce916b4cdec4705173e222ca29c68e09004b47526888746094c5ffb29fca/flashinfer_cubin-0.6.13-py3-none-any.whl", hash = "sha256:41e4848c2d09d220e8394489b2fb6cfec6b6ad09f897b5ab8b39fc23055f6c24", size = 457984995, upload-time = "2026-06-25T00:29:26.08Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c6/63b1bb7b1a7ae612ecf53c0e568312c3d004f9f7558b0ab5edcf7900c360/flashinfer_cubin-0.6.12-py3-none-any.whl", hash = "sha256:01de132c493bb21d5df42ebe6890966cf83b40aa970dae06b2a3c0bed85f13ec", size = 447533460, upload-time = "2026-05-29T23:45:27.579Z" }, ] [[package]] name = "flashinfer-jit-cache" -version = "0.6.13+cu129" +version = "0.6.12+cu129" source = { registry = "https://flashinfer.ai/whl/cu129" } wheels = [ - { url = "https://github.com/flashinfer-ai/flashinfer/releases/download/v0.6.13/flashinfer_jit_cache-0.6.13+cu129-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ef80fb82dbe2016d411f51b4c42e836f7d14b281b424c273c526fa5b3d0f737c" }, - { url = "https://github.com/flashinfer-ai/flashinfer/releases/download/v0.6.13/flashinfer_jit_cache-0.6.13+cu129-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0af546a6a0db3470b0a52b43c8158fc85358852ab1b0a8a42d83a411f0aaa369" }, + { url = "https://github.com/flashinfer-ai/flashinfer/releases/download/v0.6.12/flashinfer_jit_cache-0.6.12+cu129-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f78e25e52ebda55f422acbaf9b92f36b40d0fbc6d9ae3411493bdf6b9c4aa63e" }, + { url = "https://github.com/flashinfer-ai/flashinfer/releases/download/v0.6.12/flashinfer_jit_cache-0.6.12+cu129-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7136244e415297418e583050ef70eecd845129d4fa60bee8f84e86d385e601fd" }, ] [[package]] name = "flashinfer-python" -version = "0.6.13" +version = "0.6.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "apache-tvm-ffi", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -1514,9 +1510,9 @@ dependencies = [ { name = "torch", version = "2.11.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'extra-21-nemo-safe-synthesizer-cpu' and extra == 'extra-21-nemo-safe-synthesizer-cu129') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-21-nemo-safe-synthesizer-cu129') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-21-nemo-safe-synthesizer-cu129') or (sys_platform != 'linux' and extra == 'extra-21-nemo-safe-synthesizer-cpu' and extra == 'extra-21-nemo-safe-synthesizer-cu129')" }, { name = "tqdm", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/f7/7f6dd2b03f4277509dfd1e5c7a8ec1de2662fd245d2e663f44a3493882b1/flashinfer_python-0.6.13.tar.gz", hash = "sha256:8a6d7d3708c7c87952390ec4e3aabe6e1c356defa8c7211b26bccaa355a61c59", size = 9638085, upload-time = "2026-06-24T22:46:29.391Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/d0/114a64319f5a804def2f307d5ed8f95e6d94a2acdacac4ed5f57525cbf46/flashinfer_python-0.6.12.tar.gz", hash = "sha256:bed67f9c46d81dd22611dfef2787998fc412b2fe2648d9e7d336861dda912694", size = 9453326, upload-time = "2026-05-29T23:45:16.466Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/50/e8920ed7f68e0116a385e3ab814ac2f0010579852fc483bfb48819d11976/flashinfer_python-0.6.13-py3-none-any.whl", hash = "sha256:239e6ddc3cbbaf0bee251861a8c7c69438b1171830d69ddfa133ddea4494850d", size = 14191198, upload-time = "2026-06-24T22:46:26.565Z" }, + { url = "https://files.pythonhosted.org/packages/85/26/3ca33edbf64906603633cb91904798e427c0ac1c55a13707f8081708f3ae/flashinfer_python-0.6.12-py3-none-any.whl", hash = "sha256:0c7a01e586b4796810d974cbf13a9c0eb2ade6a94d12e3220cf7782a1c09b8d3", size = 13985243, upload-time = "2026-05-29T23:45:13.477Z" }, ] [[package]] @@ -3295,11 +3291,11 @@ requires-dist = [ { name = "dython", marker = "extra == 'engine'" }, { name = "faker", specifier = ">=20.0" }, { name = "faker", marker = "extra == 'engine'" }, - { name = "flashinfer-cubin", marker = "sys_platform == 'linux' and extra == 'cpu'", specifier = "==0.6.13" }, - { name = "flashinfer-cubin", marker = "sys_platform == 'linux' and extra == 'cu129'", specifier = "==0.6.13", index = "https://flashinfer.ai/whl/cu129", conflict = { package = "nemo-safe-synthesizer", extra = "cu129" } }, - { name = "flashinfer-jit-cache", marker = "sys_platform == 'linux' and extra == 'cu129'", specifier = "==0.6.13+cu129", index = "https://flashinfer.ai/whl/cu129", conflict = { package = "nemo-safe-synthesizer", extra = "cu129" } }, - { name = "flashinfer-python", marker = "sys_platform == 'linux' and extra == 'cpu'", specifier = "==0.6.13" }, - { name = "flashinfer-python", marker = "sys_platform == 'linux' and extra == 'cu129'", specifier = "==0.6.13", index = "https://flashinfer.ai/whl/cu129", conflict = { package = "nemo-safe-synthesizer", extra = "cu129" } }, + { name = "flashinfer-cubin", marker = "sys_platform == 'linux' and extra == 'cpu'", specifier = "==0.6.12" }, + { name = "flashinfer-cubin", marker = "sys_platform == 'linux' and extra == 'cu129'", specifier = "==0.6.12" }, + { name = "flashinfer-jit-cache", marker = "sys_platform == 'linux' and extra == 'cu129'", specifier = "==0.6.12+cu129", index = "https://flashinfer.ai/whl/cu129", conflict = { package = "nemo-safe-synthesizer", extra = "cu129" } }, + { name = "flashinfer-python", marker = "sys_platform == 'linux' and extra == 'cpu'", specifier = "==0.6.12" }, + { name = "flashinfer-python", marker = "sys_platform == 'linux' and extra == 'cu129'", specifier = "==0.6.12" }, { name = "flashtext", marker = "extra == 'engine'" }, { name = "gliner", marker = "extra == 'cpu'" }, { name = "gliner", marker = "extra == 'cu129'" },