Spaces:
Sleeping
Sleeping
| """ | |
| ACE-Step Model Downloader | |
| This module provides functionality to download models from HuggingFace Hub or ModelScope. | |
| It supports automatic downloading when models are not found locally, | |
| with intelligent fallback between download sources. | |
| """ | |
| import os | |
| import sys | |
| import argparse | |
| from typing import Optional, List, Dict, Tuple | |
| from pathlib import Path | |
| from loguru import logger | |
| # ============================================================================= | |
| # Network Detection & Smart Download | |
| # ============================================================================= | |
| def _can_access_google(timeout: float = 3.0) -> bool: | |
| """ | |
| Check if Google is accessible (to determine HuggingFace vs ModelScope). | |
| Args: | |
| timeout: Connection timeout in seconds | |
| Returns: | |
| True if Google is accessible, False otherwise | |
| """ | |
| import socket | |
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
| try: | |
| sock.settimeout(timeout) | |
| sock.connect(("www.google.com", 443)) | |
| return True | |
| except (socket.timeout, socket.error, OSError): | |
| return False | |
| finally: | |
| sock.close() | |
| def _download_from_huggingface_internal( | |
| repo_id: str, | |
| local_dir: Path, | |
| token: Optional[str] = None, | |
| ) -> None: | |
| """ | |
| Internal function to download from HuggingFace Hub. | |
| Args: | |
| repo_id: HuggingFace repository ID (e.g., "ACE-Step/Ace-Step1.5") | |
| local_dir: Local directory to save the model | |
| token: HuggingFace token for private repos (optional) | |
| Raises: | |
| Exception: If download fails | |
| """ | |
| from huggingface_hub import snapshot_download | |
| logger.info(f"[Model Download] Downloading from HuggingFace: {repo_id} -> {local_dir}") | |
| snapshot_download( | |
| repo_id=repo_id, | |
| local_dir=str(local_dir), | |
| local_dir_use_symlinks=False, | |
| token=token, | |
| ) | |
| def _download_from_modelscope_internal( | |
| repo_id: str, | |
| local_dir: Path, | |
| ) -> None: | |
| """ | |
| Internal function to download from ModelScope. | |
| Args: | |
| repo_id: ModelScope repository ID (e.g., "ACE-Step/Ace-Step1.5") | |
| local_dir: Local directory to save the model | |
| Raises: | |
| Exception: If download fails | |
| """ | |
| from modelscope import snapshot_download | |
| logger.info(f"[Model Download] Downloading from ModelScope: {repo_id} -> {local_dir}") | |
| snapshot_download( | |
| model_id=repo_id, | |
| local_dir=str(local_dir), | |
| ) | |
| def _smart_download( | |
| repo_id: str, | |
| local_dir: Path, | |
| token: Optional[str] = None, | |
| prefer_source: Optional[str] = None, | |
| ) -> Tuple[bool, str]: | |
| """ | |
| Smart download with automatic fallback between HuggingFace and ModelScope. | |
| Automatically detects network environment and chooses the best download source. | |
| If the primary source fails, automatically falls back to the alternative. | |
| Args: | |
| repo_id: Repository ID (same format for both HF and ModelScope) | |
| local_dir: Local directory to save the model | |
| token: HuggingFace token for private repos (optional) | |
| prefer_source: Preferred download source ("huggingface", "modelscope", or None for auto-detect) | |
| Returns: | |
| Tuple of (success, message) | |
| """ | |
| # Ensure directory exists | |
| local_dir.mkdir(parents=True, exist_ok=True) | |
| # Determine primary source | |
| if prefer_source == "huggingface": | |
| use_huggingface_first = True | |
| logger.info("[Model Download] User preference: HuggingFace Hub") | |
| elif prefer_source == "modelscope": | |
| use_huggingface_first = False | |
| logger.info("[Model Download] User preference: ModelScope") | |
| else: | |
| # Auto-detect network environment | |
| can_access_google = _can_access_google() | |
| use_huggingface_first = can_access_google | |
| logger.info(f"[Model Download] Auto-detected: {'HuggingFace Hub' if can_access_google else 'ModelScope'}") | |
| if use_huggingface_first: | |
| logger.info("[Model Download] Using HuggingFace Hub...") | |
| try: | |
| _download_from_huggingface_internal(repo_id, local_dir, token) | |
| return True, f"Successfully downloaded from HuggingFace: {repo_id}" | |
| except Exception as e: | |
| logger.warning(f"[Model Download] HuggingFace download failed: {e}") | |
| logger.info("[Model Download] Falling back to ModelScope...") | |
| try: | |
| _download_from_modelscope_internal(repo_id, local_dir) | |
| return True, f"Successfully downloaded from ModelScope: {repo_id}" | |
| except Exception as e2: | |
| error_msg = f"Both HuggingFace and ModelScope downloads failed. HF: {e}, MS: {e2}" | |
| logger.error(error_msg) | |
| return False, error_msg | |
| else: | |
| logger.info("[Model Download] Using ModelScope...") | |
| try: | |
| _download_from_modelscope_internal(repo_id, local_dir) | |
| return True, f"Successfully downloaded from ModelScope: {repo_id}" | |
| except Exception as e: | |
| logger.warning(f"[Model Download] ModelScope download failed: {e}") | |
| logger.info("[Model Download] Falling back to HuggingFace Hub...") | |
| try: | |
| _download_from_huggingface_internal(repo_id, local_dir, token) | |
| return True, f"Successfully downloaded from HuggingFace: {repo_id}" | |
| except Exception as e2: | |
| error_msg = f"Both ModelScope and HuggingFace downloads failed. MS: {e}, HF: {e2}" | |
| logger.error(error_msg) | |
| return False, error_msg | |
| # ============================================================================= | |
| # Model Registry | |
| # ============================================================================= | |
| # Main model contains core components (vae, text_encoder, default DiT) | |
| MAIN_MODEL_REPO = "ACE-Step/Ace-Step1.5" | |
| # Sub-models that can be downloaded separately into the checkpoints directory | |
| SUBMODEL_REGISTRY: Dict[str, str] = { | |
| # LM models | |
| "acestep-5Hz-lm-0.6B": "ACE-Step/acestep-5Hz-lm-0.6B", | |
| "acestep-5Hz-lm-4B": "ACE-Step/acestep-5Hz-lm-4B", | |
| # DiT models | |
| "acestep-v15-turbo-shift3": "ACE-Step/acestep-v15-turbo-shift3", | |
| "acestep-v15-sft": "ACE-Step/acestep-v15-sft", | |
| "acestep-v15-base": "ACE-Step/acestep-v15-base", | |
| "acestep-v15-turbo-shift1": "ACE-Step/acestep-v15-turbo-shift1", | |
| "acestep-v15-turbo-continuous": "ACE-Step/acestep-v15-turbo-continuous", | |
| } | |
| # Components that come from the main model repo (ACE-Step/Ace-Step1.5) | |
| MAIN_MODEL_COMPONENTS = [ | |
| "acestep-v15-turbo", # Default DiT model | |
| "vae", # VAE for audio encoding/decoding | |
| "Qwen3-Embedding-0.6B", # Text encoder | |
| "acestep-5Hz-lm-1.7B", # Default LM model (1.7B) | |
| ] | |
| # Default LM model (included in main model) | |
| DEFAULT_LM_MODEL = "acestep-5Hz-lm-1.7B" | |
| def get_project_root() -> Path: | |
| """Get the project root directory.""" | |
| current_file = Path(__file__).resolve() | |
| return current_file.parent.parent | |
| def get_checkpoints_dir(custom_dir: Optional[str] = None) -> Path: | |
| """Get the checkpoints directory path.""" | |
| if custom_dir: | |
| return Path(custom_dir) | |
| return get_project_root() / "checkpoints" | |
| def check_main_model_exists(checkpoints_dir: Optional[Path] = None) -> bool: | |
| """ | |
| Check if the main model components exist in the checkpoints directory. | |
| Returns: | |
| True if all main model components exist, False otherwise. | |
| """ | |
| if checkpoints_dir is None: | |
| checkpoints_dir = get_checkpoints_dir() | |
| for component in MAIN_MODEL_COMPONENTS: | |
| component_path = checkpoints_dir / component | |
| if not component_path.exists(): | |
| return False | |
| return True | |
| def check_model_exists(model_name: str, checkpoints_dir: Optional[Path] = None) -> bool: | |
| """ | |
| Check if a specific model exists in the checkpoints directory. | |
| Args: | |
| model_name: Name of the model to check | |
| checkpoints_dir: Custom checkpoints directory (optional) | |
| Returns: | |
| True if the model exists, False otherwise. | |
| """ | |
| if checkpoints_dir is None: | |
| checkpoints_dir = get_checkpoints_dir() | |
| model_path = checkpoints_dir / model_name | |
| return model_path.exists() | |
| def list_available_models() -> Dict[str, str]: | |
| """ | |
| List all available models for download. | |
| Returns: | |
| Dictionary mapping local names to HuggingFace repo IDs. | |
| """ | |
| models = { | |
| "main": MAIN_MODEL_REPO, | |
| **SUBMODEL_REGISTRY | |
| } | |
| return models | |
| def download_main_model( | |
| checkpoints_dir: Optional[Path] = None, | |
| force: bool = False, | |
| token: Optional[str] = None, | |
| prefer_source: Optional[str] = None, | |
| ) -> Tuple[bool, str]: | |
| """ | |
| Download the main ACE-Step model from HuggingFace or ModelScope. | |
| The main model includes: | |
| - acestep-v15-turbo (default DiT model) | |
| - vae (audio encoder/decoder) | |
| - Qwen3-Embedding-0.6B (text encoder) | |
| - acestep-5Hz-lm-1.7B (default LM model) | |
| Args: | |
| checkpoints_dir: Custom checkpoints directory (optional) | |
| force: Force re-download even if model exists | |
| token: HuggingFace token for private repos (optional) | |
| prefer_source: Preferred download source ("huggingface", "modelscope", or None for auto-detect) | |
| Returns: | |
| Tuple of (success, message) | |
| """ | |
| if checkpoints_dir is None: | |
| checkpoints_dir = get_checkpoints_dir() | |
| # Ensure checkpoints directory exists | |
| checkpoints_dir.mkdir(parents=True, exist_ok=True) | |
| if not force and check_main_model_exists(checkpoints_dir): | |
| return True, f"Main model already exists at {checkpoints_dir}" | |
| print(f"Downloading main model from {MAIN_MODEL_REPO}...") | |
| print(f"Destination: {checkpoints_dir}") | |
| print("This may take a while depending on your internet connection...") | |
| # Use smart download with automatic fallback | |
| return _smart_download(MAIN_MODEL_REPO, checkpoints_dir, token, prefer_source) | |
| def download_submodel( | |
| model_name: str, | |
| checkpoints_dir: Optional[Path] = None, | |
| force: bool = False, | |
| token: Optional[str] = None, | |
| prefer_source: Optional[str] = None, | |
| ) -> Tuple[bool, str]: | |
| """ | |
| Download a specific sub-model from HuggingFace or ModelScope. | |
| Args: | |
| model_name: Name of the model to download (must be in SUBMODEL_REGISTRY) | |
| checkpoints_dir: Custom checkpoints directory (optional) | |
| force: Force re-download even if model exists | |
| token: HuggingFace token for private repos (optional) | |
| prefer_source: Preferred download source ("huggingface", "modelscope", or None for auto-detect) | |
| Returns: | |
| Tuple of (success, message) | |
| """ | |
| if model_name not in SUBMODEL_REGISTRY: | |
| available = ", ".join(SUBMODEL_REGISTRY.keys()) | |
| return False, f"Unknown model '{model_name}'. Available models: {available}" | |
| if checkpoints_dir is None: | |
| checkpoints_dir = get_checkpoints_dir() | |
| # Ensure checkpoints directory exists | |
| checkpoints_dir.mkdir(parents=True, exist_ok=True) | |
| model_path = checkpoints_dir / model_name | |
| if not force and model_path.exists(): | |
| return True, f"Model '{model_name}' already exists at {model_path}" | |
| repo_id = SUBMODEL_REGISTRY[model_name] | |
| print(f"Downloading {model_name} from {repo_id}...") | |
| print(f"Destination: {model_path}") | |
| # Use smart download with automatic fallback | |
| return _smart_download(repo_id, model_path, token, prefer_source) | |
| def download_all_models( | |
| checkpoints_dir: Optional[Path] = None, | |
| force: bool = False, | |
| token: Optional[str] = None, | |
| ) -> Tuple[bool, List[str]]: | |
| """ | |
| Download all available models. | |
| Args: | |
| checkpoints_dir: Custom checkpoints directory (optional) | |
| force: Force re-download even if models exist | |
| token: HuggingFace token for private repos (optional) | |
| Returns: | |
| Tuple of (all_success, list of messages) | |
| """ | |
| if checkpoints_dir is None: | |
| checkpoints_dir = get_checkpoints_dir() | |
| messages = [] | |
| all_success = True | |
| # Download main model first | |
| success, msg = download_main_model(checkpoints_dir, force, token) | |
| messages.append(msg) | |
| if not success: | |
| all_success = False | |
| # Download all sub-models | |
| for model_name in SUBMODEL_REGISTRY: | |
| success, msg = download_submodel(model_name, checkpoints_dir, force, token) | |
| messages.append(msg) | |
| if not success: | |
| all_success = False | |
| return all_success, messages | |
| def ensure_main_model( | |
| checkpoints_dir: Optional[Path] = None, | |
| token: Optional[str] = None, | |
| prefer_source: Optional[str] = None, | |
| ) -> Tuple[bool, str]: | |
| """ | |
| Ensure the main model is available, downloading if necessary. | |
| This function is designed to be called during initialization. | |
| It will only download if the model doesn't exist. | |
| Args: | |
| checkpoints_dir: Custom checkpoints directory (optional) | |
| token: HuggingFace token for private repos (optional) | |
| prefer_source: Preferred download source ("huggingface", "modelscope", or None for auto-detect) | |
| Returns: | |
| Tuple of (success, message) | |
| """ | |
| if checkpoints_dir is None: | |
| checkpoints_dir = get_checkpoints_dir() | |
| if check_main_model_exists(checkpoints_dir): | |
| return True, "Main model is available" | |
| print("\n" + "=" * 60) | |
| print("Main model not found. Starting automatic download...") | |
| print("=" * 60 + "\n") | |
| return download_main_model(checkpoints_dir, token=token, prefer_source=prefer_source) | |
| def ensure_lm_model( | |
| model_name: Optional[str] = None, | |
| checkpoints_dir: Optional[Path] = None, | |
| token: Optional[str] = None, | |
| prefer_source: Optional[str] = None, | |
| ) -> Tuple[bool, str]: | |
| """ | |
| Ensure an LM model is available, downloading if necessary. | |
| Args: | |
| model_name: Name of the LM model (defaults to DEFAULT_LM_MODEL) | |
| checkpoints_dir: Custom checkpoints directory (optional) | |
| token: HuggingFace token for private repos (optional) | |
| prefer_source: Preferred download source ("huggingface", "modelscope", or None for auto-detect) | |
| Returns: | |
| Tuple of (success, message) | |
| """ | |
| if model_name is None: | |
| model_name = DEFAULT_LM_MODEL | |
| if checkpoints_dir is None: | |
| checkpoints_dir = get_checkpoints_dir() | |
| if check_model_exists(model_name, checkpoints_dir): | |
| return True, f"LM model '{model_name}' is available" | |
| # Check if this is a known LM model | |
| if model_name not in SUBMODEL_REGISTRY: | |
| # Check if it might be a variant name | |
| for known_model in SUBMODEL_REGISTRY: | |
| if "lm" in known_model.lower() and model_name.lower() in known_model.lower(): | |
| model_name = known_model | |
| break | |
| else: | |
| return False, f"Unknown LM model: {model_name}" | |
| print("\n" + "=" * 60) | |
| print(f"LM model '{model_name}' not found. Starting automatic download...") | |
| print("=" * 60 + "\n") | |
| return download_submodel(model_name, checkpoints_dir, token=token, prefer_source=prefer_source) | |
| def ensure_dit_model( | |
| model_name: str, | |
| checkpoints_dir: Optional[Path] = None, | |
| token: Optional[str] = None, | |
| prefer_source: Optional[str] = None, | |
| ) -> Tuple[bool, str]: | |
| """ | |
| Ensure a DiT model is available, downloading if necessary. | |
| Args: | |
| model_name: Name of the DiT model | |
| checkpoints_dir: Custom checkpoints directory (optional) | |
| token: HuggingFace token for private repos (optional) | |
| prefer_source: Preferred download source ("huggingface", "modelscope", or None for auto-detect) | |
| Returns: | |
| Tuple of (success, message) | |
| """ | |
| if checkpoints_dir is None: | |
| checkpoints_dir = get_checkpoints_dir() | |
| if check_model_exists(model_name, checkpoints_dir): | |
| return True, f"DiT model '{model_name}' is available" | |
| # Check if this is the default turbo model (part of main) | |
| if model_name == "acestep-v15-turbo": | |
| return ensure_main_model(checkpoints_dir, token, prefer_source) | |
| # Check if it's a known sub-model | |
| if model_name in SUBMODEL_REGISTRY: | |
| print("\n" + "=" * 60) | |
| print(f"DiT model '{model_name}' not found. Starting automatic download...") | |
| print("=" * 60 + "\n") | |
| return download_submodel(model_name, checkpoints_dir, token=token, prefer_source=prefer_source) | |
| return False, f"Unknown DiT model: {model_name}" | |
| def print_model_list(): | |
| """Print formatted list of available models.""" | |
| print("\nAvailable Models for Download:") | |
| print("=" * 60) | |
| print("\nSupported Sources: HuggingFace Hub <-> ModelScope (auto-fallback)") | |
| print("\n[Main Model]") | |
| print(f" main -> {MAIN_MODEL_REPO}") | |
| print(" Contains: vae, Qwen3-Embedding-0.6B, acestep-v15-turbo, acestep-5Hz-lm-1.7B") | |
| print("\n[Optional LM Models]") | |
| for name, repo in SUBMODEL_REGISTRY.items(): | |
| if "lm" in name.lower(): | |
| print(f" {name} -> {repo}") | |
| print("\n[Optional DiT Models]") | |
| for name, repo in SUBMODEL_REGISTRY.items(): | |
| if "lm" not in name.lower(): | |
| print(f" {name} -> {repo}") | |
| print("\n" + "=" * 60) | |
| def main(): | |
| """CLI entry point for model downloading.""" | |
| parser = argparse.ArgumentParser( | |
| description="Download ACE-Step models with automatic fallback (HuggingFace <-> ModelScope)", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=""" | |
| Examples: | |
| acestep-download # Download main model (includes LM 1.7B) | |
| acestep-download --all # Download all available models | |
| acestep-download --model acestep-v15-sft # Download a specific model | |
| acestep-download --list # List all available models | |
| Network Detection: | |
| Automatically detects network environment and chooses the best download source: | |
| - Google accessible -> HuggingFace (fallback to ModelScope) | |
| - Google blocked -> ModelScope (fallback to HuggingFace) | |
| Alternative using huggingface-cli: | |
| huggingface-cli download ACE-Step/Ace-Step1.5 --local-dir ./checkpoints | |
| huggingface-cli download ACE-Step/acestep-5Hz-lm-0.6B --local-dir ./checkpoints/acestep-5Hz-lm-0.6B | |
| """ | |
| ) | |
| parser.add_argument( | |
| "--model", "-m", | |
| type=str, | |
| help="Specific model to download (use --list to see available models)" | |
| ) | |
| parser.add_argument( | |
| "--all", "-a", | |
| action="store_true", | |
| help="Download all available models" | |
| ) | |
| parser.add_argument( | |
| "--list", "-l", | |
| action="store_true", | |
| help="List all available models" | |
| ) | |
| parser.add_argument( | |
| "--dir", "-d", | |
| type=str, | |
| default=None, | |
| help="Custom checkpoints directory (default: ./checkpoints)" | |
| ) | |
| parser.add_argument( | |
| "--force", "-f", | |
| action="store_true", | |
| help="Force re-download even if model exists" | |
| ) | |
| parser.add_argument( | |
| "--token", "-t", | |
| type=str, | |
| default=None, | |
| help="HuggingFace token for private repos" | |
| ) | |
| parser.add_argument( | |
| "--skip-main", | |
| action="store_true", | |
| help="Skip downloading the main model (only download specified sub-model)" | |
| ) | |
| args = parser.parse_args() | |
| # Handle --list | |
| if args.list: | |
| print_model_list() | |
| return 0 | |
| # Get checkpoints directory | |
| checkpoints_dir = get_checkpoints_dir(args.dir) if args.dir else get_checkpoints_dir() | |
| print(f"Checkpoints directory: {checkpoints_dir}") | |
| # Handle --all | |
| if args.all: | |
| success, messages = download_all_models(checkpoints_dir, args.force, args.token) | |
| for msg in messages: | |
| print(msg) | |
| return 0 if success else 1 | |
| # Handle --model | |
| if args.model: | |
| if args.model == "main": | |
| success, msg = download_main_model(checkpoints_dir, args.force, args.token) | |
| elif args.model in SUBMODEL_REGISTRY: | |
| # Download main model first if needed (unless --skip-main) | |
| if not args.skip_main and not check_main_model_exists(checkpoints_dir): | |
| print("Main model not found. Downloading main model first...") | |
| main_success, main_msg = download_main_model(checkpoints_dir, args.force, args.token) | |
| print(main_msg) | |
| if not main_success: | |
| return 1 | |
| success, msg = download_submodel(args.model, checkpoints_dir, args.force, args.token) | |
| else: | |
| print(f"Unknown model: {args.model}") | |
| print("Use --list to see available models") | |
| return 1 | |
| print(msg) | |
| return 0 if success else 1 | |
| # Default: download main model (includes default LM 1.7B) | |
| print("Downloading main model (includes vae, text encoder, DiT, and LM 1.7B)...") | |
| # Download main model | |
| success, msg = download_main_model(checkpoints_dir, args.force, args.token) | |
| print(msg) | |
| if success: | |
| print("\nDownload complete!") | |
| print(f"Models are available at: {checkpoints_dir}") | |
| return 0 if success else 1 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |