Skip to content

Commit 9d523f8

Browse files
authored
ci/cd: add github workflows to publish flashinfer-cubin wheel to pypi (#1737)
<!-- .github/pull_request_template.md --> ## 📌 Description Follow up of #1718 , this PR adds the github workload to build flashinfer-cubin wheel and publish it to pypi. ## 🔍 Related Issues <!-- Link any related issues here --> ## 🚀 Pull Request Checklist Thank you for contributing to FlashInfer! Before we review your pull request, please make sure the following items are complete. ### ✅ Pre-commit Checks - [x] I have installed `pre-commit` by running `pip install pre-commit` (or used your preferred method). - [x] I have installed the hooks with `pre-commit install`. - [x] I have run the hooks manually with `pre-commit run --all-files` and fixed any reported issues. > If you are unsure about how to set up `pre-commit`, see [the pre-commit documentation](https://pre-commit.com/). ## 🧪 Tests - [x] Tests have been added or updated as needed. - [x] All tests are passing (`unittest`, etc.). ## Reviewer Notes <!-- Optional: anything you'd like reviewers to focus on, concerns, etc. -->
1 parent f7a0be4 commit 9d523f8

File tree

8 files changed

+247
-208
lines changed

8 files changed

+247
-208
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
name: Publish flashinfer-cubin wheel to PyPI
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
tag:
7+
description: 'Tag (e.g., v1.2.3) to build and publish'
8+
required: true
9+
type: string
10+
11+
jobs:
12+
build-and-upload:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Validate tag format
16+
run: |
17+
if [[ ! "${{ inputs.tag }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([a-z0-9]+)?$ ]]; then
18+
echo "Error: Tag '${{ inputs.tag }}' does not match the expected format (e.g., v1.2.3 or v1.2.3.post1 or v1.2.3rc1)"
19+
exit 1
20+
fi
21+
echo "✓ Tag format is valid: ${{ inputs.tag }}"
22+
23+
- name: Check out tag
24+
uses: actions/checkout@v4
25+
with:
26+
ref: ${{ inputs.tag }}
27+
submodules: true
28+
29+
- name: Set up Python
30+
uses: actions/setup-python@v5
31+
with:
32+
python-version: '3.10'
33+
34+
- name: Verify tag matches version.txt (CRITICAL)
35+
run: |
36+
# Extract version from tag (remove 'v' prefix)
37+
TAG_VERSION="${{ inputs.tag }}"
38+
TAG_VERSION="${TAG_VERSION#v}"
39+
40+
# Check version.txt FIRST - this is the source of truth
41+
if [ ! -f "version.txt" ]; then
42+
echo "Error: version.txt file not found!"
43+
exit 1
44+
fi
45+
46+
VERSION_TXT=$(cat version.txt | tr -d '[:space:]')
47+
48+
if [ "$TAG_VERSION" != "$VERSION_TXT" ]; then
49+
echo "❌ CRITICAL ERROR: version.txt does not match tag!"
50+
echo " Tag version: $TAG_VERSION"
51+
echo " version.txt: $VERSION_TXT"
52+
echo ""
53+
echo "Please update version.txt to match the release version before creating a release."
54+
echo "The tag should be 'v$VERSION_TXT' (e.g., if version.txt contains '1.2.3', tag should be 'v1.2.3')"
55+
exit 1
56+
fi
57+
58+
echo "✓ version.txt matches tag version: $VERSION_TXT"
59+
60+
- name: Install build dependencies
61+
run: |
62+
python -m pip install --upgrade pip
63+
pip install build twine wheel
64+
# Install dependencies required for building flashinfer-cubin
65+
pip install setuptools>=61.0 requests filelock torch tqdm
66+
67+
- name: Build flashinfer-cubin wheel
68+
run: |
69+
echo "Building flashinfer-cubin wheel..."
70+
cd flashinfer-cubin
71+
72+
# Clean any previous builds
73+
rm -rf dist build *.egg-info
74+
75+
# Build the wheel using the build module for better isolation
76+
python -m build --wheel
77+
78+
echo "✓ Build completed"
79+
ls -lh dist/
80+
81+
# Move the wheel to the root dist directory for consistency
82+
mkdir -p ../dist
83+
cp dist/*.whl ../dist/
84+
cd ..
85+
86+
- name: Check wheel contents
87+
run: |
88+
echo "Verifying wheel contents..."
89+
# Create a temp directory to extract and check wheel
90+
mkdir -p temp_wheel_check
91+
cd temp_wheel_check
92+
93+
# Extract wheel (wheels are just zip files)
94+
unzip -l ../dist/flashinfer_cubin*.whl | head -30
95+
echo "..."
96+
97+
# Check if cubins are included
98+
unzip -l ../dist/flashinfer_cubin*.whl | grep -c "\.cubin" || true
99+
100+
cd ..
101+
rm -rf temp_wheel_check
102+
echo "✓ Wheel archive created successfully"
103+
104+
- name: Store build artifacts
105+
uses: actions/upload-artifact@v4
106+
with:
107+
name: python-package-distributions
108+
path: dist/
109+
retention-days: 7
110+
111+
- name: Test installation from wheel
112+
run: |
113+
echo "Testing installation from built wheel..."
114+
python -m venv test-env
115+
source test-env/bin/activate
116+
117+
# Install the wheel
118+
pip install dist/flashinfer_cubin*.whl
119+
120+
# Test import and check for cubins
121+
python -c "import flashinfer_cubin"
122+
deactivate
123+
rm -rf test-env
124+
125+
- name: Check package with twine
126+
run: |
127+
echo "Running twine check..."
128+
twine check dist/flashinfer_cubin*.whl
129+
echo "✓ Package validation passed"
130+
131+
- name: Upload to PyPI
132+
env:
133+
TWINE_USERNAME: __token__
134+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
135+
run: |
136+
echo "Uploading flashinfer-cubin wheel to PyPI..."
137+
twine upload --verbose --non-interactive dist/flashinfer_cubin*.whl
138+
echo "✓ Successfully uploaded to PyPI"

3rdparty/cutlass

Submodule cutlass updated 384 files

flashinfer-cubin/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
build/
22
flashinfer_cubin/cubins/
3+
flashinfer_cubin/_build_meta.py

flashinfer-cubin/MANIFEST.in

Lines changed: 0 additions & 3 deletions
This file was deleted.

flashinfer-cubin/build_backend.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
Custom build backend that downloads cubins before building the package.
3+
"""
4+
5+
import os
6+
import sys
7+
from pathlib import Path
8+
from setuptools import build_meta as _orig
9+
from setuptools.build_meta import *
10+
11+
# Add parent directory to path to import artifacts module
12+
sys.path.insert(0, str(Path(__file__).parent.parent))
13+
14+
# add flashinfer._build_meta if not there, it should exist in Path(__file__).parent.parent / "flashinfer" / "_build_meta.py"
15+
build_meta_file = Path(__file__).parent.parent / "flashinfer" / "_build_meta.py"
16+
if not build_meta_file.exists():
17+
version_file = Path(__file__).parent.parent / "version.txt"
18+
if version_file.exists():
19+
with open(version_file, "r") as f:
20+
version = f.read().strip()
21+
with open(build_meta_file, "w") as f:
22+
f.write('"""Build metadata for flashinfer package."""\n')
23+
f.write(f'__version__ = "{version}"\n')
24+
25+
26+
def _download_cubins():
27+
"""Download cubins to the source directory before building."""
28+
from flashinfer.artifacts import download_artifacts
29+
30+
# Create cubins directory in the source tree
31+
cubin_dir = Path(__file__).parent / "flashinfer_cubin" / "cubins"
32+
cubin_dir.mkdir(parents=True, exist_ok=True)
33+
34+
# Set environment variable to download to our package directory
35+
original_cubin_dir = os.environ.get("FLASHINFER_CUBIN_DIR")
36+
os.environ["FLASHINFER_CUBIN_DIR"] = str(cubin_dir)
37+
38+
try:
39+
print(f"Downloading cubins to {cubin_dir}...")
40+
download_artifacts()
41+
print(f"Successfully downloaded cubins to {cubin_dir}")
42+
43+
# Count the downloaded files
44+
cubin_files = list(cubin_dir.rglob("*.cubin"))
45+
print(f"Downloaded {len(cubin_files)} cubin files")
46+
47+
finally:
48+
# Restore original environment variable
49+
if original_cubin_dir:
50+
os.environ["FLASHINFER_CUBIN_DIR"] = original_cubin_dir
51+
else:
52+
os.environ.pop("FLASHINFER_CUBIN_DIR", None)
53+
54+
55+
def _create_build_metadata():
56+
"""Create build metadata file with version information."""
57+
version_file = Path(__file__).parent.parent / "version.txt"
58+
if version_file.exists():
59+
with open(version_file, "r") as f:
60+
version = f.read().strip()
61+
else:
62+
version = "0.0.0+unknown"
63+
64+
# Create build metadata in the source tree
65+
package_dir = Path(__file__).parent / "flashinfer_cubin"
66+
build_meta_file = package_dir / "_build_meta.py"
67+
68+
with open(build_meta_file, "w") as f:
69+
f.write('"""Build metadata for flashinfer-cubin package."""\n')
70+
f.write(f'__version__ = "{version}"\n')
71+
72+
print(f"Created build metadata file with version {version}")
73+
return version
74+
75+
76+
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
77+
"""Build a wheel, downloading cubins first."""
78+
_download_cubins()
79+
_create_build_metadata()
80+
return _orig.build_wheel(wheel_directory, config_settings, metadata_directory)
81+
82+
83+
def build_sdist(sdist_directory, config_settings=None):
84+
"""Build a source distribution, downloading cubins first."""
85+
_download_cubins()
86+
_create_build_metadata()
87+
return _orig.build_sdist(sdist_directory, config_settings)
88+
89+
90+
def build_editable(wheel_directory, config_settings=None, metadata_directory=None):
91+
"""Build an editable install, downloading cubins first."""
92+
_download_cubins()
93+
_create_build_metadata()
94+
return _orig.build_editable(wheel_directory, config_settings, metadata_directory)
95+
96+
97+
# Pass through all other hooks
98+
get_requires_for_build_wheel = _orig.get_requires_for_build_wheel
99+
get_requires_for_build_sdist = _orig.get_requires_for_build_sdist
100+
get_requires_for_build_editable = _orig.get_requires_for_build_editable
101+
prepare_metadata_for_build_wheel = _orig.prepare_metadata_for_build_wheel
102+
prepare_metadata_for_build_editable = _orig.prepare_metadata_for_build_editable

flashinfer-cubin/build_wheel.py

Lines changed: 0 additions & 66 deletions
This file was deleted.

flashinfer-cubin/pyproject.toml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[build-system]
2-
requires = ["setuptools>=61.0", "wheel", "requests", "filelock", "torch", "tqdm"] # NOTE(Zihao): we should remove torch once https://github.com/flashinfer-ai/flashinfer/pull/1641 merged
3-
build-backend = "setuptools.build_meta"
2+
requires = ["setuptools>=61.0", "wheel", "requests", "filelock", "torch", "tqdm", "numpy"] # NOTE(Zihao): we should remove torch once https://github.com/flashinfer-ai/flashinfer/pull/1641 merged
3+
build-backend = "build_backend"
4+
backend-path = ["."]
45

56
[project]
67
name = "flashinfer-cubin"
@@ -26,6 +27,7 @@ classifiers = [
2627
"Programming Language :: Python :: 3.10",
2728
"Programming Language :: Python :: 3.11",
2829
"Programming Language :: Python :: 3.12",
30+
"Programming Language :: Python :: 3.13",
2931
"Topic :: Software Development :: Libraries :: Python Modules",
3032
]
3133
dependencies = [
@@ -47,8 +49,4 @@ include-package-data = true
4749
version = {attr = "flashinfer_cubin.__version__"}
4850

4951
[tool.setuptools.package-data]
50-
flashinfer_cubin = ["cubins/**/*"]
51-
52-
[tool.setuptools.cmdclass]
53-
build_py = "setup.DownloadAndBuildPy"
54-
sdist = "setup.CustomSdist"
52+
flashinfer_cubin = ["cubins/**/*", "_build_meta.py"]

0 commit comments

Comments
 (0)