Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
b844ac0
add rocm support
tjtanaa Oct 17, 2025
73b2674
add rocm documentation
tjtanaa Oct 19, 2025
663e8b6
add amd perf benchmark data
tjtanaa Oct 19, 2025
93e247e
fix image
tjtanaa Oct 20, 2025
962010c
address reviewer feedback
tjtanaa Oct 24, 2025
f7e617d
remove torch dependencies
tjtanaa Nov 6, 2025
72eb785
handle when cuda and rocm not found
tjtanaa Nov 6, 2025
6ddba67
fix paddle paddle
tjtanaa Nov 6, 2025
8714185
setup rocm wheel ci build
tjtanaa Nov 6, 2025
5fb3a14
manual trigger rocm workflow
tjtanaa Nov 6, 2025
5e2e4af
remove system package steps
tjtanaa Nov 6, 2025
75530b5
install system dependencies
tjtanaa Nov 6, 2025
6ad6bb2
upgrade ubuntu version, skip tests
tjtanaa Nov 6, 2025
ab00395
upgrade ubuntu version, skip tests
tjtanaa Nov 6, 2025
9a11ce3
install from python from offical source
tjtanaa Nov 6, 2025
d6c1f4a
use venv instead
tjtanaa Nov 6, 2025
5d24383
fix other python ci build
tjtanaa Nov 6, 2025
5eba4b5
build many linux
tjtanaa Nov 6, 2025
c543e16
remove rocm_ tag from platform tag
tjtanaa Nov 6, 2025
8ea1956
Add automated PyPI index with separate CUDA/ROCm backends
tjtanaa Nov 6, 2025
f6101fc
only manual trigger when deploying pypi index
tjtanaa Nov 6, 2025
643d12d
add publish to index GA workflow
tjtanaa Nov 7, 2025
0935d8c
fix the publish to index
tjtanaa Nov 7, 2025
378b262
fix the publish to index write permission
tjtanaa Nov 7, 2025
5efe4a9
fix the publish to index for both mode
tjtanaa Nov 7, 2025
8871043
fix the manylinux rocmwheel build
tjtanaa Nov 7, 2025
619c531
update publish to index
tjtanaa Nov 7, 2025
6a3f302
fix nested dumb-pypi
tjtanaa Nov 7, 2025
c3a580b
fix publish to index
tjtanaa Nov 7, 2025
4eca2b7
fix the dumb-pypi
tjtanaa Nov 7, 2025
2f15898
fix the package path
tjtanaa Nov 7, 2025
8ffcefa
lint
tjtanaa Nov 7, 2025
f09cb54
remove deploy-pypi-index
tjtanaa Nov 7, 2025
821fb41
remove unused code
tjtanaa Nov 7, 2025
e9ff27f
update publish to index to handle version isolation
tjtanaa Nov 7, 2025
eff48c7
fix publish to index yaml syntax
tjtanaa Nov 7, 2025
4dcbb76
fix publish to index syntax error
tjtanaa Nov 7, 2025
86faab4
add workflow python script
tjtanaa Nov 7, 2025
2b12a97
only bundle the dependencies specified in the pyproject.toml
tjtanaa Nov 7, 2025
f0ec845
bugfix the workflow
tjtanaa Nov 7, 2025
5291031
fixing the publsih to index workflow
tjtanaa Nov 7, 2025
1f2e60c
fixing the publsih to index workflow
tjtanaa Nov 7, 2025
bf274a1
update workflow instruction
tjtanaa Nov 7, 2025
c5b4886
only allow publish to index be triggered manually
tjtanaa Nov 7, 2025
d631e44
remove github workflow
tjtanaa Nov 10, 2025
8832411
sync with upstream
tjtanaa Nov 10, 2025
e353538
update installation procedure on ROCm
tjtanaa Nov 10, 2025
8fd8b99
fix installation command
tjtanaa Nov 10, 2025
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
228 changes: 228 additions & 0 deletions .github/scripts/generate_pypi_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
#!/usr/bin/env python3
"""
Generate PyPI-compatible simple index from GitHub releases.
This script fetches all releases and creates separate indexes for CUDA and ROCm wheels.
"""

import json
import os
import sys
from pathlib import Path
from urllib.request import urlopen, Request

def fetch_releases(repo_owner, repo_name):
"""Fetch all releases from GitHub API."""
url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases"
headers = {
"Accept": "application/vnd.github.v3+json",
"User-Agent": "PyPI-Index-Generator"
}

# Add GitHub token if available (for higher rate limits)
token = os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"token {token}"

request = Request(url, headers=headers)

try:
with urlopen(request) as response:
return json.loads(response.read().decode())
except Exception as e:
print(f"Error fetching releases: {e}", file=sys.stderr)
sys.exit(1)

def categorize_backend(release_tag):
"""Determine backend (cuda/rocm) from release tag."""
tag_lower = release_tag.lower()

if "rocm" in tag_lower:
return "rocm"
elif "cuda" in tag_lower:
return "cuda"
else:
# Default to cuda for untagged releases
return "cuda"

def extract_wheels_by_backend(releases):
"""Extract wheel files from releases, categorized by backend."""
wheels_by_backend = {
"cuda": [],
"rocm": []
}

for release in releases:
backend = categorize_backend(release.get("tag_name", ""))

for asset in release.get("assets", []):
name = asset.get("name", "")
if name.endswith(".whl"):
wheels_by_backend[backend].append({
"name": name,
"url": asset.get("browser_download_url"),
"version": release.get("tag_name"),
})

return wheels_by_backend

def generate_root_index(output_dir, packages):
"""Generate the root simple index."""
html = """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Simple Index</title>
</head>
<body>
<h1>Simple Index</h1>
"""

for package in sorted(packages):
html += f' <a href="{package}/">{package}</a><br/>\n'

html += """</body>
</html>
"""

output_path = output_dir / "index.html"
output_path.write_text(html)
print(f"Generated: {output_path}")

def generate_package_index(output_dir, package_name, wheels):
"""Generate package-specific index with all wheels."""
html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Links for {package_name}</title>
</head>
<body>
<h1>Links for {package_name}</h1>
"""

# Sort wheels by version and Python version
sorted_wheels = sorted(wheels, key=lambda w: (w["name"], w["version"]), reverse=True)

for wheel in sorted_wheels:
# Extract package name from wheel filename to ensure consistency
wheel_name = wheel["name"]
url = wheel["url"]
html += f' <a href="{url}#sha256=">{wheel_name}</a><br/>\n'

html += """</body>
</html>
"""

package_dir = output_dir / package_name
package_dir.mkdir(parents=True, exist_ok=True)

output_path = package_dir / "index.html"
output_path.write_text(html)
print(f"Generated: {output_path}")

def generate_landing_page(base_dir, repo_name):
"""Generate a landing page for the PyPI index."""
html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{repo_name} - PyPI Index</title>
<style>
body {{ font-family: system-ui, -apple-system, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }}
h1 {{ color: #2c3e50; }}
.backend {{ margin: 30px 0; padding: 20px; background: #f8f9fa; border-radius: 8px; }}
code {{ background: #e9ecef; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; }}
pre {{ background: #2c3e50; color: #ecf0f1; padding: 15px; border-radius: 6px; overflow-x: auto; }}
</style>
</head>
<body>
<h1>{repo_name} - PyPI Index</h1>
<p>Choose the appropriate index URL based on your GPU backend:</p>

<div class="backend">
<h2>🔥 ROCm (AMD GPUs)</h2>
<p>For AMD GPUs using ROCm:</p>
<pre>pip install fastsafetensors --index-url https://embeddedllm.github.io/{repo_name}/rocm/simple/</pre>
</div>

<div class="backend">
<h2>💚 CUDA (NVIDIA GPUs)</h2>
<p>For NVIDIA GPUs using CUDA:</p>
<pre>pip install fastsafetensors --index-url https://embeddedllm.github.io/{repo_name}/cuda/simple/</pre>
</div>

<h3>Version Specific Installation</h3>
<pre>pip install fastsafetensors==0.1.15 --index-url https://embeddedllm.github.io/{repo_name}/rocm/simple/</pre>

<h3>In requirements.txt</h3>
<pre>--index-url https://embeddedllm.github.io/{repo_name}/rocm/simple/
fastsafetensors>=0.1.15</pre>

<hr>
<p><small>Direct access: <a href="rocm/simple/">ROCm Index</a> | <a href="cuda/simple/">CUDA Index</a></small></p>
</body>
</html>
"""

output_path = base_dir / "index.html"
output_path.write_text(html)
print(f"Generated landing page: {output_path}")

def main():
# Configuration
repo_owner = os.environ.get("GITHUB_REPOSITORY_OWNER", "EmbeddedLLM")
repo_full = os.environ.get("GITHUB_REPOSITORY", "EmbeddedLLM/fastsafetensors-rocm")
repo_name = repo_full.split("/")[-1]

print(f"Fetching releases from {repo_owner}/{repo_name}...")
releases = fetch_releases(repo_owner, repo_name)
print(f"Found {len(releases)} releases")

# Extract wheels categorized by backend
wheels_by_backend = extract_wheels_by_backend(releases)

total_wheels = sum(len(wheels) for wheels in wheels_by_backend.values())
print(f"Found {total_wheels} total wheel files")
print(f" CUDA: {len(wheels_by_backend['cuda'])} wheels")
print(f" ROCm: {len(wheels_by_backend['rocm'])} wheels")

if total_wheels == 0:
print("Warning: No wheel files found in any release", file=sys.stderr)
return

# Generate indexes for each backend
for backend, wheels in wheels_by_backend.items():
if not wheels:
print(f"Skipping {backend} index (no wheels found)")
continue

print(f"\nGenerating {backend.upper()} index...")
output_dir = Path(f"pypi-index/{backend}/simple")
output_dir.mkdir(parents=True, exist_ok=True)

# Group wheels by package name
packages = {}
for wheel in wheels:
# Extract package name from wheel filename (before first dash)
package_name = wheel["name"].split("-")[0]
if package_name not in packages:
packages[package_name] = []
packages[package_name].append(wheel)

# Generate indexes
generate_root_index(output_dir, packages.keys())

for package_name, package_wheels in packages.items():
generate_package_index(output_dir, package_name, package_wheels)

print(f" Generated {backend.upper()} index with {len(packages)} package(s)")

# Generate landing page
base_dir = Path("pypi-index")
generate_landing_page(base_dir, repo_name)

print(f"\n✓ Successfully generated indexes for all backends")
print(f" Total wheels: {total_wheels}")

if __name__ == "__main__":
main()
Loading