system_info.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. # -*- coding: utf-8 -*-
  2. """
  3. Hardware-Erkennung fuer Modell-Empfehlungen.
  4. Erkennt Festplattenspeicher, RAM, GPU/VRAM und gibt
  5. passende Modell-Empfehlungen zurueck.
  6. """
  7. from __future__ import annotations
  8. import os
  9. import shutil
  10. import subprocess
  11. from dataclasses import dataclass, field
  12. @dataclass
  13. class GpuInfo:
  14. """GPU-Informationen."""
  15. name: str = ""
  16. vram_mb: int = 0
  17. cuda_available: bool = False
  18. driver: str = ""
  19. @dataclass
  20. class SystemInfo:
  21. """Erkannte System-Hardware."""
  22. ram_total_mb: int = 0
  23. disk_free_mb: int = 0
  24. disk_total_mb: int = 0
  25. gpu: GpuInfo = field(default_factory=GpuInfo)
  26. cpu_model: str = ""
  27. cpu_cores: int = 0
  28. is_raspberry_pi: bool = False
  29. @dataclass
  30. class ModelRecommendation:
  31. """Empfehlung fuer ein Modell."""
  32. name: str
  33. description: str
  34. size_mb: int
  35. requires_gpu: bool = False
  36. min_ram_mb: int = 0
  37. min_vram_mb: int = 0
  38. recommended: bool = False
  39. reason: str = ""
  40. def detect_system() -> SystemInfo:
  41. """Erkennt die Hardware des Systems."""
  42. info = SystemInfo()
  43. # RAM
  44. try:
  45. with open("/proc/meminfo") as f:
  46. for line in f:
  47. if line.startswith("MemTotal:"):
  48. kb = int(line.split()[1])
  49. info.ram_total_mb = kb // 1024
  50. break
  51. except (OSError, ValueError):
  52. pass
  53. # Festplatte (Zielpartition)
  54. try:
  55. usage = shutil.disk_usage("/")
  56. info.disk_free_mb = usage.free // (1024 * 1024)
  57. info.disk_total_mb = usage.total // (1024 * 1024)
  58. except OSError:
  59. pass
  60. # CPU
  61. try:
  62. with open("/proc/cpuinfo") as f:
  63. cores = 0
  64. for line in f:
  65. if line.startswith("model name"):
  66. info.cpu_model = line.split(":", 1)[1].strip()
  67. if line.startswith("processor"):
  68. cores += 1
  69. info.cpu_cores = cores
  70. except (OSError, ValueError):
  71. pass
  72. # Raspberry Pi Erkennung
  73. try:
  74. with open("/proc/device-tree/model") as f:
  75. model = f.read().lower()
  76. info.is_raspberry_pi = "raspberry" in model
  77. except OSError:
  78. pass
  79. # GPU / CUDA
  80. info.gpu = _detect_gpu()
  81. return info
  82. def _detect_gpu() -> GpuInfo:
  83. """Erkennt GPU und VRAM."""
  84. gpu = GpuInfo()
  85. # NVIDIA GPU via nvidia-smi
  86. try:
  87. result = subprocess.run(
  88. ["nvidia-smi", "--query-gpu=name,memory.total,driver_version",
  89. "--format=csv,noheader,nounits"],
  90. capture_output=True, text=True, timeout=5,
  91. )
  92. if result.returncode == 0 and result.stdout.strip():
  93. parts = result.stdout.strip().split(",")
  94. if len(parts) >= 3:
  95. gpu.name = parts[0].strip()
  96. try:
  97. gpu.vram_mb = int(float(parts[1].strip()))
  98. except ValueError:
  99. pass
  100. gpu.driver = parts[2].strip()
  101. gpu.cuda_available = True
  102. return gpu
  103. except (FileNotFoundError, subprocess.TimeoutExpired):
  104. pass
  105. # PyTorch CUDA Check (falls installiert)
  106. try:
  107. result = subprocess.run(
  108. ["python3", "-c",
  109. "import torch; print(torch.cuda.is_available()); "
  110. "print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else ''); "
  111. "print(torch.cuda.get_device_properties(0).total_mem // 1048576 "
  112. "if torch.cuda.is_available() else 0)"],
  113. capture_output=True, text=True, timeout=10,
  114. )
  115. if result.returncode == 0:
  116. lines = result.stdout.strip().split("\n")
  117. if len(lines) >= 3 and lines[0] == "True":
  118. gpu.cuda_available = True
  119. gpu.name = lines[1] or gpu.name
  120. try:
  121. gpu.vram_mb = int(lines[2]) or gpu.vram_mb
  122. except ValueError:
  123. pass
  124. except (FileNotFoundError, subprocess.TimeoutExpired):
  125. pass
  126. # Fallback: /proc/driver/nvidia/gpus
  127. if not gpu.name:
  128. try:
  129. for entry in os.listdir("/proc/driver/nvidia/gpus"):
  130. with open(f"/proc/driver/nvidia/gpus/{entry}/information") as f:
  131. for line in f:
  132. if "Model:" in line:
  133. gpu.name = line.split(":", 1)[1].strip()
  134. break
  135. except (OSError, StopIteration):
  136. pass
  137. return gpu
  138. def get_model_recommendations(info: SystemInfo, selected_plugins: list[str]) -> list[ModelRecommendation]:
  139. """
  140. Gibt Modell-Empfehlungen basierend auf Hardware zurueck.
  141. Beruecksichtigt: RAM, Festplatte, GPU/VRAM, ausgewaehlte Plugins.
  142. """
  143. recs: list[ModelRecommendation] = []
  144. ram = info.ram_total_mb
  145. disk = info.disk_free_mb
  146. vram = info.gpu.vram_mb
  147. has_gpu = info.gpu.cuda_available
  148. # --- STT-Modelle ---
  149. if "stt_vosk" in selected_plugins:
  150. # Vosk Klein (45MB, laeuft ueberall)
  151. rec = ModelRecommendation(
  152. name="Vosk STT (Deutsch, klein)",
  153. description="vosk-model-small-de-0.15 — schnell, geringer Speicherbedarf",
  154. size_mb=45,
  155. min_ram_mb=256,
  156. )
  157. rec.recommended = disk > 100 and ram >= 256
  158. rec.reason = "Empfohlen — klein und schnell" if rec.recommended else "Wenig Speicher"
  159. recs.append(rec)
  160. # Vosk Gross (1.9GB, braucht mehr RAM)
  161. rec = ModelRecommendation(
  162. name="Vosk STT (Deutsch, gross)",
  163. description="vosk-model-de-0.21 — hoehere Genauigkeit, mehr Speicher",
  164. size_mb=1900,
  165. min_ram_mb=2048,
  166. )
  167. rec.recommended = disk > 2500 and ram >= 4096
  168. if rec.recommended:
  169. rec.reason = "Empfohlen — genuegend RAM und Speicherplatz"
  170. elif ram < 4096:
  171. rec.reason = f"Nicht empfohlen — benoetigt 4GB RAM (verfuegbar: {ram}MB)"
  172. elif disk <= 2500:
  173. rec.reason = f"Nicht empfohlen — benoetigt 2.5GB Speicher (frei: {disk // 1024}GB)"
  174. recs.append(rec)
  175. if "stt_whisper" in selected_plugins:
  176. # Whisper Modelle — GPU-abhaengig
  177. whisper_models = [
  178. ("tiny", 39, 1024, 0, "Schnell, niedrige Genauigkeit"),
  179. ("base", 139, 1024, 0, "Guter Kompromiss"),
  180. ("small", 461, 2048, 2048, "Hohe Genauigkeit"),
  181. ("medium", 1457, 4096, 4096, "Sehr hohe Genauigkeit"),
  182. ]
  183. for model_name, size, min_r, min_v, desc in whisper_models:
  184. rec = ModelRecommendation(
  185. name=f"Whisper STT ({model_name})",
  186. description=f"whisper-{model_name} — {desc}",
  187. size_mb=size,
  188. requires_gpu=min_v > 0,
  189. min_ram_mb=min_r,
  190. min_vram_mb=min_v,
  191. )
  192. if min_v > 0 and not has_gpu:
  193. rec.recommended = False
  194. rec.reason = "Benoetigt GPU — keine CUDA-GPU erkannt"
  195. elif min_v > 0 and vram < min_v:
  196. rec.recommended = False
  197. rec.reason = f"Benoetigt {min_v}MB VRAM (verfuegbar: {vram}MB)"
  198. elif ram < min_r:
  199. rec.recommended = False
  200. rec.reason = f"Benoetigt {min_r}MB RAM (verfuegbar: {ram}MB)"
  201. elif disk < size + 500:
  202. rec.recommended = False
  203. rec.reason = f"Zu wenig Speicherplatz (benoetigt {size}MB)"
  204. else:
  205. rec.recommended = True
  206. if has_gpu and min_v > 0:
  207. rec.reason = f"Empfohlen — GPU verfuegbar ({info.gpu.name})"
  208. else:
  209. rec.reason = "Empfohlen — CPU-Ausfuehrung"
  210. recs.append(rec)
  211. # --- TTS-Modelle ---
  212. if "tts_piper" in selected_plugins:
  213. rec = ModelRecommendation(
  214. name="Piper TTS (thorsten-medium)",
  215. description="de_DE-thorsten-medium — natuerliche deutsche Stimme",
  216. size_mb=61,
  217. min_ram_mb=256,
  218. )
  219. rec.recommended = disk > 100
  220. rec.reason = "Empfohlen — Standardstimme" if rec.recommended else "Wenig Speicher"
  221. recs.append(rec)
  222. if "tts_coqui" in selected_plugins:
  223. rec = ModelRecommendation(
  224. name="Coqui TTS (Tacotron2-DDC)",
  225. description="tts_models/de/thorsten/tacotron2-DDC — neuronale Stimme",
  226. size_mb=200,
  227. requires_gpu=True,
  228. min_ram_mb=2048,
  229. min_vram_mb=2048,
  230. )
  231. if has_gpu and vram >= 2048:
  232. rec.recommended = True
  233. rec.reason = f"Empfohlen — GPU verfuegbar ({info.gpu.name})"
  234. elif has_gpu:
  235. rec.recommended = False
  236. rec.reason = f"Benoetigt 2GB VRAM (verfuegbar: {vram}MB)"
  237. else:
  238. rec.recommended = False
  239. rec.reason = "Benoetigt CUDA-GPU — CPU zu langsam fuer Echtzeit-TTS"
  240. recs.append(rec)
  241. # --- NLP/LLM ---
  242. if "nlp_llm" in selected_plugins:
  243. # Qwen 1.5B Q4 (1.1GB, braucht 2-3GB RAM)
  244. rec = ModelRecommendation(
  245. name="Qwen 2.5 1.5B (Q4_K_M)",
  246. description="qwen2.5-1.5b-instruct-q4_k_m.gguf — lokales LLM fuer Intent-Erkennung",
  247. size_mb=1060,
  248. min_ram_mb=3072,
  249. )
  250. if ram >= 3072 and disk > 1500:
  251. rec.recommended = True
  252. if has_gpu:
  253. rec.reason = "Empfohlen — GPU-Beschleunigung moeglich"
  254. else:
  255. rec.reason = "Empfohlen — CPU-Ausfuehrung (~1-3s pro Anfrage)"
  256. elif ram < 3072:
  257. rec.recommended = False
  258. rec.reason = f"Benoetigt 3GB RAM (verfuegbar: {ram}MB) — Ollama-Backend als Alternative"
  259. elif disk <= 1500:
  260. rec.recommended = False
  261. rec.reason = f"Zu wenig Speicherplatz (benoetigt 1.5GB, frei: {disk // 1024}GB)"
  262. recs.append(rec)
  263. return recs
  264. def format_system_summary(info: SystemInfo) -> str:
  265. """Formatiert die System-Info als lesbaren Text."""
  266. lines = []
  267. # CPU
  268. if info.cpu_model:
  269. lines.append(f"CPU: {info.cpu_model} ({info.cpu_cores} Kerne)")
  270. elif info.cpu_cores:
  271. lines.append(f"CPU: {info.cpu_cores} Kerne")
  272. # RAM
  273. if info.ram_total_mb:
  274. ram_gb = info.ram_total_mb / 1024
  275. lines.append(f"RAM: {ram_gb:.1f} GB")
  276. # Festplatte
  277. if info.disk_total_mb:
  278. total_gb = info.disk_total_mb / 1024
  279. free_gb = info.disk_free_mb / 1024
  280. lines.append(f"Festplatte: {free_gb:.1f} GB frei / {total_gb:.1f} GB gesamt")
  281. # GPU
  282. if info.gpu.cuda_available:
  283. vram_gb = info.gpu.vram_mb / 1024
  284. lines.append(f"GPU: {info.gpu.name} ({vram_gb:.1f} GB VRAM, CUDA)")
  285. else:
  286. lines.append("GPU: Keine CUDA-GPU erkannt")
  287. # Raspberry Pi
  288. if info.is_raspberry_pi:
  289. lines.append("Plattform: Raspberry Pi")
  290. return "\n".join(lines)