Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Push Trained Model and Results to Hugging Face Hub | |
| Integrates with Trackio monitoring and provides complete model deployment | |
| """ | |
| import os | |
| import json | |
| import argparse | |
| import logging | |
| from pathlib import Path | |
| from typing import Dict, Any, Optional, List | |
| from datetime import datetime | |
| import subprocess | |
| import shutil | |
| try: | |
| from huggingface_hub import HfApi, create_repo, upload_file | |
| from huggingface_hub import snapshot_download, hf_hub_download | |
| HF_AVAILABLE = True | |
| except ImportError: | |
| HF_AVAILABLE = False | |
| print("Warning: huggingface_hub not available. Install with: pip install huggingface_hub") | |
| try: | |
| from monitoring import SmolLM3Monitor | |
| MONITORING_AVAILABLE = True | |
| except ImportError: | |
| MONITORING_AVAILABLE = False | |
| print("Warning: monitoring module not available") | |
| logger = logging.getLogger(__name__) | |
| class HuggingFacePusher: | |
| """Push trained models and results to Hugging Face Hub""" | |
| def __init__( | |
| self, | |
| model_path: str, | |
| repo_name: str, | |
| token: Optional[str] = None, | |
| private: bool = False, | |
| trackio_url: Optional[str] = None, | |
| experiment_name: Optional[str] = None | |
| ): | |
| self.model_path = Path(model_path) | |
| self.repo_name = repo_name | |
| self.token = token or os.getenv('HF_TOKEN') | |
| self.private = private | |
| self.trackio_url = trackio_url | |
| self.experiment_name = experiment_name | |
| # Initialize HF API | |
| if HF_AVAILABLE: | |
| self.api = HfApi(token=self.token) | |
| else: | |
| raise ImportError("huggingface_hub is required. Install with: pip install huggingface_hub") | |
| # Initialize monitoring if available | |
| self.monitor = None | |
| if MONITORING_AVAILABLE and trackio_url: | |
| self.monitor = SmolLM3Monitor( | |
| experiment_name=experiment_name or "model_push", | |
| trackio_url=trackio_url, | |
| enable_tracking=True | |
| ) | |
| logger.info(f"Initialized HuggingFacePusher for {repo_name}") | |
| def create_repository(self) -> bool: | |
| """Create the Hugging Face repository""" | |
| try: | |
| logger.info(f"Creating repository: {self.repo_name}") | |
| # Create repository | |
| create_repo( | |
| repo_id=self.repo_name, | |
| token=self.token, | |
| private=self.private, | |
| exist_ok=True | |
| ) | |
| logger.info(f"β Repository created: https://huggingface.co/{self.repo_name}") | |
| return True | |
| except Exception as e: | |
| logger.error(f"β Failed to create repository: {e}") | |
| return False | |
| def validate_model_path(self) -> bool: | |
| """Validate that the model path contains required files""" | |
| required_files = [ | |
| "config.json", | |
| "pytorch_model.bin", | |
| "tokenizer.json", | |
| "tokenizer_config.json" | |
| ] | |
| missing_files = [] | |
| for file in required_files: | |
| if not (self.model_path / file).exists(): | |
| missing_files.append(file) | |
| if missing_files: | |
| logger.error(f"β Missing required files: {missing_files}") | |
| return False | |
| logger.info("β Model files validated") | |
| return True | |
| def create_model_card(self, training_config: Dict[str, Any], results: Dict[str, Any]) -> str: | |
| """Create a comprehensive model card""" | |
| model_card = f"""--- | |
| language: | |
| - en | |
| license: mit | |
| tags: | |
| - smollm3 | |
| - fine-tuned | |
| - text-generation | |
| - transformers | |
| --- | |
| # {self.repo_name.split('/')[-1]} | |
| This is a fine-tuned SmolLM3 model based on the HuggingFaceTB/SmolLM3-3B architecture. | |
| ## Model Details | |
| - **Base Model**: HuggingFaceTB/SmolLM3-3B | |
| - **Fine-tuning Method**: Supervised Fine-tuning | |
| - **Training Date**: {datetime.now().strftime('%Y-%m-%d')} | |
| - **Model Size**: {self._get_model_size():.1f} GB | |
| ## Training Configuration | |
| ```json | |
| {json.dumps(training_config, indent=2)} | |
| ``` | |
| ## Training Results | |
| ```json | |
| {json.dumps(results, indent=2)} | |
| ``` | |
| ## Usage | |
| ```python | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| # Load model and tokenizer | |
| model = AutoModelForCausalLM.from_pretrained("{self.repo_name}") | |
| tokenizer = AutoTokenizer.from_pretrained("{self.repo_name}") | |
| # Generate text | |
| inputs = tokenizer("Hello, how are you?", return_tensors="pt") | |
| outputs = model.generate(**inputs, max_new_tokens=100) | |
| print(tokenizer.decode(outputs[0], skip_special_tokens=True)) | |
| ``` | |
| ## Training Information | |
| - **Framework**: Transformers | |
| - **Hardware**: {self._get_hardware_info()} | |
| - **Training Time**: {results.get('training_time_hours', 'Unknown')} hours | |
| - **Final Loss**: {results.get('final_loss', 'Unknown')} | |
| - **Final Accuracy**: {results.get('final_accuracy', 'Unknown')} | |
| ## Model Performance | |
| - **Training Loss**: {results.get('train_loss', 'Unknown')} | |
| - **Validation Loss**: {results.get('eval_loss', 'Unknown')} | |
| - **Training Steps**: {results.get('total_steps', 'Unknown')} | |
| ## Limitations and Biases | |
| This model is fine-tuned for specific tasks and may not generalize well to all use cases. Please evaluate the model's performance on your specific task before deployment. | |
| ## License | |
| This model is licensed under the MIT License. | |
| """ | |
| return model_card | |
| def _get_model_size(self) -> float: | |
| """Get model size in GB""" | |
| try: | |
| total_size = 0 | |
| for file in self.model_path.rglob("*"): | |
| if file.is_file(): | |
| total_size += file.stat().st_size | |
| return total_size / (1024**3) # Convert to GB | |
| except: | |
| return 0.0 | |
| def _get_hardware_info(self) -> str: | |
| """Get hardware information""" | |
| try: | |
| import torch | |
| if torch.cuda.is_available(): | |
| gpu_name = torch.cuda.get_device_name(0) | |
| return f"GPU: {gpu_name}" | |
| else: | |
| return "CPU" | |
| except: | |
| return "Unknown" | |
| def upload_model_files(self) -> bool: | |
| """Upload model files to Hugging Face Hub""" | |
| try: | |
| logger.info("Uploading model files...") | |
| # Upload all files in the model directory | |
| for file_path in self.model_path.rglob("*"): | |
| if file_path.is_file(): | |
| relative_path = file_path.relative_to(self.model_path) | |
| remote_path = str(relative_path) | |
| logger.info(f"Uploading {relative_path}") | |
| upload_file( | |
| path_or_fileobj=str(file_path), | |
| path_in_repo=remote_path, | |
| repo_id=self.repo_name, | |
| token=self.token | |
| ) | |
| logger.info("β Model files uploaded successfully") | |
| return True | |
| except Exception as e: | |
| logger.error(f"β Failed to upload model files: {e}") | |
| return False | |
| def upload_training_results(self, results_path: str) -> bool: | |
| """Upload training results and logs""" | |
| try: | |
| logger.info("Uploading training results...") | |
| results_files = [ | |
| "train_results.json", | |
| "eval_results.json", | |
| "training_config.json", | |
| "training.log" | |
| ] | |
| for file_name in results_files: | |
| file_path = Path(results_path) / file_name | |
| if file_path.exists(): | |
| logger.info(f"Uploading {file_name}") | |
| upload_file( | |
| path_or_fileobj=str(file_path), | |
| path_in_repo=f"training_results/{file_name}", | |
| repo_id=self.repo_name, | |
| token=self.token | |
| ) | |
| logger.info("β Training results uploaded successfully") | |
| return True | |
| except Exception as e: | |
| logger.error(f"β Failed to upload training results: {e}") | |
| return False | |
| def create_readme(self, training_config: Dict[str, Any], results: Dict[str, Any]) -> bool: | |
| """Create and upload README.md""" | |
| try: | |
| logger.info("Creating README.md...") | |
| readme_content = f"""# {self.repo_name.split('/')[-1]} | |
| A fine-tuned SmolLM3 model for text generation tasks. | |
| ## Quick Start | |
| ```python | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| model = AutoModelForCausalLM.from_pretrained("{self.repo_name}") | |
| tokenizer = AutoTokenizer.from_pretrained("{self.repo_name}") | |
| # Generate text | |
| text = "Hello, how are you?" | |
| inputs = tokenizer(text, return_tensors="pt") | |
| outputs = model.generate(**inputs, max_new_tokens=100) | |
| print(tokenizer.decode(outputs[0], skip_special_tokens=True)) | |
| ``` | |
| ## Model Information | |
| - **Base Model**: HuggingFaceTB/SmolLM3-3B | |
| - **Fine-tuning Date**: {datetime.now().strftime('%Y-%m-%d')} | |
| - **Model Size**: {self._get_model_size():.1f} GB | |
| - **Training Steps**: {results.get('total_steps', 'Unknown')} | |
| - **Final Loss**: {results.get('final_loss', 'Unknown')} | |
| ## Training Configuration | |
| ```json | |
| {json.dumps(training_config, indent=2)} | |
| ``` | |
| ## Performance Metrics | |
| ```json | |
| {json.dumps(results, indent=2)} | |
| ``` | |
| ## Files | |
| - `pytorch_model.bin`: Model weights | |
| - `config.json`: Model configuration | |
| - `tokenizer.json`: Tokenizer configuration | |
| - `training_results/`: Training logs and results | |
| ## License | |
| MIT License | |
| """ | |
| # Write README to temporary file | |
| readme_path = Path("temp_readme.md") | |
| with open(readme_path, "w") as f: | |
| f.write(readme_content) | |
| # Upload README | |
| upload_file( | |
| path_or_fileobj=str(readme_path), | |
| path_in_repo="README.md", | |
| repo_id=self.repo_name, | |
| token=self.token | |
| ) | |
| # Clean up | |
| readme_path.unlink() | |
| logger.info("β README.md uploaded successfully") | |
| return True | |
| except Exception as e: | |
| logger.error(f"β Failed to create README: {e}") | |
| return False | |
| def log_to_trackio(self, action: str, details: Dict[str, Any]): | |
| """Log push action to Trackio""" | |
| if self.monitor: | |
| try: | |
| self.monitor.log_metrics({ | |
| "push_action": action, | |
| "repo_name": self.repo_name, | |
| "model_size_gb": self._get_model_size(), | |
| **details | |
| }) | |
| logger.info(f"β Logged {action} to Trackio") | |
| except Exception as e: | |
| logger.error(f"β Failed to log to Trackio: {e}") | |
| def push_model(self, training_config: Optional[Dict[str, Any]] = None, | |
| results: Optional[Dict[str, Any]] = None) -> bool: | |
| """Complete model push process""" | |
| logger.info(f"π Starting model push to {self.repo_name}") | |
| # Validate model path | |
| if not self.validate_model_path(): | |
| return False | |
| # Create repository | |
| if not self.create_repository(): | |
| return False | |
| # Load training config and results if not provided | |
| if training_config is None: | |
| training_config = self._load_training_config() | |
| if results is None: | |
| results = self._load_training_results() | |
| # Create and upload model card | |
| model_card = self.create_model_card(training_config, results) | |
| model_card_path = Path("temp_model_card.md") | |
| with open(model_card_path, "w") as f: | |
| f.write(model_card) | |
| try: | |
| upload_file( | |
| path_or_fileobj=str(model_card_path), | |
| path_in_repo="README.md", | |
| repo_id=self.repo_name, | |
| token=self.token | |
| ) | |
| finally: | |
| model_card_path.unlink() | |
| # Upload model files | |
| if not self.upload_model_files(): | |
| return False | |
| # Upload training results | |
| if results: | |
| self.upload_training_results(str(self.model_path)) | |
| # Log to Trackio | |
| self.log_to_trackio("model_push", { | |
| "model_path": str(self.model_path), | |
| "repo_name": self.repo_name, | |
| "private": self.private, | |
| "training_config": training_config, | |
| "results": results | |
| }) | |
| logger.info(f"π Model successfully pushed to: https://huggingface.co/{self.repo_name}") | |
| return True | |
| def _load_training_config(self) -> Dict[str, Any]: | |
| """Load training configuration""" | |
| config_path = self.model_path / "training_config.json" | |
| if config_path.exists(): | |
| with open(config_path, "r") as f: | |
| return json.load(f) | |
| return {"model_name": "HuggingFaceTB/SmolLM3-3B"} | |
| def _load_training_results(self) -> Dict[str, Any]: | |
| """Load training results""" | |
| results_path = self.model_path / "train_results.json" | |
| if results_path.exists(): | |
| with open(results_path, "r") as f: | |
| return json.load(f) | |
| return {"final_loss": "Unknown", "total_steps": "Unknown"} | |
| def parse_args(): | |
| """Parse command line arguments""" | |
| parser = argparse.ArgumentParser(description='Push trained model to Hugging Face Hub') | |
| # Required arguments | |
| parser.add_argument('model_path', type=str, help='Path to trained model directory') | |
| parser.add_argument('repo_name', type=str, help='Hugging Face repository name (username/repo-name)') | |
| # Optional arguments | |
| parser.add_argument('--token', type=str, default=None, help='Hugging Face token') | |
| parser.add_argument('--private', action='store_true', help='Make repository private') | |
| parser.add_argument('--trackio-url', type=str, default=None, help='Trackio Space URL for logging') | |
| parser.add_argument('--experiment-name', type=str, default=None, help='Experiment name for Trackio') | |
| return parser.parse_args() | |
| def main(): | |
| """Main function""" | |
| args = parse_args() | |
| # Setup logging | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' | |
| ) | |
| logger.info("Starting model push to Hugging Face Hub") | |
| # Initialize pusher | |
| try: | |
| pusher = HuggingFacePusher( | |
| model_path=args.model_path, | |
| repo_name=args.repo_name, | |
| token=args.token, | |
| private=args.private, | |
| trackio_url=args.trackio_url, | |
| experiment_name=args.experiment_name | |
| ) | |
| # Push model | |
| success = pusher.push_model() | |
| if success: | |
| logger.info("β Model push completed successfully!") | |
| logger.info(f"π View your model at: https://huggingface.co/{args.repo_name}") | |
| else: | |
| logger.error("β Model push failed!") | |
| return 1 | |
| except Exception as e: | |
| logger.error(f"β Error during model push: {e}") | |
| return 1 | |
| return 0 | |
| if __name__ == "__main__": | |
| exit(main()) |