Files
wehub-resource-sync c8a779b1bb
Docker Image CI / build-ubuntu2004 (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:55 +08:00

579 lines
18 KiB
Python

#!/usr/bin/env python3
#
# SPDX-FileCopyrightText: Copyright (c) 1993-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
Setup script for TensorRT Diffusion dependencies.
Usage:
python setup.py [GROUP] [OPTIONS]
Groups:
sd = SD 1.4, SDXL, SD3, SD3.5, SVD, Stable Cascade (Stability AI)
flux = Flux models (Black Forest Labs)
cosmos = Cosmos models (NVIDIA), Wan2.2 T2V
all = Install all groups (default)
Options:
--skip-tensorrt Skip TensorRT upgrade/installation
--force Force reinstallation even if already installed
--deps-root DIR Root directory for dependencies
-q, --quiet Suppress informational output (errors are always shown)
Examples:
python setup.py # Install all
python setup.py sd # Install SD family only
python setup.py flux # Install Flux only
python setup.py cosmos # Install Cosmos only
python setup.py --skip-tensorrt # Install all, skip TensorRT upgrade
python setup.py flux --skip-tensorrt # Install Flux only, skip TensorRT upgrade
python setup.py all --quiet # Install all, minimal output
"""
import argparse
import os
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
# Global quiet flag (set by parse_args)
_quiet = False
def log(msg: str = ""):
"""Print a message unless in quiet mode."""
if not _quiet:
print(msg)
# Group descriptions
GROUP_DESCRIPTIONS = {
"sd": "SD family (SD 1.4, SDXL, SD3, SD3.5, SVD, Stable Cascade)",
"flux": "Flux family (Black Forest Labs)",
"cosmos": "Cosmos family (NVIDIA), Wan2.2 T2V",
}
# Valid dependency groups
VALID_GROUPS = set(GROUP_DESCRIPTIONS.keys())
# Default installation root
# Can be overridden by:
# 1. Environment variable: TENSORRT_DIFFUSION_DEPS_ROOT
# 2. Command-line argument: --deps-root
DEFAULT_DEPS_ROOT = os.environ.get("TENSORRT_DIFFUSION_DEPS_ROOT", "/workspace/deps")
# Marker file to indicate successful installation
# This file is created after all packages are successfully installed
# Prevents treating incomplete installations as complete
INSTALL_COMPLETE_MARKER = ".install_complete"
def _normalize_package_name(name: str) -> str:
"""Normalize a package name per PEP 503 (e.g. 'Nvidia_ModelOpt' -> 'nvidia-modelopt')."""
return re.sub(r"[-_.]+", "-", name).lower()
# Regex to extract the package name from a PEP 508 requirement string.
# Matches the leading identifier before any extras, version specifier, or URL marker.
# "nvidia-modelopt[torch,onnx]==0.40.0" -> "nvidia-modelopt"
# "flux @ git+https://..." -> "flux"
_REQ_NAME_RE = re.compile(r"^([A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?)")
def get_pyproject_package_names(pyproject_path: str, group: str) -> set[str]:
"""Return normalized names of packages explicitly listed in pyproject.toml."""
try:
import tomllib
except ModuleNotFoundError:
try:
import tomli as tomllib
except ModuleNotFoundError:
return set()
with open(pyproject_path, "rb") as f:
data = tomllib.load(f)
project = data.get("project", {})
reqs = list(project.get("dependencies", []))
reqs.extend(project.get("optional-dependencies", {}).get(group, []))
names = set()
for req in reqs:
match = _REQ_NAME_RE.match(req.strip())
if match:
names.add(_normalize_package_name(match.group(1)))
return names
def get_container_provided_packages() -> list[str]:
"""Discover torch/NVIDIA packages already installed in the container."""
from importlib.metadata import distributions
prefixes = ("torch", "torchvision", "triton", "nvidia-")
return sorted({
dist.metadata["Name"].lower()
for dist in distributions()
if dist.metadata["Name"].lower().startswith(prefixes)
})
def print_header():
"""Print setup header."""
log("=" * 60)
log(" TensorRT Diffusion - Dependency Setup")
log("=" * 60)
def check_uv_installed() -> tuple[bool, bool]:
"""
Check if uv is installed and accessible.
Returns:
tuple: (is_installed, needs_path_setup)
- is_installed: True if uv is available
- needs_path_setup: True if user needs to add ~/.local/bin to PATH permanently
"""
# First check if uv is in PATH
try:
subprocess.run(
["uv", "--version"],
capture_output=True,
check=True,
text=True
)
return True, False
except (subprocess.CalledProcessError, FileNotFoundError):
# If not in PATH, check the default installation location
local_bin = os.path.expanduser("~/.local/bin")
uv_path = os.path.join(local_bin, "uv")
if os.path.exists(uv_path) and os.access(uv_path, os.X_OK):
# uv exists but not in PATH - add it temporarily for this script
if local_bin not in os.environ["PATH"]:
os.environ["PATH"] = f"{local_bin}:{os.environ['PATH']}"
return True, True # Needs permanent PATH setup
return False, False
def upgrade_tensorrt(pip_spec: str) -> bool:
"""Upgrade/install TensorRT using pip.
Args:
pip_spec: PIP specifier for TensorRT, e.g. "tensorrt-cu12" or "tensorrt-cu12==10.16.*"
Returns:
True on success, False otherwise
"""
print("Installing TensorRT: {}".format(pip_spec))
try:
subprocess.run(
[sys.executable, "-m", "pip", "install", "--upgrade", "--pre", pip_spec],
check=True,
capture_output=_quiet,
)
# Print installed version for confirmation
try:
import importlib
trt = importlib.import_module("tensorrt")
log(f" Installed TensorRT version: {getattr(trt, '__version__', 'unknown')}")
except Exception:
pass
return True
except subprocess.CalledProcessError as e:
print(f"Failed to upgrade/install TensorRT: {e}")
return False
def install_libgl1() -> bool:
"""Install libgl1 system package when possible (Debian/Ubuntu).
Returns:
True if successfully installed or already present, False otherwise.
"""
print("Checking for libgl1 (system dependency)...")
apt_get = shutil.which("apt-get")
if not apt_get:
log(" Skipping: apt-get not found. Please install 'libgl1' via your OS package manager.")
return False
sudo = shutil.which("sudo")
use_sudo = False
try:
use_sudo = (hasattr(os, "geteuid") and os.geteuid() != 0 and sudo is not None)
except Exception:
use_sudo = sudo is not None
try:
# Update package list first
cmd_update = ([sudo] if use_sudo else []) + [apt_get, "update"]
subprocess.run(cmd_update, check=True, capture_output=_quiet)
cmd_install = ([sudo] if use_sudo else []) + [apt_get, "install", "-y", "libgl1"]
subprocess.run(cmd_install, check=True, capture_output=_quiet)
log(" libgl1 installed (or already up to date)")
return True
except subprocess.CalledProcessError as e:
print(f" Warning: Failed to install libgl1: {e}")
print(" Please install 'libgl1' manually via your OS package manager.")
return False
def install_uv():
"""Install uv package manager."""
log("Installing uv...")
try:
# Download and run uv installer
curl_cmd = [
"curl", "-LsSf",
"https://astral.sh/uv/install.sh"
]
curl_process = subprocess.Popen(
curl_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
sh_process = subprocess.Popen(
["sh"],
stdin=curl_process.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
curl_process.stdout.close()
stdout, stderr = sh_process.communicate()
if sh_process.returncode != 0:
print(f"Failed to install uv: {stderr.decode()}")
sys.exit(1)
# Add to PATH for this session
# uv installer uses ~/.local/bin by default
local_bin = os.path.expanduser("~/.local/bin")
if local_bin not in os.environ["PATH"]:
os.environ["PATH"] = f"{local_bin}:{os.environ['PATH']}"
log("uv installed successfully")
except Exception as e:
print(f"Error installing uv: {e}")
print("Please install manually: curl -LsSf https://astral.sh/uv/install.sh | sh")
sys.exit(1)
def get_installed_groups(deps_root: str) -> set[str]:
"""
Get set of already installed dependency groups.
A group is considered installed only if both:
1. The group directory exists
2. The .install_complete marker file exists in that directory
Args:
deps_root: Root directory where dependencies are installed
Returns:
Set of installed group names
"""
installed = set()
deps_path = Path(deps_root)
if not deps_path.exists():
return installed
for group in VALID_GROUPS:
group_dir = deps_path / group
marker_file = group_dir / INSTALL_COMPLETE_MARKER
# Only consider it installed if marker file exists
if group_dir.exists() and marker_file.exists():
installed.add(group)
return installed
def install_group(group: str, deps_root: str, project_root: str) -> bool:
"""
Install dependencies for a specific group.
Args:
group: Dependency group name
deps_root: Root directory where dependencies will be installed
project_root: Project root directory (where pyproject.toml is)
Returns:
True if installation succeeded, False otherwise
"""
description = GROUP_DESCRIPTIONS.get(group, group)
install_path = os.path.join(deps_root, group)
print(f"Installing {description}...")
log(f" Location: {install_path}")
try:
# Determine Python version and site-packages path
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
site_packages = Path(install_path) / "lib" / f"python{python_version}" / "site-packages"
# Create site-packages directory if it doesn't exist
site_packages.mkdir(parents=True, exist_ok=True)
# Build an overrides file so uv never installs packages that are
# already installed in the container. Exclude packages explicitly
# listed in pyproject.toml so they get installed at the pinned version.
container_pkgs = get_container_provided_packages()
declared_pkgs = get_pyproject_package_names(
str(Path(project_root) / "pyproject.toml"), group
)
overrides_content = "\n".join(
f'{pkg} ; python_version < "0"'
for pkg in container_pkgs
if _normalize_package_name(pkg) not in declared_pkgs
)
with tempfile.NamedTemporaryFile(
mode="w", suffix=".txt", prefix="uv_overrides_", delete=False
) as f:
f.write(overrides_content)
overrides_path = f.name
try:
cmd = [
"uv", "pip", "install",
"--python-preference", "only-system",
"--prefix", install_path,
"--overrides", overrides_path,
f".[{group}]"
]
subprocess.run(
cmd,
cwd=project_root,
text=True,
capture_output=_quiet,
check=True
)
finally:
os.unlink(overrides_path)
# Create marker file to indicate successful installation
marker_file = Path(install_path) / INSTALL_COMPLETE_MARKER
marker_file.write_text("Installation completed successfully\n")
print(f" {description} installed successfully")
return True
except subprocess.CalledProcessError as e:
print(f"Failed to install {description}")
print(f" Error: {e}")
# Clean up incomplete installation
if os.path.exists(install_path):
log(f" Cleaning up incomplete installation at {install_path}")
try:
shutil.rmtree(install_path)
except Exception as cleanup_error:
print(f" Warning: Failed to clean up: {cleanup_error}")
return False
def determine_groups_to_install(requested: str, already_installed: set[str]) -> list[str]:
"""
Determine which groups need to be installed.
Args:
requested: Requested group or "all"
already_installed: Set of already installed groups
Returns:
List of groups to install
"""
if requested == "all":
return sorted(VALID_GROUPS - already_installed)
elif requested in VALID_GROUPS:
return [] if requested in already_installed else [requested]
return []
def print_summary(installed_groups: set[str]):
"""
Print summary of installed groups.
Args:
installed_groups: Set of all installed groups
"""
log("=" * 60)
print("Setup complete!")
log("=" * 60)
if installed_groups:
log("Installed groups:")
for group in sorted(installed_groups):
description = GROUP_DESCRIPTIONS.get(group, group)
log(f" {description}")
log("Each demo script automatically uses the correct dependencies.")
else:
log("No groups installed.")
log()
def parse_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description="Setup TensorRT Diffusion dependencies",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python setup.py # Install all groups
python setup.py sd # Install SD family only
python setup.py flux # Install Flux only
python setup.py cosmos # Install Cosmos only
Groups:
sd - SD 1.4, SDXL, SD3, SD3.5, SVD, Stable Cascade (Stability AI)
flux - Flux models (Black Forest Labs)
cosmos - Cosmos models (NVIDIA)
all - All groups (default)
"""
)
parser.add_argument(
"group",
nargs="?",
default="all",
choices=list(VALID_GROUPS) + ["all"],
help="Dependency group to install (default: all)"
)
parser.add_argument(
"--deps-root",
default=DEFAULT_DEPS_ROOT,
help=f"Root directory for dependencies (default: {DEFAULT_DEPS_ROOT})"
)
parser.add_argument(
"--force",
action="store_true",
help="Force reinstallation even if already installed"
)
parser.add_argument("--skip-tensorrt", action="store_true", help="Skip TensorRT upgrade/installation")
parser.add_argument("-q", "--quiet", action="store_true", help="Suppress informational output (errors are always shown)")
return parser.parse_args()
def main():
"""Main setup function."""
args = parse_args()
global _quiet
_quiet = args.quiet
print_header()
# Check/install uv
uv_installed, needs_path_setup = check_uv_installed()
if not uv_installed:
install_uv()
needs_path_setup = True # Fresh install always needs PATH setup
else:
log("uv is already installed")
if needs_path_setup:
log(" (temporarily added ~/.local/bin to PATH for this session)")
# Get project root (where pyproject.toml is)
project_root = str(Path(__file__).parent.absolute())
pyproject_path = Path(project_root) / "pyproject.toml"
if not pyproject_path.exists():
print(f"Error: pyproject.toml not found in {project_root}")
print(" Make sure you're running this script from the project root.")
sys.exit(1)
# Check what's already installed
installed_groups = get_installed_groups(args.deps_root)
# Install system dependency (best-effort) and ensure TensorRT is present
install_libgl1()
if not args.skip_tensorrt:
upgrade_tensorrt("tensorrt-cu12")
else:
log("Skipping TensorRT upgrade (--skip-tensorrt specified)")
if installed_groups and not args.force:
log(f"Already installed: {', '.join(sorted(installed_groups))}")
# Determine what to install
if args.force and args.group == "all":
groups_to_install = sorted(VALID_GROUPS)
elif args.force and args.group in VALID_GROUPS:
groups_to_install = [args.group]
else:
groups_to_install = determine_groups_to_install(args.group, installed_groups)
# Early exit if nothing to do
if not groups_to_install:
if args.group == "all":
print("All requested dependencies are already installed!")
else:
description = GROUP_DESCRIPTIONS.get(args.group, args.group)
print(f"{description} is already installed!")
log(f" To reinstall, use --force flag or remove {args.deps_root}/{args.group}")
print_summary(installed_groups)
sys.exit(0)
# Install groups
print(f"Installing {len(groups_to_install)} group(s): {', '.join(groups_to_install)}")
success_count = 0
for group in groups_to_install:
if install_group(group, args.deps_root, project_root):
installed_groups.add(group)
success_count += 1
# Print summary
print_summary(installed_groups)
# Print PATH setup instructions if needed
if needs_path_setup:
print()
print("=" * 60)
print(" Action Required: Update PATH")
print("=" * 60)
print("uv was installed to ~/.local/bin but is not in your PATH.")
print()
print("To make uv available in future sessions, run:")
print(" source ~/.local/bin/env")
print()
print("Or restart your shell.")
print("=" * 60)
# Exit with error if any installations failed
if success_count < len(groups_to_install):
print(f"Warning: {len(groups_to_install) - success_count} group(s) failed to install")
sys.exit(1)
if __name__ == "__main__":
main()