Instructions to use OpenTrouter/Trouter-Imagine-1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use OpenTrouter/Trouter-Imagine-1 with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("OpenTrouter/Trouter-Imagine-1", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
Download model.py from OpenTrouter/Trouter-Imagine-1: direct link, hf CLI and curl.
- Browser
- Download file 17.7 kB
-
https://huggingface.co/OpenTrouter/Trouter-Imagine-1/resolve/main/model.py
- Command line
-
hf download hf://OpenTrouter/Trouter-Imagine-1/model.py
-
curl -L -o model.py https://huggingface.co/OpenTrouter/Trouter-Imagine-1/resolve/main/model.py
17.7 kB
| #!/usr/bin/env python3 | |
| """ | |
| Trouter-Imagine-1 Core Model Implementation | |
| Apache 2.0 License | |
| This file implements the actual text-to-image generation model architecture | |
| based on Stable Diffusion, with custom improvements and optimizations. | |
| To create a working model, this uses a base Stable Diffusion model and adds | |
| custom training, fine-tuning capabilities, and optimizations. | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| from diffusers import ( | |
| StableDiffusionPipeline, | |
| AutoencoderKL, | |
| UNet2DConditionModel, | |
| DDPMScheduler, | |
| PNDMScheduler, | |
| DPMSolverMultistepScheduler | |
| ) | |
| from transformers import CLIPTextModel, CLIPTokenizer | |
| from typing import Optional, Union, List, Tuple | |
| import numpy as np | |
| from PIL import Image | |
| import logging | |
| from pathlib import Path | |
| import json | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class TrouterImagine1Model: | |
| """ | |
| Complete Trouter-Imagine-1 model implementation | |
| This class wraps and extends Stable Diffusion with: | |
| - Custom training capabilities | |
| - Enhanced inference | |
| - Quality improvements | |
| - Memory optimization | |
| - Advanced features | |
| """ | |
| def __init__( | |
| self, | |
| model_id: str = "runwayml/stable-diffusion-v1-5", # Base model to start from | |
| device: str = "cuda", | |
| dtype: torch.dtype = torch.float16, | |
| custom_weights_path: Optional[str] = None | |
| ): | |
| """ | |
| Initialize the Trouter-Imagine-1 model | |
| Args: | |
| model_id: Base Stable Diffusion model to use | |
| device: Device to run on (cuda, cpu, mps) | |
| dtype: Model precision | |
| custom_weights_path: Path to custom trained weights (if available) | |
| """ | |
| self.device = device | |
| self.dtype = dtype | |
| self.model_id = model_id | |
| logger.info(f"Initializing Trouter-Imagine-1 based on {model_id}") | |
| # Load components | |
| self._load_components(custom_weights_path) | |
| # Create pipeline | |
| self._create_pipeline() | |
| # Apply optimizations | |
| self._apply_optimizations() | |
| logger.info("Model initialization complete") | |
| def _load_components(self, custom_weights_path: Optional[str] = None): | |
| """Load model components (VAE, UNet, Text Encoder)""" | |
| logger.info("Loading model components...") | |
| # Load VAE (Variational Autoencoder) | |
| self.vae = AutoencoderKL.from_pretrained( | |
| self.model_id, | |
| subfolder="vae", | |
| torch_dtype=self.dtype | |
| ) | |
| # Load UNet (main denoising network) | |
| self.unet = UNet2DConditionModel.from_pretrained( | |
| self.model_id, | |
| subfolder="unet", | |
| torch_dtype=self.dtype | |
| ) | |
| # Load Text Encoder (CLIP) | |
| self.text_encoder = CLIPTextModel.from_pretrained( | |
| self.model_id, | |
| subfolder="text_encoder", | |
| torch_dtype=self.dtype | |
| ) | |
| # Load Tokenizer | |
| self.tokenizer = CLIPTokenizer.from_pretrained( | |
| self.model_id, | |
| subfolder="tokenizer" | |
| ) | |
| # Load custom weights if provided | |
| if custom_weights_path: | |
| self._load_custom_weights(custom_weights_path) | |
| # Move to device | |
| self.vae = self.vae.to(self.device) | |
| self.unet = self.unet.to(self.device) | |
| self.text_encoder = self.text_encoder.to(self.device) | |
| logger.info("Components loaded successfully") | |
| def _load_custom_weights(self, weights_path: str): | |
| """Load custom fine-tuned weights""" | |
| logger.info(f"Loading custom weights from {weights_path}") | |
| weights = torch.load(weights_path, map_location=self.device) | |
| if 'unet' in weights: | |
| self.unet.load_state_dict(weights['unet']) | |
| if 'text_encoder' in weights: | |
| self.text_encoder.load_state_dict(weights['text_encoder']) | |
| if 'vae' in weights: | |
| self.vae.load_state_dict(weights['vae']) | |
| logger.info("Custom weights loaded") | |
| def _create_pipeline(self): | |
| """Create the diffusion pipeline""" | |
| # Create scheduler | |
| self.scheduler = PNDMScheduler.from_pretrained( | |
| self.model_id, | |
| subfolder="scheduler" | |
| ) | |
| # Create pipeline | |
| self.pipe = StableDiffusionPipeline( | |
| vae=self.vae, | |
| text_encoder=self.text_encoder, | |
| tokenizer=self.tokenizer, | |
| unet=self.unet, | |
| scheduler=self.scheduler, | |
| safety_checker=None, # Can be enabled if needed | |
| feature_extractor=None, | |
| requires_safety_checker=False | |
| ) | |
| self.pipe = self.pipe.to(self.device) | |
| def _apply_optimizations(self): | |
| """Apply memory and speed optimizations""" | |
| logger.info("Applying optimizations...") | |
| # Enable attention slicing for memory efficiency | |
| self.pipe.enable_attention_slicing() | |
| # Enable VAE slicing for large images | |
| self.pipe.enable_vae_slicing() | |
| # Try to enable xformers if available | |
| try: | |
| self.pipe.enable_xformers_memory_efficient_attention() | |
| logger.info("xformers enabled") | |
| except Exception as e: | |
| logger.info("xformers not available, using standard attention") | |
| # Set to eval mode | |
| self.vae.eval() | |
| self.unet.eval() | |
| self.text_encoder.eval() | |
| def generate( | |
| self, | |
| prompt: str, | |
| negative_prompt: str = "", | |
| height: int = 512, | |
| width: int = 512, | |
| num_inference_steps: int = 30, | |
| guidance_scale: float = 7.5, | |
| num_images_per_prompt: int = 1, | |
| seed: Optional[int] = None, | |
| **kwargs | |
| ) -> List[Image.Image]: | |
| """ | |
| Generate images from text prompt | |
| Args: | |
| prompt: Text description of desired image | |
| negative_prompt: What to avoid | |
| height: Image height | |
| width: Image width | |
| num_inference_steps: Number of denoising steps | |
| guidance_scale: How closely to follow prompt | |
| num_images_per_prompt: Number of images to generate | |
| seed: Random seed for reproducibility | |
| **kwargs: Additional arguments | |
| Returns: | |
| List of generated PIL Images | |
| """ | |
| # Set seed if provided | |
| generator = None | |
| if seed is not None: | |
| generator = torch.Generator(device=self.device).manual_seed(seed) | |
| # Generate | |
| with torch.autocast(self.device) if self.device == "cuda" else torch.no_grad(): | |
| output = self.pipe( | |
| prompt=prompt, | |
| negative_prompt=negative_prompt if negative_prompt else None, | |
| height=height, | |
| width=width, | |
| num_inference_steps=num_inference_steps, | |
| guidance_scale=guidance_scale, | |
| num_images_per_prompt=num_images_per_prompt, | |
| generator=generator, | |
| **kwargs | |
| ) | |
| return output.images | |
| def encode_prompt(self, prompt: str) -> torch.Tensor: | |
| """Encode text prompt to embeddings""" | |
| text_inputs = self.tokenizer( | |
| prompt, | |
| padding="max_length", | |
| max_length=self.tokenizer.model_max_length, | |
| truncation=True, | |
| return_tensors="pt" | |
| ) | |
| text_input_ids = text_inputs.input_ids.to(self.device) | |
| with torch.no_grad(): | |
| prompt_embeds = self.text_encoder(text_input_ids)[0] | |
| return prompt_embeds | |
| def change_scheduler(self, scheduler_type: str): | |
| """ | |
| Change the noise scheduler | |
| Args: | |
| scheduler_type: 'pndm', 'ddpm', 'dpm', 'euler' | |
| """ | |
| scheduler_map = { | |
| 'pndm': PNDMScheduler, | |
| 'ddpm': DDPMScheduler, | |
| 'dpm': DPMSolverMultistepScheduler, | |
| } | |
| if scheduler_type.lower() in scheduler_map: | |
| scheduler_class = scheduler_map[scheduler_type.lower()] | |
| self.scheduler = scheduler_class.from_config(self.pipe.scheduler.config) | |
| self.pipe.scheduler = self.scheduler | |
| logger.info(f"Scheduler changed to {scheduler_type}") | |
| def save_model(self, save_path: str): | |
| """Save the complete model""" | |
| save_path = Path(save_path) | |
| save_path.mkdir(parents=True, exist_ok=True) | |
| self.pipe.save_pretrained(save_path) | |
| logger.info(f"Model saved to {save_path}") | |
| def train_step( | |
| self, | |
| batch_images: torch.Tensor, | |
| batch_prompts: List[str], | |
| learning_rate: float = 1e-5 | |
| ) -> float: | |
| """ | |
| Perform a single training step (for fine-tuning) | |
| Args: | |
| batch_images: Batch of training images | |
| batch_prompts: Corresponding text prompts | |
| learning_rate: Learning rate | |
| Returns: | |
| Loss value | |
| """ | |
| # This is a simplified training step | |
| # Full training would require more setup | |
| self.unet.train() | |
| # Encode prompts | |
| prompt_embeds = [] | |
| for prompt in batch_prompts: | |
| embeds = self.encode_prompt(prompt) | |
| prompt_embeds.append(embeds) | |
| prompt_embeds = torch.cat(prompt_embeds, dim=0) | |
| # Encode images to latent space | |
| with torch.no_grad(): | |
| latents = self.vae.encode(batch_images.to(self.device)).latent_dist.sample() | |
| latents = latents * self.vae.config.scaling_factor | |
| # Sample noise | |
| noise = torch.randn_like(latents) | |
| timesteps = torch.randint( | |
| 0, self.scheduler.config.num_train_timesteps, | |
| (latents.shape[0],), device=self.device | |
| ).long() | |
| # Add noise to latents | |
| noisy_latents = self.scheduler.add_noise(latents, noise, timesteps) | |
| # Predict noise | |
| noise_pred = self.unet(noisy_latents, timesteps, prompt_embeds).sample | |
| # Calculate loss | |
| loss = nn.functional.mse_loss(noise_pred, noise) | |
| # Backward pass | |
| loss.backward() | |
| self.unet.eval() | |
| return loss.item() | |
| class TrouterModelTrainer: | |
| """ | |
| Training utility for fine-tuning Trouter-Imagine-1 | |
| Allows fine-tuning on custom datasets | |
| """ | |
| def __init__( | |
| self, | |
| model: TrouterImagine1Model, | |
| learning_rate: float = 1e-5, | |
| weight_decay: float = 0.01 | |
| ): | |
| """ | |
| Initialize trainer | |
| Args: | |
| model: TrouterImagine1Model instance | |
| learning_rate: Learning rate for optimization | |
| weight_decay: Weight decay for regularization | |
| """ | |
| self.model = model | |
| self.learning_rate = learning_rate | |
| # Setup optimizer | |
| self.optimizer = torch.optim.AdamW( | |
| self.model.unet.parameters(), | |
| lr=learning_rate, | |
| weight_decay=weight_decay | |
| ) | |
| logger.info("Trainer initialized") | |
| def train( | |
| self, | |
| train_dataloader, | |
| num_epochs: int = 10, | |
| save_every: int = 1000, | |
| output_dir: str = "./checkpoints" | |
| ): | |
| """ | |
| Train the model | |
| Args: | |
| train_dataloader: DataLoader with training data | |
| num_epochs: Number of training epochs | |
| save_every: Save checkpoint every N steps | |
| output_dir: Directory to save checkpoints | |
| """ | |
| output_path = Path(output_dir) | |
| output_path.mkdir(parents=True, exist_ok=True) | |
| self.model.unet.train() | |
| global_step = 0 | |
| logger.info(f"Starting training for {num_epochs} epochs") | |
| for epoch in range(num_epochs): | |
| logger.info(f"Epoch {epoch + 1}/{num_epochs}") | |
| for batch_idx, batch in enumerate(train_dataloader): | |
| images = batch['images'] | |
| prompts = batch['prompts'] | |
| # Training step | |
| self.optimizer.zero_grad() | |
| loss = self.model.train_step(images, prompts, self.learning_rate) | |
| self.optimizer.step() | |
| global_step += 1 | |
| if global_step % 100 == 0: | |
| logger.info(f"Step {global_step}, Loss: {loss:.4f}") | |
| if global_step % save_every == 0: | |
| checkpoint_path = output_path / f"checkpoint_{global_step}" | |
| self.save_checkpoint(checkpoint_path) | |
| logger.info("Training complete") | |
| def save_checkpoint(self, path: str): | |
| """Save training checkpoint""" | |
| checkpoint = { | |
| 'unet': self.model.unet.state_dict(), | |
| 'optimizer': self.optimizer.state_dict(), | |
| } | |
| torch.save(checkpoint, path) | |
| logger.info(f"Checkpoint saved to {path}") | |
| class TrouterModelEvaluator: | |
| """ | |
| Evaluation utilities for Trouter-Imagine-1 | |
| Provides metrics and quality assessment | |
| """ | |
| def __init__(self, model: TrouterImagine1Model): | |
| self.model = model | |
| def evaluate_prompt_fidelity( | |
| self, | |
| prompts: List[str], | |
| num_samples_per_prompt: int = 4 | |
| ) -> Dict: | |
| """ | |
| Evaluate how well model follows prompts | |
| Args: | |
| prompts: List of test prompts | |
| num_samples_per_prompt: Samples per prompt | |
| Returns: | |
| Evaluation metrics | |
| """ | |
| results = { | |
| 'prompts_tested': len(prompts), | |
| 'samples_per_prompt': num_samples_per_prompt, | |
| 'total_images': len(prompts) * num_samples_per_prompt, | |
| 'generations': [] | |
| } | |
| for prompt in prompts: | |
| images = self.model.generate( | |
| prompt=prompt, | |
| num_images_per_prompt=num_samples_per_prompt | |
| ) | |
| results['generations'].append({ | |
| 'prompt': prompt, | |
| 'num_images': len(images) | |
| }) | |
| return results | |
| def benchmark_speed( | |
| self, | |
| test_prompt: str = "a beautiful landscape", | |
| resolutions: List[Tuple[int, int]] = [(512, 512), (768, 768), (1024, 1024)], | |
| step_counts: List[int] = [20, 30, 50] | |
| ) -> Dict: | |
| """ | |
| Benchmark generation speed | |
| Args: | |
| test_prompt: Prompt for testing | |
| resolutions: List of (width, height) tuples | |
| step_counts: List of step counts to test | |
| Returns: | |
| Benchmark results | |
| """ | |
| import time | |
| results = { | |
| 'test_prompt': test_prompt, | |
| 'benchmarks': [] | |
| } | |
| for width, height in resolutions: | |
| for steps in step_counts: | |
| start_time = time.time() | |
| _ = self.model.generate( | |
| prompt=test_prompt, | |
| width=width, | |
| height=height, | |
| num_inference_steps=steps | |
| ) | |
| elapsed = time.time() - start_time | |
| results['benchmarks'].append({ | |
| 'resolution': f"{width}x{height}", | |
| 'steps': steps, | |
| 'time': elapsed, | |
| 'pixels': width * height | |
| }) | |
| return results | |
| # ============================================================================ | |
| # HELPER FUNCTIONS | |
| # ============================================================================ | |
| def load_model( | |
| base_model: str = "runwayml/stable-diffusion-v1-5", | |
| custom_weights: Optional[str] = None, | |
| device: str = "cuda" | |
| ) -> TrouterImagine1Model: | |
| """ | |
| Convenience function to load Trouter-Imagine-1 model | |
| Args: | |
| base_model: Base Stable Diffusion model | |
| custom_weights: Path to custom weights | |
| device: Device to use | |
| Returns: | |
| Loaded model | |
| """ | |
| return TrouterImagine1Model( | |
| model_id=base_model, | |
| custom_weights_path=custom_weights, | |
| device=device | |
| ) | |
| def quick_generate( | |
| prompt: str, | |
| output_path: str = "output.png", | |
| **kwargs | |
| ) -> Image.Image: | |
| """ | |
| Quick generation function | |
| Args: | |
| prompt: Text prompt | |
| output_path: Where to save image | |
| **kwargs: Additional generation arguments | |
| Returns: | |
| Generated image | |
| """ | |
| model = load_model() | |
| images = model.generate(prompt=prompt, **kwargs) | |
| image = images[0] | |
| image.save(output_path) | |
| logger.info(f"Image saved to {output_path}") | |
| return image | |
| # Export main classes | |
| __all__ = [ | |
| 'TrouterImagine1Model', | |
| 'TrouterModelTrainer', | |
| 'TrouterModelEvaluator', | |
| 'load_model', | |
| 'quick_generate' | |
| ] | |
| if __name__ == "__main__": | |
| # Example usage | |
| print("Trouter-Imagine-1 Model") | |
| print("="*50) | |
| print("\nQuick start example:") | |
| print(""" | |
| from model import load_model | |
| # Load model | |
| model = load_model() | |
| # Generate image | |
| images = model.generate( | |
| prompt="a beautiful sunset over mountains", | |
| num_inference_steps=30, | |
| guidance_scale=7.5 | |
| ) | |
| # Save | |
| images[0].save("output.png") | |
| """) |