#!/usr/bin/env python """ Quick script to update your Hugging Face Space for phi-4-unsloth-bnb-4bit training. This script handles the specific requirements for the 4-bit quantized Phi-4 model training, including proper configuration and dependency management. """ import os import sys import json import subprocess import argparse import logging from pathlib import Path from huggingface_hub import HfApi, login import getpass # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", handlers=[logging.StreamHandler(sys.stdout)] ) logger = logging.getLogger(__name__) def load_env_variables(): """Load environment variables from system or .env file.""" # First try to load from local .env file try: from dotenv import load_dotenv env_path = Path(__file__).parent / ".env" if env_path.exists(): # Load and explicitly set environment variables with open(env_path) as f: for line in f: if line.strip() and not line.startswith('#'): key, value = line.strip().split('=', 1) os.environ[key] = value.strip() logger.info(f"Loaded environment variables from {env_path}") else: logger.warning(f"No .env file found at {env_path}") except ImportError: logger.warning("python-dotenv not installed, skipping .env loading") # Check if we're running in a Hugging Face Space if os.environ.get("SPACE_ID"): logger.info("Running in Hugging Face Space") if "/" in os.environ.get("SPACE_ID", ""): username = os.environ.get("SPACE_ID").split("/")[0] os.environ["HF_USERNAME"] = username logger.info(f"Set HF_USERNAME from SPACE_ID: {username}") # Verify required variables required_vars = { "HF_TOKEN": os.environ.get("HF_TOKEN"), "HF_USERNAME": os.environ.get("HF_USERNAME"), "HF_SPACE_NAME": os.environ.get("HF_SPACE_NAME", "phi4training") } # Ensure the space name is set correctly if "HF_SPACE_NAME" not in os.environ: os.environ["HF_SPACE_NAME"] = "phi4training" missing_vars = [k for k, v in required_vars.items() if not v] if missing_vars: raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}") logger.info(f"Using environment variables: USERNAME={required_vars['HF_USERNAME']}, SPACE_NAME={required_vars['HF_SPACE_NAME']}") return required_vars def verify_configs(): """Verify that all necessary configuration files exist and are valid.""" current_dir = Path(__file__).parent required_files = [ "transformers_config.json", "requirements.txt", "run_transformers_training.py" ] missing_files = [] for file in required_files: if not (current_dir / file).exists(): missing_files.append(file) if missing_files: raise FileNotFoundError(f"Missing required files: {', '.join(missing_files)}") # Verify JSON configs json_files = [f for f in required_files if f.endswith('.json')] for json_file in json_files: try: with open(current_dir / json_file) as f: json.load(f) logger.info(f"Verified {json_file} is valid JSON") except json.JSONDecodeError as e: raise ValueError(f"Invalid JSON in {json_file}: {e}") def update_requirements(): """Update requirements.txt with necessary packages using a two-stage installation process.""" logger.info("Setting up requirements files for sequential installation...") current_dir = Path(__file__).parent base_req_path = current_dir / "requirements-base.txt" main_req_path = current_dir / "requirements.txt" flash_req_path = current_dir / "requirements-flash.txt" # First ensure base requirements exist required_base_packages = { "torch>=2.0.0", "transformers>=4.36.0", "accelerate>=0.27.0", "bitsandbytes>=0.41.0", "tensorboard>=2.15.0", "gradio>=5.17.0", "huggingface-hub>=0.19.0", "datasets>=2.15.0" } # Additional packages for main requirements required_additional_packages = { "einops>=0.7.0", "filelock>=3.13.1", "matplotlib>=3.7.0", "numpy>=1.24.0", "packaging>=23.0", "peft>=0.9.0", "psutil>=5.9.0", "python-dotenv>=1.0.0", "pyyaml>=6.0.1", "regex>=2023.0.0", "requests>=2.31.0", "safetensors>=0.4.1", "sentencepiece>=0.1.99", "tqdm>=4.65.0", "typing-extensions>=4.8.0", "unsloth>=2024.3" } # Read existing base requirements existing_requirements = set() if base_req_path.exists(): with open(base_req_path) as f: existing_requirements = {line.strip() for line in f if line.strip() and not line.startswith('-r')} # Add new requirements updated_requirements = existing_requirements.union(required_base_packages) # 1. Write updated base requirements with open(base_req_path, 'w') as f: # Ensure torch is first torch_req = next((req for req in updated_requirements if req.startswith("torch")), "torch>=2.0.0") f.write(f"{torch_req}\n") # Write all other requirements (excluding torch) for req in sorted(r for r in updated_requirements if not r.startswith("torch")): f.write(f"{req}\n") # 2. Create main requirements file (references base) with open(main_req_path, 'w') as f: f.write("-r requirements-base.txt\n") for req in sorted(required_additional_packages): f.write(f"{req}\n") # 3. Create or update flash-attn requirements with open(flash_req_path, 'w') as f: f.write("-r requirements-base.txt\n") f.write("flash-attn==2.5.2\n") logger.info("Updated requirements files for sequential installation:") logger.info(f"1. Base requirements in {base_req_path}") logger.info(f"2. Main requirements in {main_req_path}") logger.info(f"3. Flash-attention requirements in {flash_req_path}") logger.info("This ensures packages are installed in the correct order") def create_space(username, space_name): """Create or get a Hugging Face Space.""" try: api = HfApi() space_id = f"{username}/{space_name}" logger.info(f"Checking Space {space_id}...") # First try to get the space try: space_info = api.space_info(repo_id=space_id) logger.info(f"Space {space_id} already exists") return space_info except Exception as e: logger.info(f"Space {space_id} does not exist, creating new space...") # Create new space try: api.create_repo( repo_id=space_id, private=False, repo_type="space", space_sdk="gradio" ) logger.info(f"Created new space: {space_id}") return api.space_info(repo_id=space_id) except Exception as e: logger.error(f"Failed to create space: {str(e)}") raise except Exception as e: raise RuntimeError(f"Error with Space {space_id}: {str(e)}") def main(): """Main function to update the Space.""" try: # Parse command line arguments parser = argparse.ArgumentParser(description='Update Hugging Face Space for Phi-4 training') parser.add_argument('--space_name', type=str, help='Space name (default: from env)') parser.add_argument('--force', action='store_true', help='Skip confirmation when updating Space') args = parser.parse_args() # Load environment variables env_vars = load_env_variables() verify_configs() # Verify we have the necessary variables if not all(k in env_vars and env_vars[k] for k in ["HF_TOKEN", "HF_USERNAME", "HF_SPACE_NAME"]): logger.error("Missing required environment variables. Please check your .env file.") logger.error(f"HF_TOKEN: {'Set' if 'HF_TOKEN' in env_vars and env_vars['HF_TOKEN'] else 'Not Set'}") logger.error(f"HF_USERNAME: {'Set' if 'HF_USERNAME' in env_vars and env_vars['HF_USERNAME'] else 'Not Set'}") logger.error(f"HF_SPACE_NAME: {'Set' if 'HF_SPACE_NAME' in env_vars and env_vars['HF_SPACE_NAME'] else 'Not Set'}") return False logger.info(f"Environment variables loaded: USERNAME={env_vars['HF_USERNAME']}, SPACE_NAME={env_vars['HF_SPACE_NAME']}") # Ask for confirmation unless forced if not args.force: print("\nWARNING: Updating the Space will INTERRUPT any ongoing training!") confirm = input("Are you sure you want to update the Space? Type 'yes' to confirm: ") if confirm.lower() != 'yes': logger.info("Update cancelled by user") return False # Additional password check for safety password = getpass.getpass("Enter your password to confirm update: ") if password.strip() == "": logger.info("No password entered. Update cancelled.") return False else: logger.info("Skipping confirmation due to --force flag") # Update requirements update_requirements() logger.info("Requirements updated successfully") # Get space name from args or env, prioritize args space_name = args.space_name if args.space_name else env_vars["HF_SPACE_NAME"] logger.info(f"Using space name: {space_name}") # Login to Hugging Face logger.info("Logging in to Hugging Face...") login(token=env_vars["HF_TOKEN"]) logger.info("Successfully logged in to Hugging Face") # Create/get space space_info = create_space(env_vars["HF_USERNAME"], space_name) logger.info(f"Space info: {space_info}") # Upload files current_dir = Path(__file__).parent logger.info(f"Uploading files from {current_dir} to Space {env_vars['HF_USERNAME']}/{space_name}...") # Create .gitignore with open(current_dir / ".gitignore", "w") as f: f.write(".env\n*.pyc\n__pycache__\n") logger.info("Created .gitignore file") api = HfApi() api.upload_folder( folder_path=str(current_dir), repo_id=f"{env_vars['HF_USERNAME']}/{space_name}", repo_type="space", ignore_patterns=[".env", "*.pyc", "__pycache__", "TRAINING_IN_PROGRESS.lock"] ) logger.info(f"Files uploaded successfully") space_url = f"https://huggingface.co/spaces/{env_vars['HF_USERNAME']}/{space_name}" logger.info(f"Space URL: {space_url}") print(f"\nSpace created successfully! You can view it at:\n{space_url}") return True except Exception as e: logger.error(f"Error updating Space: {str(e)}") return False if __name__ == "__main__": success = main() sys.exit(0 if success else 1)