| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- # -*- 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)
|