fix: update package_cuda.py for PyInstaller 6.18 layout and remove split_binary.py

- Fix is_nvidia_file() to match NVIDIA DLLs in _internal/torch/lib/
  (PyInstaller 6.18 + torch 2.10 no longer uses nvidia/ subdirectories)
- Remove deprecated split_binary.py (both archives are under 2GB)
- Update torch_compat range to >=2.6.0,<2.11.0
- Update build docs for new dual-archive packaging flow
This commit is contained in:
Jamie Pine
2026-03-17 04:57:05 -07:00
parent 564d787927
commit 88be097b62
5 changed files with 54 additions and 114 deletions
+45 -19
View File
@@ -19,41 +19,67 @@ import sys
import tarfile
from pathlib import Path
# Directories / prefixes that belong in the CUDA libs archive.
# PyInstaller --onedir puts NVIDIA packages in nvidia/ subdirectories
# (e.g. nvidia/cublas/lib/, nvidia/cudnn/lib/, etc.)
NVIDIA_PREFIXES = (
"nvidia/",
"nvidia\\",
)
# Individual DLL patterns that may end up at the top level on Windows
# DLL name prefixes that identify NVIDIA CUDA runtime libraries.
# These DLLs may appear in different locations depending on the torch
# and PyInstaller version:
# - nvidia/ subdirectories (older torch with separate nvidia-* packages)
# - _internal/torch/lib/ (torch 2.10+ bundles NVIDIA DLLs directly)
# - Top-level directory (some PyInstaller versions)
NVIDIA_DLL_PREFIXES = (
"cublas",
"cublaslt",
"cudart",
"cudnn",
"cufft",
"cufftw",
"curand",
"cusolver",
"cusolvermg",
"cusparse",
"nvjitlink",
"nvrtc",
"nccl",
"caffe2_nvrtc",
)
# Files to keep in the server core even if they match NVIDIA prefixes.
# These are small Python modules or stubs, not the large runtime DLLs.
NVIDIA_KEEP_IN_CORE = {
"torch/cuda/nccl.py",
"torch/_inductor/codegen/cuda/cutlass_lib_extensions/cutlass_mock_imports/cuda/cudart.py",
}
def is_nvidia_file(rel_path: str) -> bool:
"""Check if a relative path belongs to the NVIDIA CUDA libs."""
"""Check if a relative path belongs to the NVIDIA CUDA libs.
Identifies large NVIDIA runtime DLLs (.dll/.so) regardless of where
PyInstaller placed them. Excludes small Python stubs that happen to
share NVIDIA-related names.
"""
rel_lower = rel_path.lower().replace("\\", "/")
# Files under nvidia/ subdirectory tree
if rel_lower.startswith("nvidia/"):
return True
# Never split out Python source files or small stubs
if rel_lower in NVIDIA_KEEP_IN_CORE:
return False
# Top-level NVIDIA DLLs (Windows) — e.g. cublas64_12.dll
name = rel_lower.rsplit("/", 1)[-1]
for prefix in NVIDIA_DLL_PREFIXES:
if name.startswith(prefix) and (name.endswith(".dll") or name.endswith(".so")):
# Files under nvidia/ subdirectory tree (older torch layout)
if rel_lower.startswith("nvidia/") or "nvidia/" in rel_lower.split("/", 1)[-1:]:
# Only DLLs/shared objects — not .py, .dist-info, etc.
if rel_lower.endswith((".dll", ".so")):
return True
# Include entire nvidia/ namespace package tree
for part in rel_lower.split("/"):
if part == "nvidia":
return True
# NVIDIA DLLs anywhere in the tree (e.g. _internal/torch/lib/cublas64_12.dll)
name = rel_lower.rsplit("/", 1)[-1]
if name.endswith(".dll") or name.endswith(".so"):
name_no_ext = name.rsplit(".", 1)[0]
for prefix in NVIDIA_DLL_PREFIXES:
if name_no_ext.startswith(prefix):
return True
return False
@@ -186,8 +212,8 @@ def main():
parser.add_argument(
"--torch-compat",
type=str,
default=">=2.6.0,<2.8.0",
help="Torch version compatibility range (default: >=2.6.0,<2.8.0)",
default=">=2.6.0,<2.11.0",
help="Torch version compatibility range (default: >=2.6.0,<2.11.0)",
)
args = parser.parse_args()
-88
View File
@@ -1,88 +0,0 @@
"""
Split a large binary into chunks for GitHub Releases (<2 GB each).
DEPRECATED: For CUDA builds, use scripts/package_cuda.py instead.
This script was used when the CUDA binary was built with --onefile and
needed to be split into parts for the 2GB GitHub Release asset limit.
With the switch to --onedir + dual archives (server core + CUDA libs),
package_cuda.py handles the packaging.
Usage:
python scripts/split_binary.py backend/dist/voicebox-server-cuda.exe
python scripts/split_binary.py backend/dist/voicebox-server-cuda.exe --chunk-size 1900000000
python scripts/split_binary.py backend/dist/voicebox-server-cuda.exe --output release-assets/
The script produces:
- voicebox-server-cuda.part00.exe, .part01.exe, ... (binary chunks)
- voicebox-server-cuda.sha256 (SHA-256 checksum of the complete file)
- voicebox-server-cuda.manifest (ordered list of part filenames)
"""
import argparse
import hashlib
import sys
from pathlib import Path
def split(input_path: Path, chunk_size: int, output_dir: Path):
output_dir.mkdir(parents=True, exist_ok=True)
data = input_path.read_bytes()
total_size = len(data)
# Write SHA-256 of the complete file
sha256 = hashlib.sha256(data).hexdigest()
checksum_file = output_dir / f"{input_path.stem}.sha256"
checksum_file.write_text(f"{sha256} {input_path.name}\n")
# Split into chunks
parts = []
for i in range(0, total_size, chunk_size):
part_index = len(parts)
part_name = f"{input_path.stem}.part{part_index:02d}{input_path.suffix}"
part_path = output_dir / part_name
part_path.write_bytes(data[i : i + chunk_size])
parts.append(part_name)
# Write manifest (ordered list of part filenames)
manifest_file = output_dir / f"{input_path.stem}.manifest"
manifest_file.write_text("\n".join(parts) + "\n")
print(f"Input: {input_path} ({total_size / (1024**3):.2f} GB)")
print(f"Output: {output_dir}/")
print(f"Parts: {len(parts)} (chunk size: {chunk_size / (1024**3):.2f} GB)")
print(f"SHA-256: {sha256}")
print(f"Manifest: {manifest_file.name}")
for p in parts:
size = (output_dir / p).stat().st_size
print(f" {p} ({size / (1024**3):.2f} GB)")
def main():
parser = argparse.ArgumentParser(
description="Split a large binary into chunks for GitHub Releases"
)
parser.add_argument("input", type=Path, help="Path to the binary file to split")
parser.add_argument(
"--chunk-size",
type=int,
default=1_900_000_000, # 1.9 GB — safely under 2 GB GitHub limit
help="Maximum chunk size in bytes (default: 1.9 GB)",
)
parser.add_argument(
"--output",
type=Path,
default=None,
help="Output directory (default: same directory as input)",
)
args = parser.parse_args()
if not args.input.exists():
print(f"Error: {args.input} does not exist", file=sys.stderr)
sys.exit(1)
output_dir = args.output or args.input.parent
split(args.input, args.chunk_size, output_dir)
if __name__ == "__main__":
main()