Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Demo Space Deployment Script | |
| Deploys a Gradio demo space to Hugging Face Spaces for testing the fine-tuned model. | |
| """ | |
| import os | |
| import sys | |
| import json | |
| import logging | |
| import argparse | |
| import subprocess | |
| import requests | |
| import tempfile | |
| import shutil | |
| from pathlib import Path | |
| from typing import Optional, Dict, Any | |
| import time | |
| # Import Hugging Face Hub API | |
| try: | |
| from huggingface_hub import HfApi, create_repo, upload_file | |
| HF_HUB_AVAILABLE = True | |
| except ImportError: | |
| HF_HUB_AVAILABLE = False | |
| print("Warning: huggingface_hub not available. Install with: pip install huggingface_hub") | |
| # Add src to path for imports | |
| sys.path.append(str(Path(__file__).parent.parent / "src")) | |
| from config import SmolLM3Config | |
| # Setup logging | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| class DemoSpaceDeployer: | |
| """Deploy demo space to Hugging Face Spaces""" | |
| def __init__(self, hf_token: str, hf_username: str, model_id: str, | |
| subfolder: str = "int4", space_name: Optional[str] = None): | |
| self.hf_token = hf_token | |
| self.hf_username = hf_username | |
| self.model_id = model_id | |
| self.subfolder = subfolder | |
| self.space_name = space_name or f"{model_id.split('/')[-1]}-demo" | |
| self.space_id = f"{hf_username}/{self.space_name}" | |
| self.space_url = f"https://huggingface.co/spaces/{self.space_id}" | |
| # Template paths | |
| self.template_dir = Path(__file__).parent.parent / "templates" / "spaces" / "demo" | |
| self.workspace_dir = Path.cwd() | |
| # Initialize HF API | |
| if HF_HUB_AVAILABLE: | |
| self.api = HfApi(token=self.hf_token) | |
| else: | |
| self.api = None | |
| logger.warning("huggingface_hub not available, using CLI fallback") | |
| def validate_model_exists(self) -> bool: | |
| """Validate that the model exists on Hugging Face Hub""" | |
| try: | |
| logger.info(f"Validating model: {self.model_id}") | |
| if HF_HUB_AVAILABLE: | |
| # Use HF Hub API | |
| try: | |
| model_info = self.api.model_info(self.model_id) | |
| logger.info(f"β Model {self.model_id} exists and is accessible") | |
| return True | |
| except Exception as e: | |
| logger.error(f"β Model {self.model_id} not found via API: {e}") | |
| return False | |
| else: | |
| # Fallback to requests | |
| url = f"https://huggingface.co/api/models/{self.model_id}" | |
| headers = {"Authorization": f"Bearer {self.hf_token}"} | |
| response = requests.get(url, headers=headers, timeout=30) | |
| if response.status_code == 200: | |
| logger.info(f"β Model {self.model_id} exists and is accessible") | |
| return True | |
| else: | |
| logger.error(f"β Model {self.model_id} not found or not accessible") | |
| return False | |
| except Exception as e: | |
| logger.error(f"β Error validating model: {e}") | |
| return False | |
| def create_space_repository(self) -> bool: | |
| """Create the space repository on Hugging Face Hub""" | |
| try: | |
| logger.info(f"Creating Space: {self.space_name}") | |
| if not HF_HUB_AVAILABLE: | |
| logger.warning("huggingface_hub not available, falling back to CLI") | |
| return self._create_space_cli() | |
| # Use the latest HF Hub API to create space | |
| try: | |
| # Create the space using the API | |
| create_repo( | |
| repo_id=self.space_id, | |
| token=self.hf_token, | |
| repo_type="space", | |
| exist_ok=True, | |
| private=False, # Spaces are typically public | |
| space_sdk="gradio", # Specify Gradio SDK | |
| space_hardware="cpu-basic" # Use basic CPU | |
| ) | |
| logger.info(f"β Space created successfully: {self.space_url}") | |
| return True | |
| except Exception as api_error: | |
| logger.error(f"API creation failed: {api_error}") | |
| logger.info("Falling back to CLI method...") | |
| return self._create_space_cli() | |
| except Exception as e: | |
| logger.error(f"β Error creating space: {e}") | |
| return False | |
| def _create_space_cli(self) -> bool: | |
| """Fallback method using CLI commands""" | |
| try: | |
| logger.info("Using CLI fallback method...") | |
| # Set HF token for CLI | |
| os.environ['HF_TOKEN'] = self.hf_token | |
| # Create space using Hugging Face CLI | |
| cmd = [ | |
| "hf", "repo", "create", | |
| self.space_id, | |
| "--type", "space" | |
| ] | |
| logger.info(f"Running command: {' '.join(cmd)}") | |
| result = subprocess.run(cmd, capture_output=True, text=True) | |
| if result.returncode != 0: | |
| logger.warning(f"First attempt failed: {result.stderr}") | |
| # Try alternative approach without space-specific flags | |
| logger.info("Retrying with basic space creation...") | |
| cmd = [ | |
| "hf", "repo", "create", | |
| self.space_id | |
| ] | |
| result = subprocess.run(cmd, capture_output=True, text=True) | |
| if result.returncode == 0: | |
| logger.info(f"β Space created successfully: {self.space_url}") | |
| return True | |
| else: | |
| logger.error(f"β Failed to create space: {result.stderr}") | |
| return False | |
| except Exception as e: | |
| logger.error(f"β Error creating space with CLI: {e}") | |
| return False | |
| def prepare_space_files(self) -> str: | |
| """Prepare all necessary files for the Space in a temporary directory""" | |
| try: | |
| logger.info("Preparing Space files...") | |
| # Create temporary directory | |
| temp_dir = tempfile.mkdtemp() | |
| logger.info(f"Created temporary directory: {temp_dir}") | |
| # Copy template files | |
| copied_files = [] | |
| for file_path in self.template_dir.iterdir(): | |
| if file_path.is_file(): | |
| dest_path = Path(temp_dir) / file_path.name | |
| shutil.copy2(file_path, dest_path) | |
| copied_files.append(file_path.name) | |
| logger.info(f"β Copied {file_path.name} to temp directory") | |
| # Update app.py with environment variables | |
| app_file = Path(temp_dir) / "app.py" | |
| if app_file.exists(): | |
| with open(app_file, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| # Add environment variable setup at the top | |
| env_setup = f""" | |
| # Environment variables for model configuration | |
| import os | |
| os.environ['HF_MODEL_ID'] = '{self.model_id}' | |
| os.environ['MODEL_SUBFOLDER'] = '{self.subfolder if self.subfolder else ""}' | |
| os.environ['MODEL_NAME'] = '{self.model_id.split("/")[-1]}' | |
| """ | |
| # Insert after imports | |
| lines = content.split('\n') | |
| import_end = 0 | |
| for i, line in enumerate(lines): | |
| if line.startswith('import ') or line.startswith('from '): | |
| import_end = i + 1 | |
| elif line.strip() == '' and import_end > 0: | |
| break | |
| lines.insert(import_end, env_setup) | |
| content = '\n'.join(lines) | |
| with open(app_file, 'w', encoding='utf-8') as f: | |
| f.write(content) | |
| logger.info("β Updated app.py with model configuration") | |
| # Create README.md for the space | |
| readme_content = f"""# Demo: {self.model_id} | |
| This is an interactive demo for the fine-tuned model {self.model_id}. | |
| ## Features | |
| - Interactive chat interface | |
| - Customizable system prompts | |
| - Advanced generation parameters | |
| - Thinking mode support | |
| ## Model Information | |
| - **Model ID**: {self.model_id} | |
| - **Subfolder**: {self.subfolder if self.subfolder and self.subfolder.strip() else "main"} | |
| - **Deployed by**: {self.hf_username} | |
| ## Usage | |
| Simply start chatting with the model using the interface below! | |
| --- | |
| *This demo was automatically deployed by the SmolLM3 Fine-tuning Pipeline* | |
| """ | |
| with open(Path(temp_dir) / "README.md", 'w', encoding='utf-8') as f: | |
| f.write(readme_content) | |
| logger.info(f"β Prepared {len(copied_files)} files in temporary directory") | |
| return temp_dir | |
| except Exception as e: | |
| logger.error(f"β Error preparing files: {e}") | |
| return None | |
| def upload_files_to_space(self, temp_dir: str) -> bool: | |
| """Upload files to the Space using HF Hub API directly""" | |
| try: | |
| logger.info("Uploading files to Space using HF Hub API...") | |
| if not HF_HUB_AVAILABLE: | |
| logger.error("β huggingface_hub not available for file upload") | |
| return self._upload_files_cli(temp_dir) | |
| # Upload each file using the HF Hub API | |
| temp_path = Path(temp_dir) | |
| uploaded_files = [] | |
| for file_path in temp_path.iterdir(): | |
| if file_path.is_file(): | |
| try: | |
| # Upload file to the space | |
| upload_file( | |
| path_or_fileobj=str(file_path), | |
| path_in_repo=file_path.name, | |
| repo_id=self.space_id, | |
| repo_type="space", | |
| token=self.hf_token | |
| ) | |
| uploaded_files.append(file_path.name) | |
| logger.info(f"β Uploaded {file_path.name}") | |
| except Exception as e: | |
| logger.error(f"β Failed to upload {file_path.name}: {e}") | |
| return False | |
| logger.info(f"β Successfully uploaded {len(uploaded_files)} files to Space") | |
| return True | |
| except Exception as e: | |
| logger.error(f"β Error uploading files: {e}") | |
| return self._upload_files_cli(temp_dir) | |
| def _upload_files_cli(self, temp_dir: str) -> bool: | |
| """Fallback method using CLI for file upload""" | |
| try: | |
| logger.info("Using CLI fallback for file upload...") | |
| # Set HF token for CLI | |
| os.environ['HF_TOKEN'] = self.hf_token | |
| # Initialize git repository | |
| subprocess.run(["git", "init"], cwd=temp_dir, check=True) | |
| subprocess.run(["git", "config", "user.name", "Demo Deployer"], cwd=temp_dir, check=True) | |
| subprocess.run(["git", "config", "user.email", "[email protected]"], cwd=temp_dir, check=True) | |
| # Add files | |
| subprocess.run(["git", "add", "."], cwd=temp_dir, check=True) | |
| subprocess.run(["git", "commit", "-m", f"Deploy demo for {self.model_id}"], cwd=temp_dir, check=True) | |
| # Add remote and push | |
| remote_url = f"https://{self.hf_token}@huggingface.co/spaces/{self.space_id}" | |
| subprocess.run(["git", "remote", "add", "origin", remote_url], cwd=temp_dir, check=True) | |
| subprocess.run(["git", "push", "-u", "origin", "main"], cwd=temp_dir, check=True) | |
| logger.info(f"β Successfully pushed files to space: {self.space_id}") | |
| return True | |
| except subprocess.CalledProcessError as e: | |
| logger.error(f"β Git operation failed: {e}") | |
| return False | |
| except Exception as e: | |
| logger.error(f"β Error pushing to space: {e}") | |
| return False | |
| def set_space_secrets(self) -> bool: | |
| """Set environment variables/secrets for the Space using HF Hub API""" | |
| try: | |
| logger.info("Setting Space secrets using HF Hub API...") | |
| if not HF_HUB_AVAILABLE: | |
| logger.warning("β huggingface_hub not available for setting secrets") | |
| return self._manual_secret_setup() | |
| # Set the HF_TOKEN secret for the space using the API | |
| try: | |
| self.api.add_space_secret( | |
| repo_id=self.space_id, | |
| key="HF_TOKEN", | |
| value=self.hf_token, | |
| description="Hugging Face token for model access" | |
| ) | |
| logger.info("β Successfully set HF_TOKEN secret via API") | |
| # Set model-specific environment variables | |
| self.api.add_space_variable( | |
| repo_id=self.space_id, | |
| key="HF_MODEL_ID", | |
| value=self.model_id, | |
| description="Model ID for the demo" | |
| ) | |
| logger.info(f"β Successfully set HF_MODEL_ID variable: {self.model_id}") | |
| if self.subfolder and self.subfolder.strip(): | |
| self.api.add_space_variable( | |
| repo_id=self.space_id, | |
| key="MODEL_SUBFOLDER", | |
| value=self.subfolder, | |
| description="Model subfolder for the demo" | |
| ) | |
| logger.info(f"β Successfully set MODEL_SUBFOLDER variable: {self.subfolder}") | |
| else: | |
| logger.info("βΉοΈ No subfolder specified, using main model") | |
| return True | |
| except Exception as api_error: | |
| logger.error(f"β Failed to set secrets via API: {api_error}") | |
| logger.info("Falling back to manual setup...") | |
| return self._manual_secret_setup() | |
| except Exception as e: | |
| logger.error(f"β Error setting space secrets: {e}") | |
| return self._manual_secret_setup() | |
| def _manual_secret_setup(self) -> bool: | |
| """Fallback method for manual secret setup""" | |
| logger.info("π Manual Space Secrets Configuration:") | |
| logger.info(f" HF_TOKEN={self.hf_token}") | |
| logger.info(f" HF_MODEL_ID={self.model_id}") | |
| if self.subfolder and self.subfolder.strip(): | |
| logger.info(f" MODEL_SUBFOLDER={self.subfolder}") | |
| else: | |
| logger.info(" MODEL_SUBFOLDER=(empty - using main model)") | |
| logger.info(f"\nπ§ To set secrets in your Space:") | |
| logger.info(f"1. Go to your Space settings: {self.space_url}/settings") | |
| logger.info("2. Navigate to the 'Repository secrets' section") | |
| logger.info("3. Add the following secrets:") | |
| logger.info(f" Name: HF_TOKEN") | |
| logger.info(f" Value: {self.hf_token}") | |
| logger.info(f" Name: HF_MODEL_ID") | |
| logger.info(f" Value: {self.model_id}") | |
| if self.subfolder and self.subfolder.strip(): | |
| logger.info(f" Name: MODEL_SUBFOLDER") | |
| logger.info(f" Value: {self.subfolder}") | |
| else: | |
| logger.info(" Name: MODEL_SUBFOLDER") | |
| logger.info(" Value: (leave empty)") | |
| logger.info("4. Save the secrets") | |
| return True | |
| def test_space(self) -> bool: | |
| """Test if the Space is working correctly""" | |
| try: | |
| logger.info("Testing Space...") | |
| # Wait a bit for the space to build | |
| logger.info("Waiting 180 seconds for Space to build...") | |
| time.sleep(180) | |
| # Try to access the space | |
| response = requests.get(self.space_url, timeout=30) | |
| if response.status_code == 200: | |
| logger.info(f"β Space is accessible: {self.space_url}") | |
| return True | |
| else: | |
| logger.warning(f"β οΈ Space returned status code: {response.status_code}") | |
| logger.warning(f"Response: {response.text[:500]}...") | |
| return False | |
| except Exception as e: | |
| logger.error(f"β Error testing space: {e}") | |
| return False | |
| def deploy(self) -> bool: | |
| """Main deployment method""" | |
| logger.info(f"π Starting demo space deployment for {self.model_id}") | |
| # Step 1: Validate model exists | |
| if not self.validate_model_exists(): | |
| return False | |
| # Step 2: Create space repository | |
| if not self.create_space_repository(): | |
| return False | |
| # Step 3: Prepare files | |
| temp_dir = self.prepare_space_files() | |
| if not temp_dir: | |
| return False | |
| # Step 4: Upload files | |
| if not self.upload_files_to_space(temp_dir): | |
| return False | |
| # Step 5: Set space secrets | |
| if not self.set_space_secrets(): | |
| return False | |
| # Step 6: Clean up temp directory | |
| try: | |
| shutil.rmtree(temp_dir) | |
| logger.info("β Cleaned up temporary directory") | |
| except Exception as e: | |
| logger.warning(f"β οΈ Warning: Could not clean up temp directory: {e}") | |
| # Step 7: Test space | |
| if not self.test_space(): | |
| logger.warning("β οΈ Space created but may need more time to build") | |
| logger.info("Please check the Space manually in a few minutes") | |
| logger.info(f"π Demo space deployment completed!") | |
| logger.info(f"π Space URL: {self.space_url}") | |
| logger.info(f"π§ Space configuration: {self.space_url}/settings") | |
| return True | |
| def main(): | |
| """Main function for command line usage""" | |
| print("Demo Space Deployment Script") | |
| print("=" * 40) | |
| parser = argparse.ArgumentParser(description="Deploy demo space to Hugging Face Spaces") | |
| parser.add_argument("--hf-token", required=True, help="Hugging Face token") | |
| parser.add_argument("--hf-username", required=True, help="Hugging Face username") | |
| parser.add_argument("--model-id", required=True, help="Model ID to deploy demo for") | |
| parser.add_argument("--subfolder", default="int4", help="Model subfolder (default: int4)") | |
| parser.add_argument("--space-name", help="Custom space name (optional)") | |
| args = parser.parse_args() | |
| deployer = DemoSpaceDeployer( | |
| hf_token=args.hf_token, | |
| hf_username=args.hf_username, | |
| model_id=args.model_id, | |
| subfolder=args.subfolder, | |
| space_name=args.space_name | |
| ) | |
| success = deployer.deploy() | |
| if success: | |
| print("\nβ Deployment successful!") | |
| print(f"π Your Demo Space: {deployer.space_url}") | |
| print(f"π€ Username: {deployer.hf_username}") | |
| print(f"π€ Model: {deployer.model_id}") | |
| print("\nNext steps:") | |
| print("1. Wait for the Space to build (usually 2-5 minutes)") | |
| print("2. Secrets have been automatically set via API") | |
| print("3. Test the interface by visiting the Space URL") | |
| print("4. Share your demo with others!") | |
| print("\nIf the Space doesn't work immediately, check:") | |
| print("- The Space logs at the Space URL") | |
| print("- That all files were uploaded correctly") | |
| print("- That the HF token has write permissions") | |
| print("- That the secrets were set correctly in Space settings") | |
| else: | |
| print("\nβ Deployment failed!") | |
| print("Check the error messages above and try again.") | |
| print("\nTroubleshooting:") | |
| print("1. Verify your HF token has write permissions") | |
| print("2. Check that the space name is available") | |
| print("3. Verify the model exists and is accessible") | |
| print("4. Try creating the space manually on HF first") | |
| sys.exit(0 if success else 1) | |
| if __name__ == "__main__": | |
| main() |