#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ EVA Mirror Space v5 - QWEN UPDATE + WHISPER FIX + QWEN3-ASR + GGUF - Qwen3 LLM: löscht alte fehlerhafte model_q4f16 Dateien, spiegelt ORT-GenAI Dateien - Whisper: löscht whisper-medium (EN-only) und altes whisper-tiny (2-Datei-System), spiegelt whisper-tiny + whisper-base im gemergten fp32-Zielformat - Qwen3-ASR-0.6B (ONNX): spiegelt das komplette Community-ONNX-Repo neu dazu - GGUF (neu in v5): legt llm/gguf/ an, spiegelt Q4_K_M-Quants von Qwen3.5-9B, Qwen3.5-0.8B (mit Fallback-Quellen), Qwen3-1.7B, Qwen3-0.6B und Qwen3-ASR-0.6B, plus mmproj-F16 für die multimodalen Qwen3.5-Modelle Läuft auf HF-Servern (Space). Kein lokales/mobiles Datenvolumen. """ import os import io import requests import gradio as gr from huggingface_hub import HfApi HF_TOKEN = os.environ.get("HF_TOKEN", "") ZIEL_REPO = "Lafu01/eva-assets" REPO_TYPE = "model" # ============================================================================= # TEIL 1: QWEN3 LLM (unverändert aus v3) # ============================================================================= QWEN_MODELLE = [ {"name": "qwen3-0.6b", "quelle": "onnx-community/Qwen3-0.6B-ONNX"}, {"name": "qwen3-1.7b", "quelle": "onnx-community/Qwen3-1.7B-ONNX"}, {"name": "qwen3-4b", "quelle": "onnx-community/Qwen3-4B-ONNX"}, ] QWEN_UNTERORDNER = "onnxruntime/cpu_and_mobile/cpu-int4-kld-block-128" QWEN_LOESCHEN = [ "llm/qwen3-0.6b/model_q4f16.onnx", "llm/qwen3-0.6b/model_q4f16.onnx_data", "llm/qwen3-1.7b/model_q4f16.onnx", "llm/qwen3-1.7b/model_q4f16.onnx_data", "llm/qwen3-4b/model_q4f16.onnx", "llm/qwen3-4b/model_q4f16.onnx_data", ] # ============================================================================= # TEIL 2: WHISPER (neu in v4) # ============================================================================= # Alte, falsche Whisper-Dateien werden per Prefix gefunden und gelöscht # (nicht hart codiert, da die genauen Dateinamen im Ziel-Repo variieren) WHISPER_LOESCH_PREFIXE = [ "stt/whisper-medium/", # EN-only, wird nicht mehr gebraucht "stt/whisper/Whisper_tiny(FB32)/", # altes 2-Datei-System (falsch) ] # Nur die GEMERGTE fp32-Variante (Ziel-System) - kein altes 2-Datei-System, # keine fp16/int8/q4/bnb4/uint8 Nebenvarianten WHISPER_ONNX_DATEIEN = ["encoder_model.onnx", "decoder_model_merged.onnx"] WHISPER_ROOT_DATEIEN = [ "added_tokens.json", "config.json", "generation_config.json", "merges.txt", "normalizer.json", "preprocessor_config.json", "special_tokens_map.json", "tokenizer.json", "tokenizer_config.json", "vocab.json", ] WHISPER_MODELLE = [ {"name": "whisper-tiny", "quelle": "onnx-community/whisper-tiny", "ziel_ordner": "stt/whisper-tiny"}, {"name": "whisper-base", "quelle": "onnx-community/whisper-base", "ziel_ordner": "stt/whisper-base"}, ] # ============================================================================= # TEIL 3: QWEN3-ASR (neu in v4) # ============================================================================= QWEN_ASR_QUELLE = "baicai1145/Qwen3-ASR-0.6B-ONNX" QWEN_ASR_ZIEL_ORDNER = "llm/qwen3-asr-0.6b" # ============================================================================= # TEIL 4: GGUF (neu in v5) - alle in Q4_K_M # ============================================================================= GGUF_ZIEL_PREFIX = "llm/gguf" MMPROJ_PRAEZISION = "f16" # F16 statt BF16: kein natives BF16 auf Snapdragon Gen 1 # Pro Modell: Liste von Quellen (erste, die eine passende Datei hat, gewinnt). # "mmproj" = True nur bei den multimodalen Qwen3.5-Modellen. GGUF_MODELLE = [ { "name": "qwen3.5-9b", "quellen": ["unsloth/Qwen3.5-9B-GGUF"], "mmproj": True, }, { "name": "qwen3.5-0.8b", "quellen": [ "unsloth/Qwen3.5-0.8B-GGUF", "bartowski/Qwen_Qwen3.5-0.8B-GGUF", "mradermacher/Qwen3.5-0.8B-GGUF", ], "mmproj": True, }, { "name": "qwen3-1.7b", "quellen": ["unsloth/Qwen3-1.7B-GGUF"], "mmproj": False, }, { "name": "qwen3-0.6b", "quellen": ["unsloth/Qwen3-0.6B-GGUF"], "mmproj": False, }, { "name": "qwen3-asr-0.6b", # bereits als Q4_K_M vor-quantisiert von OpenVoiceOS, fuer Edge/CPU gebaut "quellen": ["OpenVoiceOS/qwen3-asr-0.6b-q4-k-m"], "mmproj": False, }, ] def datei_nach_muster_finden(dateien, enthaelt, ausschliessen=None): """Sucht in einer Dateiliste (case-insensitive) nach einem Treffer. Nutzt Wortgrenzen fuer die Praezisions-Tokens (f16/bf16/f32 etc.), damit 'f16' NICHT versehentlich in 'bf16' matcht (das war ein Bug bis v5).""" import re ausschliessen = ausschliessen or [] def token_passt(token, dateiname_klein): # Grenzen: kein Buchstabe/Ziffer direkt davor oder danach pattern = r'(? F16 Bugfix) # ============================================================================= def mmproj_korrigieren(token_eingabe): """Loescht faelschlich geholte mmproj-BF16.gguf Dateien (Bug: 'f16' matchte als Teilstring von 'bf16') und holt danach mit dem gefixten Musterabgleich die korrekte mmproj-F16.gguf nach.""" token = HF_TOKEN or token_eingabe.strip() if not token: return "FEHLER: Kein Token vorhanden." api = HfApi(token=token) log = [] try: vorhanden = set(api.list_repo_files(ZIEL_REPO, repo_type=REPO_TYPE, token=token)) except Exception as e: return f"FEHLER: Repo nicht erreichbar — {e}" log.append(f"Repo: {ZIEL_REPO} | {len(vorhanden)} Dateien vorhanden") log.append("=" * 60) log.append("\n🗑️ SCHRITT 1: Loesche falsche mmproj-BF16.gguf (Bug-Fund)...") betroffene_ordner = [m["name"] for m in GGUF_MODELLE if m["mmproj"]] for name in betroffene_ordner: ziel_ordner = f"{GGUF_ZIEL_PREFIX}/{name}" falsch = [f for f in vorhanden if f.startswith(ziel_ordner + "/") and "bf16" in f.lower() and "mmproj" in f.lower()] for pfad in falsch: try: api.delete_file(path_in_repo=pfad, repo_id=ZIEL_REPO, repo_type=REPO_TYPE, token=token) log.append(f" GELÖSCHT: {pfad}") vorhanden.discard(pfad) except Exception as e: log.append(f" Löschen fehlgeschlagen: {pfad} — {e}") if not falsch: log.append(f" SKIP (keine falsche BF16-Datei in {ziel_ordner}/)") log.append("\n📦 SCHRITT 2: Hole korrekte mmproj-F16.gguf nach (gefixter Musterabgleich)...") for modell in GGUF_MODELLE: if not modell["mmproj"]: continue name = modell["name"] ziel_ordner = f"{GGUF_ZIEL_PREFIX}/{name}" log.append(f"\n [{name.upper()}]") for quelle in modell["quellen"]: try: quell_dateien = api.list_repo_files(quelle, repo_type="model") except Exception as e: log.append(f" ⚠️ Quelle {quelle} nicht erreichbar ({e}) - versuche naechste...") continue mmproj_datei = datei_nach_muster_finden(quell_dateien, ["mmproj", MMPROJ_PRAEZISION]) if not mmproj_datei: log.append(f" ⚠️ Kein echtes {MMPROJ_PRAEZISION} in {quelle} gefunden - versuche naechste Quelle...") continue ziel_pfad = f"{ziel_ordner}/{mmproj_datei}" if ziel_pfad in vorhanden: log.append(f" SKIP (bereits vorhanden): {mmproj_datei}") else: url = hf_url(quelle, mmproj_datei) log.append(f" Kopiere: {mmproj_datei} ...") ergebnis = stream_kopieren(api, url, ziel_pfad, token) log.append(f" {ergebnis}") break log.append("\n" + "=" * 60) log.append("✅ mmproj-Korrektur abgeschlossen!") return "\n".join(log) # ============================================================================= # Ablauf: Alles nacheinander # ============================================================================= def alles_starten(token_eingabe): teile = [ qwen_llm_starten(token_eingabe), whisper_starten(token_eingabe), qwen_asr_starten(token_eingabe), gguf_starten(token_eingabe), ] return ("\n\n" + "#" * 60 + "\n\n").join(teile) # ============================================================================= # Gradio GUI # ============================================================================= with gr.Blocks(title="EVA Mirror Fixer v5") as demo: gr.Markdown("## 🔧 EVA Mirror Fixer v5\nQwen3-LLM-Fix, Whisper-Fix (tiny+base, gemergt), Qwen3-ASR und GGUF-Modelle (Q4_K_M).") token_feld = gr.Textbox( label="HF Token (Write)", placeholder="hf_... (nicht nötig wenn als Space-Secret hinterlegt)", type="password" ) with gr.Row(): qwen_btn = gr.Button("1️⃣ Qwen3 LLM Fix", variant="secondary") whisper_btn = gr.Button("2️⃣ Whisper Fix", variant="secondary") asr_btn = gr.Button("3️⃣ Qwen3-ASR hinzufügen", variant="secondary") gguf_btn = gr.Button("4️⃣ GGUF hinzufügen", variant="secondary") mmproj_fix_btn = gr.Button("🩹 mmproj BF16→F16 Korrektur (9B + 0.8B)", variant="stop") alles_btn = gr.Button("🚀 Alles nacheinander ausführen", variant="primary") ausgabe = gr.Textbox(label="Aktivitäts-Log", lines=30) qwen_btn.click(fn=qwen_llm_starten, inputs=token_feld, outputs=ausgabe) whisper_btn.click(fn=whisper_starten, inputs=token_feld, outputs=ausgabe) asr_btn.click(fn=qwen_asr_starten, inputs=token_feld, outputs=ausgabe) gguf_btn.click(fn=gguf_starten, inputs=token_feld, outputs=ausgabe) mmproj_fix_btn.click(fn=mmproj_korrigieren, inputs=token_feld, outputs=ausgabe) alles_btn.click(fn=alles_starten, inputs=token_feld, outputs=ausgabe) demo.launch()