# -*- coding: utf-8 -*- """ Hardware-Erkennung fuer Modell-Empfehlungen. Erkennt Festplattenspeicher, RAM, GPU/VRAM und gibt passende Modell-Empfehlungen zurueck. """ from __future__ import annotations import os import shutil import subprocess from dataclasses import dataclass, field @dataclass class GpuInfo: """GPU-Informationen.""" name: str = "" vram_mb: int = 0 cuda_available: bool = False driver: str = "" @dataclass class SystemInfo: """Erkannte System-Hardware.""" ram_total_mb: int = 0 disk_free_mb: int = 0 disk_total_mb: int = 0 gpu: GpuInfo = field(default_factory=GpuInfo) cpu_model: str = "" cpu_cores: int = 0 is_raspberry_pi: bool = False @dataclass class ModelRecommendation: """Empfehlung fuer ein Modell.""" name: str description: str size_mb: int requires_gpu: bool = False min_ram_mb: int = 0 min_vram_mb: int = 0 recommended: bool = False reason: str = "" def detect_system() -> SystemInfo: """Erkennt die Hardware des Systems.""" info = SystemInfo() # RAM try: with open("/proc/meminfo") as f: for line in f: if line.startswith("MemTotal:"): kb = int(line.split()[1]) info.ram_total_mb = kb // 1024 break except (OSError, ValueError): pass # Festplatte (Zielpartition) try: usage = shutil.disk_usage("/") info.disk_free_mb = usage.free // (1024 * 1024) info.disk_total_mb = usage.total // (1024 * 1024) except OSError: pass # CPU try: with open("/proc/cpuinfo") as f: cores = 0 for line in f: if line.startswith("model name"): info.cpu_model = line.split(":", 1)[1].strip() if line.startswith("processor"): cores += 1 info.cpu_cores = cores except (OSError, ValueError): pass # Raspberry Pi Erkennung try: with open("/proc/device-tree/model") as f: model = f.read().lower() info.is_raspberry_pi = "raspberry" in model except OSError: pass # GPU / CUDA info.gpu = _detect_gpu() return info def _detect_gpu() -> GpuInfo: """Erkennt GPU und VRAM.""" gpu = GpuInfo() # NVIDIA GPU via nvidia-smi try: result = subprocess.run( ["nvidia-smi", "--query-gpu=name,memory.total,driver_version", "--format=csv,noheader,nounits"], capture_output=True, text=True, timeout=5, ) if result.returncode == 0 and result.stdout.strip(): parts = result.stdout.strip().split(",") if len(parts) >= 3: gpu.name = parts[0].strip() try: gpu.vram_mb = int(float(parts[1].strip())) except ValueError: pass gpu.driver = parts[2].strip() gpu.cuda_available = True return gpu except (FileNotFoundError, subprocess.TimeoutExpired): pass # PyTorch CUDA Check (falls installiert) try: result = subprocess.run( ["python3", "-c", "import torch; print(torch.cuda.is_available()); " "print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else ''); " "print(torch.cuda.get_device_properties(0).total_mem // 1048576 " "if torch.cuda.is_available() else 0)"], capture_output=True, text=True, timeout=10, ) if result.returncode == 0: lines = result.stdout.strip().split("\n") if len(lines) >= 3 and lines[0] == "True": gpu.cuda_available = True gpu.name = lines[1] or gpu.name try: gpu.vram_mb = int(lines[2]) or gpu.vram_mb except ValueError: pass except (FileNotFoundError, subprocess.TimeoutExpired): pass # Fallback: /proc/driver/nvidia/gpus if not gpu.name: try: for entry in os.listdir("/proc/driver/nvidia/gpus"): with open(f"/proc/driver/nvidia/gpus/{entry}/information") as f: for line in f: if "Model:" in line: gpu.name = line.split(":", 1)[1].strip() break except (OSError, StopIteration): pass return gpu def get_model_recommendations(info: SystemInfo, selected_plugins: list[str]) -> list[ModelRecommendation]: """ Gibt Modell-Empfehlungen basierend auf Hardware zurueck. Beruecksichtigt: RAM, Festplatte, GPU/VRAM, ausgewaehlte Plugins. """ recs: list[ModelRecommendation] = [] ram = info.ram_total_mb disk = info.disk_free_mb vram = info.gpu.vram_mb has_gpu = info.gpu.cuda_available # --- STT-Modelle --- if "stt_vosk" in selected_plugins: # Vosk Klein (45MB, laeuft ueberall) rec = ModelRecommendation( name="Vosk STT (Deutsch, klein)", description="vosk-model-small-de-0.15 — schnell, geringer Speicherbedarf", size_mb=45, min_ram_mb=256, ) rec.recommended = disk > 100 and ram >= 256 rec.reason = "Empfohlen — klein und schnell" if rec.recommended else "Wenig Speicher" recs.append(rec) # Vosk Gross (1.9GB, braucht mehr RAM) rec = ModelRecommendation( name="Vosk STT (Deutsch, gross)", description="vosk-model-de-0.21 — hoehere Genauigkeit, mehr Speicher", size_mb=1900, min_ram_mb=2048, ) rec.recommended = disk > 2500 and ram >= 4096 if rec.recommended: rec.reason = "Empfohlen — genuegend RAM und Speicherplatz" elif ram < 4096: rec.reason = f"Nicht empfohlen — benoetigt 4GB RAM (verfuegbar: {ram}MB)" elif disk <= 2500: rec.reason = f"Nicht empfohlen — benoetigt 2.5GB Speicher (frei: {disk // 1024}GB)" recs.append(rec) if "stt_whisper" in selected_plugins: # Whisper Modelle — GPU-abhaengig whisper_models = [ ("tiny", 39, 1024, 0, "Schnell, niedrige Genauigkeit"), ("base", 139, 1024, 0, "Guter Kompromiss"), ("small", 461, 2048, 2048, "Hohe Genauigkeit"), ("medium", 1457, 4096, 4096, "Sehr hohe Genauigkeit"), ] for model_name, size, min_r, min_v, desc in whisper_models: rec = ModelRecommendation( name=f"Whisper STT ({model_name})", description=f"whisper-{model_name} — {desc}", size_mb=size, requires_gpu=min_v > 0, min_ram_mb=min_r, min_vram_mb=min_v, ) if min_v > 0 and not has_gpu: rec.recommended = False rec.reason = "Benoetigt GPU — keine CUDA-GPU erkannt" elif min_v > 0 and vram < min_v: rec.recommended = False rec.reason = f"Benoetigt {min_v}MB VRAM (verfuegbar: {vram}MB)" elif ram < min_r: rec.recommended = False rec.reason = f"Benoetigt {min_r}MB RAM (verfuegbar: {ram}MB)" elif disk < size + 500: rec.recommended = False rec.reason = f"Zu wenig Speicherplatz (benoetigt {size}MB)" else: rec.recommended = True if has_gpu and min_v > 0: rec.reason = f"Empfohlen — GPU verfuegbar ({info.gpu.name})" else: rec.reason = "Empfohlen — CPU-Ausfuehrung" recs.append(rec) # --- TTS-Modelle --- if "tts_piper" in selected_plugins: rec = ModelRecommendation( name="Piper TTS (thorsten-medium)", description="de_DE-thorsten-medium — natuerliche deutsche Stimme", size_mb=61, min_ram_mb=256, ) rec.recommended = disk > 100 rec.reason = "Empfohlen — Standardstimme" if rec.recommended else "Wenig Speicher" recs.append(rec) if "tts_coqui" in selected_plugins: rec = ModelRecommendation( name="Coqui TTS (Tacotron2-DDC)", description="tts_models/de/thorsten/tacotron2-DDC — neuronale Stimme", size_mb=200, requires_gpu=True, min_ram_mb=2048, min_vram_mb=2048, ) if has_gpu and vram >= 2048: rec.recommended = True rec.reason = f"Empfohlen — GPU verfuegbar ({info.gpu.name})" elif has_gpu: rec.recommended = False rec.reason = f"Benoetigt 2GB VRAM (verfuegbar: {vram}MB)" else: rec.recommended = False rec.reason = "Benoetigt CUDA-GPU — CPU zu langsam fuer Echtzeit-TTS" recs.append(rec) # --- NLP/LLM --- if "nlp_llm" in selected_plugins: # Qwen 1.5B Q4 (1.1GB, braucht 2-3GB RAM) rec = ModelRecommendation( name="Qwen 2.5 1.5B (Q4_K_M)", description="qwen2.5-1.5b-instruct-q4_k_m.gguf — lokales LLM fuer Intent-Erkennung", size_mb=1060, min_ram_mb=3072, ) if ram >= 3072 and disk > 1500: rec.recommended = True if has_gpu: rec.reason = "Empfohlen — GPU-Beschleunigung moeglich" else: rec.reason = "Empfohlen — CPU-Ausfuehrung (~1-3s pro Anfrage)" elif ram < 3072: rec.recommended = False rec.reason = f"Benoetigt 3GB RAM (verfuegbar: {ram}MB) — Ollama-Backend als Alternative" elif disk <= 1500: rec.recommended = False rec.reason = f"Zu wenig Speicherplatz (benoetigt 1.5GB, frei: {disk // 1024}GB)" recs.append(rec) return recs def format_system_summary(info: SystemInfo) -> str: """Formatiert die System-Info als lesbaren Text.""" lines = [] # CPU if info.cpu_model: lines.append(f"CPU: {info.cpu_model} ({info.cpu_cores} Kerne)") elif info.cpu_cores: lines.append(f"CPU: {info.cpu_cores} Kerne") # RAM if info.ram_total_mb: ram_gb = info.ram_total_mb / 1024 lines.append(f"RAM: {ram_gb:.1f} GB") # Festplatte if info.disk_total_mb: total_gb = info.disk_total_mb / 1024 free_gb = info.disk_free_mb / 1024 lines.append(f"Festplatte: {free_gb:.1f} GB frei / {total_gb:.1f} GB gesamt") # GPU if info.gpu.cuda_available: vram_gb = info.gpu.vram_mb / 1024 lines.append(f"GPU: {info.gpu.name} ({vram_gb:.1f} GB VRAM, CUDA)") else: lines.append("GPU: Keine CUDA-GPU erkannt") # Raspberry Pi if info.is_raspberry_pi: lines.append("Plattform: Raspberry Pi") return "\n".join(lines)