Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Kostengünstiges LORA-Training für OpenHermes-2.5-Mistral-7B | |
| Optimiert für lokale Ausführung mit effizienter Speichernutzung | |
| """ | |
| import torch | |
| from transformers import ( | |
| AutoTokenizer, AutoModelForCausalLM, | |
| TrainingArguments, Trainer, DataCollatorForLanguageModeling | |
| ) | |
| from peft import LoraConfig, get_peft_model, TaskType | |
| from datasets import Dataset | |
| import json | |
| from pathlib import Path | |
| import logging | |
| # Setup logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class LoRATrainer: | |
| def __init__(self, base_model_id="teknium/OpenHermes-2.5-Mistral-7B"): | |
| self.base_model_id = base_model_id | |
| self.output_dir = Path("../models/lora-checkpoint") | |
| self.output_dir.mkdir(parents=True, exist_ok=True) | |
| def setup_model_and_tokenizer(self): | |
| """Modell und Tokenizer mit optimalen Einstellungen laden""" | |
| logger.info(f"Lade Modell: {self.base_model_id}") | |
| # Tokenizer | |
| self.tokenizer = AutoTokenizer.from_pretrained(self.base_model_id) | |
| if self.tokenizer.pad_token is None: | |
| self.tokenizer.pad_token = self.tokenizer.eos_token | |
| # Modell mit 4-bit Quantisierung für Speichereffizienz | |
| self.model = AutoModelForCausalLM.from_pretrained( | |
| self.base_model_id, | |
| load_in_4bit=True, | |
| torch_dtype=torch.float16, | |
| device_map="auto", | |
| trust_remote_code=True | |
| ) | |
| # LoRA Konfiguration - kostengünstig optimiert | |
| lora_config = LoraConfig( | |
| task_type=TaskType.CAUSAL_LM, | |
| r=16, # Rank - niedrig für Effizienz | |
| lora_alpha=32, # Skalierungsfaktor | |
| lora_dropout=0.1, | |
| target_modules=["q_proj", "v_proj", "k_proj", "o_proj"], # Nur wichtige Module | |
| bias="none" | |
| ) | |
| # PEFT Modell erstellen | |
| self.model = get_peft_model(self.model, lora_config) | |
| self.model.print_trainable_parameters() | |
| def prepare_dataset(self, data_path="training_data.json"): | |
| """Trainingsdaten vorbereiten""" | |
| logger.info(f"Lade Trainingsdaten: {data_path}") | |
| # Beispiel-Datenformat für Horoskop-Generierung | |
| if not Path(data_path).exists(): | |
| self.create_sample_data(data_path) | |
| with open(data_path, 'r', encoding='utf-8') as f: | |
| data = json.load(f) | |
| def tokenize_function(examples): | |
| # Prompt-Template für Horoskop-Generierung | |
| texts = [] | |
| for item in examples: | |
| prompt = f"<|im_start|>system\nDu bist ein erfahrener Astrologe. Erstelle ein präzises und einfühlsames Horoskop.<|im_end|>\n<|im_start|>user\nErstelle ein Horoskop für {item['sign']} am {item['date']}.<|im_end|>\n<|im_start|>assistant\n{item['horoscope']}<|im_end|>" | |
| texts.append(prompt) | |
| tokenized = self.tokenizer( | |
| texts, | |
| truncation=True, | |
| padding="max_length", | |
| max_length=512, | |
| return_tensors="pt" | |
| ) | |
| tokenized["labels"] = tokenized["input_ids"].clone() | |
| return tokenized | |
| dataset = Dataset.from_list(data) | |
| tokenized_dataset = dataset.map(tokenize_function, batched=False) | |
| return tokenized_dataset | |
| def create_sample_data(self, data_path): | |
| """Beispiel-Trainingsdaten erstellen""" | |
| sample_data = [ | |
| { | |
| "sign": "Widder", | |
| "date": "heute", | |
| "horoscope": "Die Energie des Mars verleiht Ihnen heute besondere Kraft. Nutzen Sie diese für wichtige Entscheidungen in der Liebe." | |
| }, | |
| { | |
| "sign": "Stier", | |
| "date": "morgen", | |
| "horoscope": "Venus bringt harmonische Schwingungen in Ihr Leben. Ein perfekter Tag für romantische Begegnungen." | |
| }, | |
| # Weitere Beispiele... | |
| ] | |
| with open(data_path, 'w', encoding='utf-8') as f: | |
| json.dump(sample_data, f, ensure_ascii=False, indent=2) | |
| logger.info(f"Beispiel-Trainingsdaten erstellt: {data_path}") | |
| def train(self, dataset, num_epochs=3): | |
| """Training starten - kostengünstig optimiert""" | |
| logger.info("Starte Training...") | |
| training_args = TrainingArguments( | |
| output_dir=str(self.output_dir), | |
| num_train_epochs=num_epochs, | |
| per_device_train_batch_size=1, # Kleine Batch-Size für wenig RAM | |
| gradient_accumulation_steps=4, # Simuliert größere Batches | |
| warmup_steps=100, | |
| logging_steps=10, | |
| save_steps=500, | |
| evaluation_strategy="no", # Keine Evaluation für Effizienz | |
| save_strategy="epoch", | |
| load_best_model_at_end=False, | |
| remove_unused_columns=False, | |
| dataloader_pin_memory=False, | |
| gradient_checkpointing=True, # Speicher sparen | |
| optim="adamw_torch", | |
| learning_rate=5e-5, | |
| weight_decay=0.01, | |
| lr_scheduler_type="cosine", | |
| report_to=None, # Kein Wandb etc. für Kosten | |
| ) | |
| data_collator = DataCollatorForLanguageModeling( | |
| tokenizer=self.tokenizer, | |
| mlm=False | |
| ) | |
| trainer = Trainer( | |
| model=self.model, | |
| args=training_args, | |
| train_dataset=dataset, | |
| data_collator=data_collator, | |
| ) | |
| # Training starten | |
| trainer.train() | |
| # Modell speichern | |
| trainer.save_model() | |
| self.tokenizer.save_pretrained(str(self.output_dir)) | |
| logger.info(f"Training abgeschlossen. Modell gespeichert in: {self.output_dir}") | |
| def main(): | |
| """Hauptfunktion für Training""" | |
| trainer = LoRATrainer() | |
| # 1. Modell und Tokenizer setup | |
| trainer.setup_model_and_tokenizer() | |
| # 2. Dataset vorbereiten | |
| dataset = trainer.prepare_dataset() | |
| # 3. Training starten | |
| trainer.train(dataset, num_epochs=3) | |
| print("✅ LORA-Training erfolgreich abgeschlossen!") | |
| print(f"📁 Modell gespeichert in: {trainer.output_dir}") | |
| print("🚀 Jetzt können Sie das trainierte Modell in Ihrem FastAPI-Server verwenden.") | |
| if __name__ == "__main__": | |
| main() | |