Zum Inhalt

Eigene KI

📅 Erstellt: 14.09.2026 🔄 Aktualisiert: 06.10.2026

Neuer Ansatz 14.09.2026

Hardware-Kontext

  • System: CachyOS (Arch Linux)
  • CPU: Intel i7-10700K
  • RAM: 32 GB DDR4
  • GPU: NVIDIA RTX 4080 (16 GB VRAM)
  • Ziel: Lokale KI mit Internet-Recherche, dauerhaftem Gedächtnis und Offline-Betrieb.

Ollama

Ollama dient als Backend, um KI-Modelle direkt auf der NVIDIA GPU (via CUDA) auszuführen.

Installation:

sudo pacman -S ollama-cuda

Dienst aktivieren:

sudo systemctl enable --now ollama

Test:
Das Modell qwen2.5:14b wurde via CLI getestet. Es ist ideal für die 16 GB VRAM, da es exzellent in Logik, Coding und deutscher Sprache ist.

ollama run qwen2.5:14b

Ich lade außerdem testweise noch folgende Modelle herunter:

  • qwen2.5:14b - bereits geladen
  • qwen3.8:27b
  • qwen3-vl:4b-instruct
  • qwen3-vl:8b-instruct
  • qwen2.5-coder:14b
  • deepseek-coder-v2
  • deepseek-r1:14b
  • nomic-embed-text
  • FLUX.1-schnell - über ComfyUi

ComfyUI für Bildgenerierung

Da ich mein System sauber halten will von komischen Abhängigkeiten der Python-Installation verwende ich kein pip, sondern pipx. Mit ComfyUI werde ich nun auch uv testen.

uv installieren:
yay -S uv

ComfyUI installieren:
git clone https://github.com/comfyanonymous/ComfyUI.git
cd ComfyUI

Einrichtung

Virtuelle Umgebung mit uv einrichten:

❯ uv venv --python 3.11
Using CPython 3.11.16
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate.fish

~/git/ComfyUI master
❯ source .venv/bin/activate.fish 

~/git/ComfyUI master
ComfyUI ❯ uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
Resolved 26 packages in 990ms
Prepared 26 packages in 1m 13s
Installed 26 packages in 86ms
...

Abhängigkeiten installieren:

ComfyUI ❯ uv pip install -r requirements.txt
Resolved 96 packages in 660ms
Prepared 70 packages in 17.98s
Installed 70 packages in 72ms
...

Mit hf (HuggingFace) Modelle suchen:
hf models ls --search "text encoders"

FLUX.1 herunterladen:

ComfyUI ❯ uv pip install "huggingface_hub[cli]"
Checked 1 package in 1ms

ComfyUI ❯ hf download Comfy-Org/flux1-schnell flux1-schnell-fp8.safetensors --local-dir models/checkpoints
flux1-schnell-fp8.safetensors: downloading bytes: ██████████████████████████████████████████████████████████████████████████████████████████████████| 17.2GB, 39.1MB/s  
flux1-schnell-fp8.safetensors: reconstructing file: 100%|██████████████████████████████████████████████████████████████████████████████████| 17.2GB / 17.2GB,  148MB/s  
✓ Downloaded
  path: /home/marquisor/git/ComfyUI/models/checkpoints/flux1-schnell-fp8.safetensors

ComfyUI ❯ hf download comfyanonymous/flux_text_encoders clip_l.safetensors --local-dir models/clip
Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
clip_l.safetensors: downloading bytes: █████████████████████████████████████████████████████████████████████████████████████████████████████████████|  246MB, 17.0MB/s  
clip_l.safetensors: reconstructing file: 100%|█████████████████████████████████████████████████████████████████████████████████████████████|  246MB /  246MB, 20.2MB/s  
✓ Downloaded
  path: /home/marquisor/git/ComfyUI/models/clip/clip_l.safetensors

ComfyUI ❯ hf download comfyanonymous/flux_text_encoders t5xxl_fp8_e4m3fn.safetensors --local-dir models/clip
t5xxl_fp8_e4m3fn.safetensors: downloading bytes: ███████████████████████████████████████████████████████████████████████████████████████████████████| 4.77GB, 39.1MB/s  
t5xxl_fp8_e4m3fn.safetensors: reconstructing file: 100%|███████████████████████████████████████████████████████████████████████████████████| 4.89GB / 4.89GB,  143MB/s  
✓ Downloaded
  path: /home/marquisor/git/ComfyUI/models/clip/t5xxl_fp8_e4m3fn.safetensors

ComfyUI ❯ uv pip install --upgrade comfy-kitchen
Resolved 1 package in 52ms
Checked 1 package in 0.03ms

ComfyUI ❯ uv pip install --upgrade -r requirements.txt
Resolved 103 packages in 267ms
Prepared 25 packages in 1m 16s
Uninstalled 6 packages in 97ms
Installed 25 packages in 79ms
...

ComfyUI ❯ uv pip install --upgrade torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
Resolved 33 packages in 738ms
Checked 33 packages in 0.22ms

ComfyUI ❯ python main.py --listen 127.0.0.1 --enable-cors-header
[INFO] setup plugin alembic.autogenerate.schemas
...
[INFO] Starting server
[INFO] To see the GUI go to: http://127.0.0.1:8188

Arbeiten mit dem Webinterface

Auf die Oberfläche im Browser gehen URL: http://127.0.0.1:8188:
806f800b28b0e4249eb5f8eb8154ff01.png
Das Fenster schließen oder ESC drücken. Dann landet man auf dem leeren Node-Graph.

Checkpoint unter Models hinzufügen:
1a7a19371dce5f3f3ecba6a2467eae56.png

Rechtsclick -> Add Node -> model -> loaders -> Load CLIP (Dual):
e889185032293e2d2cbd9c15252911cf.png

Folgende Einstellungen machen:
dd3fd26a7d15a9dc00a77f725d0e2004.png,

Mit der linken Maustaste am Clip-Punkt klicken und gedrückt halten und auf freie Fläche ziehen, CLIP Text Encode (Prompt) auswählen und platzieren:
79c20e109f817117d12a3948af4bf7a6.png

Einen zweiten CLIP Text Encode (Prompt) erstellen, Rechtsklick auf den bestehenden, Clone und platzieren, sowie mit dem Clip-Knoten des Load CLIP (Dual) verbinden:
dcde023b0a5577b20cc2c1ab3380501b.png
Der obere Knoten ist für den Positiv-Prompt, der untere für den Negativ-Prompt (kann bei FLUX leer bleiben)

Doppelklick auf leere Fläche "Empty Latent Image" und platzieren, sowie auf 1024x1024 setzen:
3a2097d2ef9caa83db14c5718a776b9d.png

Doppelklick auf leere Fläche "KSampler" platzieren und die Knoten verbinden wie gezeigt:
085e69ce7ed1f35aed13682ad712b7a3.png

"VAE Decode" und "Save Image" hinzufügen, Roter Punkt VAE aus Load Checkpoint -> vae am VAE Decode, Rosa Punkt LATENT aus KSampler -> rosa Punkt samples am VAE Decode, blauer Punkt IMAGE aus VAE Decode -> blauer Punkt images am Save Image:
0c64797a5a94ac77a46c5fd2b7a5b5c9.png
So sieht der fertige Graph aus

Ergebnisse

Auf Run drücken und Feuer frei! Bei Erfolg erscheint bei "Save Image" das Bild:
5db180ba377155d312cb2102ac3394bf.png

Und hier in groß:
ComfyUI_00001_.png

Die Arbeitsfläche kann man abspeichern und exportieren über das "C"omfy Menü.
ComfyUI speichert außerdem in jedem erzeugten Bild im Ordner ComfyUI/output/ automatisch das vollständige Knotensystem als Metadaten. Man kann diese .png Datei dann einfach in den Graph ziehen und das Schema wird übernommen.

Modelle nachinstallieren

Genauere Ergebnisse kann man mit FLUX.1-dev erhalten und im KSampler den Wert steps von 4 auf 25 oder 30 stellen. Beim FLUX1-schnell Modell bringen steps >4 nichts, sondern verfälschen das Bild nur.
Die Ausführungszeit verdoppelt sich bei mir dabei ungefähr von 3-4 Sekunden auf ca. 8 Sekunden.

In ComfyUI/models/checkpoints herunterladen:
wget -c https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors

In der WebUI dann bei Models refreshen und F5 drücken. Bei "Load Checkpoint" dann einfach ein neues Modell wählen und die steps auf 25 oder 30 stellen.

Höhere Textpräzision mit FP16 vom T5-XXL Modell in ComfyUI/models/clip herunterladen:
wget -c https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp16.safetensors

Stile (LoRAs) und vieles mehr kann man auf HuggingFace herunterladen.

Beispiel Wasserfarbe/Aquarell:
ComfyUI_00073_.png

AnythingLLM

Für Arch Linux habe ich noch kein natives Paket, weder im Repo oder AUR, noch ein Flatpak gefunden, daher nehme ich ein AppImage.

Herunterladen:
curl -L https://cdn.anythingllm.com/latest/AnythingLLMDesktop.AppImage -o AnythingLLM.AppImage

Ausführbar machen:
chmod +x AnythingLLM.AppImage

AnythingLLM starten, verschiedene Abhängigkeiten werden automatisch heruntergeladen:
./AnythingLLM.AppImage

AnythingLLM für lokale Verwendung mit Ollama manuell einrichten, Modell: qwen2.5:14b für meine nvidia GPU:
0a21184b4e93e642a638771ea36ebab5.png

Embedding Präferenz auf Provider Ollama und Model auf nomic-embed-text:latest setzen:
4dd9601187ab475732fe8ce21165a19f.png
Änderungen speichern nicht vergessen!

Agentenfähigkeiten -> Live-Websuche und -Browsing einschalten und Suchmaschine auf DuckDuckGo setzen:
0a804006b2771431e6f9d6d1701d94eb.png

Erinnerungen konfigurieren (rechts oben das Einstellungssymbol anklicken):
f3422f07d0aeb043b31f81d7c4ae8354.png

Bei Global sowas einstellen wie:
c754c767502fb76d0f48040c2fc4ee9e.png

Aider

Aider-Chat wird der Coding Agent.

Mit pipx

Abhängigkeiten installieren:
sudo pacman -S gcc-fortran openblas

Mit pipx aider-chat installieren:
pipx install aider-chat

Weitere Abhängigkeiten installieren:
pipx inject aider-chat pyaudioop

Auszug:

❯ pipx install aider-chat
  installed package aider-chat 0.86.2, installed using Python 3.14.7
  These apps are now available
    - aider
done! ✨ 🌟 ✨

Aider starten mit:
aider --model ollama_chat/qwen2.5-coder:14b

Sollte das nicht funktionieren geht noch der uv-Weg.

Mit uv

Da eventuell die abhängigen Bibliotheken von aider-chat noch nicht für Python 3.14.7 gebaut sind bzw. verfügbar sind, nehmen wir eine ältere Umgebung her:

❯ uv tool install --python 3.12 aider-chat
Resolved 108 packages in 138ms
Prepared 21 packages in 1.89s
Installed 108 packages in 121ms
 + aider-chat==0.86.2
 ...
 + zipp==3.23.0
Installed 1 executable: aider

Testumgebung

Aider läuft standardmäßig in dem Verzeichnis in dem es gestartet wird und arbeitet mit Git.
Daher erstelle ich für einen Testzweck ein Verzeichnis:
mkdir -p ~/AI/hardware_test

Ins Verzeichnis wechseln:
cd ~/AI/hardware_test

Git initialisieren:
git init

Aider starten mit:
aider --model ollama_chat/qwen2.5-coder:14b

Prompt: Erstelle eine Datei gpu_specs.md mit einer strukturierte Tabelle zur NVIDIA RTX 4080 Super (16 GB VRAM) im Vergleich zur RTX 4090. Gehe dabei auf Speicherbandbreite, CUDA-Cores, Stromverbrauch (TBP) und Eignung für lokale LLM-Inferenz ein.

Ergebnis gpu_specs.md innerhalb weniger Minuten:

GPU-Modell Speicherbandbreite CUDA-Cores TBP (Maximaler Typische Betriebsspannungsverbrauch) Eignung für lokale LLM-Inferenz
NVIDIA RTX 4080 Super (16 GB VRAM) 320 GB/s 9728 340 W Gut, aber begrenzt durch geringere Anzahl an CUDA-Cores und geringere VRAM
NVIDIA RTX 4090 (24 GB VRAM) 384 GB/s 16384 450 W Sehr gut, dank höherer Anzahl an CUDA-Cores und mehr VRAM
Erweitertes Modell

Mal mit dem großen 27B-Modell testen, 16-17 GB VRAM soll das belegen samt "minimaler" CPU-Auslagerung.

Ins Verzeichnis wechseln:
cd ~/AI/hardware_test

Aider mit 27B-Modell starten:
aider --model ollama_chat/qwen3.8:27b

Im Prompteingabefenster:
/add gpu_specs.md

Mit der schon erstellten gpu_specs.md kann weiter gearbeitet werden. Die Informationen sind ja bereits erstellt worden.

Prompt: Erweitere gpu_specs.md um eine detaillierte Gegenüberstellung der Chip-Architekturen (AD103 vs. AD102), der L2-Cache-Größen, der Tensor-Cores der 4. Generation und analysiere, warum die RTX 4080 Super für 14B-27B LLMs ein optimales Preis-Leistungs-Verhältnis auf Linux bietet.

OpenCode

OpenCode aus dem Arch Linux Repo installieren:
yay -S opencode-beta

Unbedingt diese beta laden, denn alles andere hat hier überhaupt nicht gescheit funktioniert!

Konfiguration von OpenCode v2 siehe z.B. offizielle Dokumentation

In einem Projektordner, z.B. ~/opencode eine opencode.jsonc erstellen:

{
  "$schema": "https://opencode.ai/config.json",
  "providers": {
    "ollama": {
          "name": "Ollama (local)",
      "settings": {
        "baseURL": "http://127.0.0.1:11434/v1",
        "apiKey": "{env:OLLAMA_API_KEY}",
      },
    },
  },
}

Dies definiert den Provider, mein lokales Ollama, bereits installiert im Schritt Eigene KI#Ollama

TextToSpeech (TTS)

Kokoro

Hier spiele ich mal mit Kokoro-TTS rum. Das ist Open-Source.
In meinem ~/Apps Ordner erstelle ich ein Verzeichnis kokoro-tts:
mkdir kokoro-tts && cd kokoro-tts

Python 3.11 Umgebung in diesem Verzeichnis:
uv venv --python 3.11

Umgebung für die momentane Fish-Shell aktivieren:
source .venv/bin/activate.fish

Pakete installieren:
uv pip install kokoro-onnx soundfile

Sprachdateien installieren:

python -c "
from huggingface_hub import hf_hub_download, list_repo_files
import shutil

repos = ['onnx-community/Kokoro-82M-ONNX', 'hexgrad/Kokoro-82M-ONNX']
found_repo, files = None, []

for r in repos:
    try:
        files = list_repo_files(r)
        found_repo = r
        break
    except Exception:
        continue

if not found_repo:
    raise RuntimeError('Kein ONNX-Repository gefunden.')

onnx_file = next((f for f in files if f.endswith('.onnx')), None)
voice_file = next((f for f in files if 'voices' in f), None)

print(f'Gefunden in {found_repo}: {onnx_file} & {voice_file}')

p1 = hf_hub_download(repo_id=found_repo, filename=onnx_file)
p2 = hf_hub_download(repo_id=found_repo, filename=voice_file)

shutil.copy(p1, 'kokoro-v0_19.onnx')
shutil.copy(p2, 'voices.bin')
print('Download erfolgreich abgeschlossen!')
"

Korrekte voices.bin nachladen:
wget -O voices.bin https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files/voices.bin

Das v1.0 Modell laden:

curl -L -o kokoro-v1.0.onnx https://huggingface.co/onnx-community/Kokoro-82M-ONNX/resolve/main/onnx/model.onnx
curl -L -o voices.bin https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files/voices.bin
curl -L -o config.json https://huggingface.co/hexgrad/Kokoro-82M/raw/main/config.json

Ein Testskript für englische Sprache amerikanisch wie britisch, test_kokoro.py:

import numpy as np

# Workaround: allow_pickle=True für kokoro-onnx aktivieren
_orig_load = np.load
np.load = lambda *args, **kwargs: _orig_load(
    *args, **dict(kwargs, allow_pickle=True)
)

from kokoro_onnx import Kokoro
import soundfile as sf

# Modell und Stimmen laden
kokoro = Kokoro("kokoro-v0_19.onnx", "voices.bin")

# Alle verfügbaren Stimmen im Terminal anzeigen lassen
print("Verfügbare Stimmen in deiner voices.bin:")
print(list(kokoro.voices.keys()))
print("-" * 50)

# US-Englisch Sprachausgabe ('af_bella' = American Female)
samples_us, sample_rate = kokoro.create(
    "Hello! Kokoro TTS is running incredibly fast on your CachyOS system.",
    voice="af_bella",
    speed=1.0,
    lang="en-us",
)
sf.write("output_us.wav", samples_us, sample_rate)

# UK-Englisch Sprachausgabe ('bm_george' = British Male)
samples_uk, sample_rate = kokoro.create(
    "And here is a very fine British voice, testing the system.",
    voice="bm_george",
    speed=1.0,
    lang="en-us", # v0.19 nutzt oft en-us als generelles Routing für Englisch
)
sf.write("output_uk.wav", samples_uk, sample_rate)

print("Audio-Dateien wurden erfolgreich generiert.")

Stimmen abspielen:
US: pw-play output_us.wav
UK: pw-play output_uk.wav

Fazit: Für englische Stimmen US wie UK eine sehr gute Qualität, wie ich finde!

XTTS-v2

Installation:

mkdir -p ~/Apps/xtts-v2 && cd ~/Apps/xtts-v2
uv venv --python 3.11
source .venv/bin/activate.fish

# PyTorch mit CUDA (RTX 4080 Super)
uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

# ToS setzen und Coqui TTS installieren
set -gx COQUI_TOS_AGREED 1
uv pip install TTS

Verifizierung:
python -c "import torch, TTS; print('CUDA:', torch.cuda.is_available(), 'TTS OK')"
Ergebnis muss lauten: CUDA: True TTS OK

Transformers 4.36.2 installieren:
uv pip install "transformers==4.36.2"

Testdatei test_xtts.py erstellen:

import torch
from TTS.api import TTS

device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Lade XTTS v2 auf {device}...")

tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device)

tts.tts_to_file(
    text="Hallo! XTTS läuft lokal auf deiner RTX 4080 Super im Apps-Ordner unter CachyOS.",
    speaker="Ana Florence",
    language="de",
    file_path="xtts_test.wav"
)
print("Fertig! xtts_test.wav wurde generiert.")

Skript ausführen:
python test_xtts.py

Beispiel anhören:
pw-play xtts_test.wav

Piper TTS

Unter Arch Linux (CachyOS) aus dem AUR installierbar:
yay -S piper-tts

<- hier fehlen noch die Modelle! ->

Beispiel einer smart_talk.py:

#!/usr/bin/env python3
import sys
import os
import subprocess
import requests
import re


def is_dense_data(text: str) -> bool:
    digits_and_symbols = len(re.findall(r'[\d°C/hPa.+-]', text))
    ratio = digits_and_symbols / max(len(text), 1)
    keywords = ["hPa", "Apparent temp", "Wet Bulb", "Humidex", "W/m²", "kts", "mm/min"]
    return ratio > 0.10 or any(k in text for k in keywords)


def clean_data_report(text: str) -> str:
    text = re.sub(r'0\.0\s*mm', 'null Millimeter', text, flags=re.IGNORECASE)
    text = re.sub(r'0\.0\s*kmh', 'null Kilometer pro Stunde', text, flags=re.IGNORECASE)
    text = re.sub(r'0\.0\s*kts', 'null Knoten', text, flags=re.IGNORECASE)
    text = re.sub(r'0\.0\s*W/m²', 'null Watt pro Quadratmeter', text, flags=re.IGNORECASE)
    text = re.sub(r'\b0\.0\b', 'null Komma null', text)
    text = re.sub(r'\b0,8\b|\b0\.8\b', 'null Komma acht', text)

    # Standard-Einheiten & Symbole glattziehen
    text = re.sub(r'(\d+)\.(\d+)', r'\1,\2', text)

    # Uhrzeiten: 06:00 -> 6 Uhr, 06:05 -> 6 Uhr 5
    text = re.sub(r'\b0?(\d{1,2}):(\d{2})\b', lambda m: f"{int(m.group(1))} Uhr" if m.group(2) == "00" else f"{int(m.group(1))} Uhr {int(m.group(2))}", text)

    text = re.sub(r'([A-Za-zäöüÄÖÜß]+)\s+(\d{1,2})\s+(\d{4})', r'\2. \1 \3', text)
    text = re.sub(r'(\d+)\s*[-–]\s*(\d+)', r'\1 bis \2', text)

    # Maßeinheiten fixen
    text = re.sub(r'°C', ' Grad Celsius', text)
    text = re.sub(r'°F', ' Grad Fahrenheit', text)
    text = re.sub(r'hPa', ' Hektopascal', text)
    text = re.sub(r'W/m²', ' Watt pro Quadratmeter', text)
    text = re.sub(r'kmh', ' Kilometer pro Stunde', text, flags=re.IGNORECASE)
    text = re.sub(r'kts', ' Knoten', text, flags=re.IGNORECASE)
    text = re.sub(r'mm/min', ' Millimeter pro Minute', text, flags=re.IGNORECASE)
    text = re.sub(r'mm/stunde|mm/h', ' Millimeter pro Stunde', text, flags=re.IGNORECASE)
    text = re.sub(r'mm', ' Millimeter', text)
    text = re.sub(r'in\.', ' Zoll', text)
    return text


def normalize_for_tts(text: str, force_raw: bool = False) -> str:
    text = re.sub(r'(\d+)\s*[-–]\s*(\d+)', r'\1 bis \2', text)

    if force_raw or is_dense_data(text):
        print("[Bypass] Deterministischer Regex-Modus aktiv (kein LLM).")
        return clean_data_report(text)

    prompt = f"""Schreibe den folgenden Text für eine Sprachausgabe um.
Wandele ALLE Zahlen in korrekte, vollständig ausgeschriebene deutsche Wörter um. KEINE Erfindungen, keine Einleitungen, kein Chinesisch!

MUSTER FÜR ZAHLEN UND KONTEXT:
1984 (als Jahr) -> neunzehnhundertvierundachtzig
1984€ (als Währung) -> eintausendneunhundertvierundachtzig Euro
2024 -> zweitausendvierundzwanzig
54 -> vierundfünfzig
15.4 -> fünfzehn Komma vier
ca. -> circa

WICHTIG:
- Beachte den Kontext! Währungen/Mengen ab 1000 als "eintausend...", Jahre vor 2000 als "neunzehnhundert...".
- Verschlucke NIEMALS die Zehnerstellen bei Jahreszahlen!
- Behalte den restlichen Text absolut unverändert bei.

Text: {text}
Ausgabe (NUR der bereinigte Text, keine Einleitung):"""

    try:
        resp = requests.post("http://localhost:11434/api/generate", json={
            "model": "qwen2.5:14b",
            "prompt": prompt,
            "stream": False,
            "options": {"temperature": 0.0}
        }, timeout=15)
        out = resp.json()["response"].strip()
        if any(ord(c) > 0x4E00 for c in out):
            return clean_data_report(text)
        return out
    except Exception:
        return clean_data_report(text)


def speak(raw_text: str, force_raw: bool = False):
    clean = normalize_for_tts(raw_text, force_raw=force_raw)
    clean = clean_data_report(clean)  # Safety net für Einheiten/Uhrzeiten
    print(f"\n[Gereinigter TTS-Text]: {clean}\n")

    model_path = os.path.expanduser("~/piper-tts/models/de_DE-thorsten-medium.onnx")
    out_path = "/tmp/tts_out.wav"

    if not os.path.exists(model_path):
        print(f"Fehler: Piper-Modell nicht gefunden unter {model_path}")
        return

    cmd = ["piper-tts", "--model", model_path, "--output_file", out_path]
    subprocess.run(cmd, input=clean.encode("utf-8"), check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    subprocess.run(["pw-play", out_path])


if __name__ == "__main__":
    args_raw = [a for a in sys.argv[1:] if a == "--raw"]
    force_raw = len(args_raw) > 0
    non_flag_args = [a for a in sys.argv[1:] if a != "--raw"]

    content = ""
    if non_flag_args:
        arg = non_flag_args[0]
        if os.path.isfile(arg):
            with open(arg, "r", encoding="utf-8") as f:
                content = f.read()
        else:
            content = arg
    elif not sys.stdin.isatty():
        content = sys.stdin.read()
    else:
        content = "Im Jahr 2026 waren 54 Leute da."

    if content.strip():
        speak(content, force_raw=force_raw)
    else:
        print("Kein Text zum Vorlesen gefunden.")

TBC

Was sind Tokens?

Tokens sind wie Klemmbausteine für Sprache: Feste Größe, standardisiert, optimale digitale Verarbeitungsmethode.

Einfache Wörter wie "Haus", "Baum", "Apfel" sind ein Klemmbaustein = 1 Token.
Längere Wörter werden zum Beispiel in Silben oder sinnvolle Wortteile zerlegt: "Speicherverwaltung" -> Speicher + ver + waltung = 3 Tokens.
Sonderzeichen, Satzzeichen, Zeilenumbrüche sind einzeln für sich auch jeweils 1 Token.

Warum Tokens?

Beim "dummen" PC geht es nicht um Sprache, sondern Verarbeitung von Daten. Daten verbrauchen Speicherplatz, ob auf der Festplatte oder im RAM. Feste Größen, handlich, wiederverwendbar sind blitzschnell zu verarbeiten, es muss nicht mehr auf die Größe oder den Inhalt an sich eingegangen werden, sondern nur noch auf das Token.

Jeder Baustein (Token) wird in eine Zahlen ID übersetzt, diese hat dann 4 Bytes (32-Bit Integer). Damit hat man zum Beispiel schon gegenüber dem Wort "Speicher" - Länge 8 Buchstaben, das wären 8 Bytes, 4 Bytes gespart. Weil "Speicher" nun ein Token ist, eine Zahlen ID mit 4 Bytes Größe.
Außerdem ist die Datenstruktur so optimal, da CPUs (auch GPUs sind CPUs) diese 32-Bit Integerzahlen blitzschnell verarbeiten können.

Aber mehr bildlicher:
Man stelle sich vor man baut ein Klemmbaustein-Haus nur aus 4x2 Steinen, man spart sich die Suche nach den richtigen Bausteingrößen, hat standardisierte Breiten für Türen, Fenster, und Bauhöhe und baut einfach nur Ratzfatz in die Höhe.
Man büßt etwas Flexibilität ein, das wird aber durch reine Geschwindigkeit mehr als kompensiert!
Und darum geht es bei KI, Geschwindigkeit und Effizienz.

Kleines Schema:

flowchart LR
    A["Text:<br>'Speicherverwaltung'"] --> B["Tokenizer<br>(Zerlegung)"]

    subgraph Tokens ["3 Bausteine (Tokens)"]
        C1["'Speicher'"]
        C2["'ver'"]
        C3["'waltung'"]
    end

    B --> C1 & C2 & C3

    C1 --> D1["ID: 18420"]
    C2 --> D2["ID: 512"]
    C3 --> D3["ID: 8941"]

    subgraph Processing ["KI-Berechnung"]
        D1 & D2 & D3 --> E["Blitzschnelle GPU/VRAM-<br>Verarbeitung der Zahlen"]
    end

Veränderung der Tokengrenzen in Zusammenhang mit der Geschwindigkeit

Braucht man mehr Tokens für ein Modell, um z.B. komplexe Programmieraufgaben zu lösen oder erweiterte Chat-History und mehr Dateien einzubinden, kann man die Modelle neu konfigurieren für mehr Token.
Um ein besseres bzw. höherwertiges Modell für eigentlich mehr Speicherbedarf auch für kleinere Aufgaben nutzen zu können, oder deren Geschwindigkeit zu erhöhen, kann man die Modelle für weniger Token konfigurieren.

Das geht allerdings zu lasten 1. des VRAMs, 2. Auslagerung auf CPU. Letzteres wird - wenn verwendet - die Erstellung deutlich verlangsamen. Es ist mit maximal 5-10% der normalen, reinen GPU-Geschwindigkeit zu rechnen!
Daher kann im Gegenzug bei Verringerung der Token eine gewisse Auslagerung auf die CPU und deren RAM vermieden werden. Grafikspeicher ist im übrigen auch erheblich schneller als DDR4 oder DDR5 Hauptspeicher (Faktor 8-20x):

RAM-Typ Bandbreite (theoretisch)
DDR4 Dual-Channel 35-50 GB/s
DDR5 Dual-Channel 60-90 GB/s
GDDR6X (VRAM 256-Bit) ~736 GB/s

Der KV-Cache

Der KV-Cache (Key-Value Cache) ist das temporäre Gedächtnis des Attention-Mechanismus in Transformer-Modellen. Bei der Generierung jedes einzelnen neuen Wortes (Tokens) muss das Modell die Beziehungsdaten aller vorherigen Tokens auswerten.

Um nicht für jedes neue Token den kompletten Textverlauf von vorne durchzurechnen, speichert das Modell die Schlüssel- (Key) und Wert-Vektoren (Value) aller bisherigen Tokens im Speicher ab.

Der Speicherbedarf des KV-Caches berechnet sich nach folgender Formel:

\[\text{KV-Cache (Bytes)} = 2 \times \text{Schichten} \times \text{KV-Köpfe} \times \text{Kopf-Dimension} \times \text{Kontextlänge} \times \text{Bytes pro Element}\]

Der KV-Cache wächst strikt linear mit der Anzahl der verarbeiteten Tokens. Je größer der Kontext (z. B. 64k Tokens), desto mehr Grafikspeicher wird ausschließlich für das Aufrechterhalten des Gesprächsverlaufs benötigt.

FlashAttention-Algorithmus

Weiterhin sollte man in der Shell die Umgebungsvariable OLLAMA_FLASH_ATTENTION 1 einstellen. Das aktiviert in Ollama die Nutzung des FlashAttention-Algorithmus.
Standardmäßig berechnet ein Transformer die Beziehungen aller Tokens zueinander in einer großen \(N \times N\) Attention-Matrix, die komplett im Grafikspeicher (VRAM) zwischengespeichert werden muss. FlashAttention teilt diese Berechnung in kleinere Blöcke auf (Tiling) und führt sie direkt im extrem schnellen On-Chip-SRAM der GPU aus, anstatt ständig große Datenmengen in den VRAM zu schreiben und wieder herauszulesen.

Vorteile
  • Massive VRAM-Einsparung im KV-Cache: Der Speicherbedarf für das Kontextfenster sinkt bei langen Texten um 30 % bis 50 %. Das ermöglicht die Nutzung großer Kontextfenster (z. B. 64k Tokens), ohne dass der Grafikspeicher überläuft
  • Höhere Geschwindigkeit bei langen Texten: Die Verarbeitung großer Prompts (Prefill-Phase) und die Erzeugung von Antworten beschleunigen sich spürbar, da der Bandbreiten-Flaschenhals des VRAMs umgangen wird
  • Keine Qualitätseinbußen: Im Gegensatz zu aggressiverer Quantisierung führt FlashAttention mathematisch zu exakt denselben Ergebnissen wie die Standard-Attention
Nachteile
  • Strikte Hardware-Voraussetzungen: FlashAttention setzt moderne GPU-Architekturen mit passenden Tensor Cores voraus (bei NVIDIA ab der RTX 30xx / 40xx Serie bzw. Ampere/Ada-Architektur)
  • Minimaler Nutzen bei Kurztexten: Bei sehr kurzen Anfragen (< 512 Tokens) ist der Leistungsgewinn kaum messbar, da der VRAM-Verbrauch hier ohnehin vernachlässigbar ist
  • Potenzielle Kantenfehler: Bei sehr spezifischen Quantisierungsformaten oder älteren GPU-Treibern kann es vereinzelt zu Instabilitäten kommen

Setzen der Umgebungsvariablen

Für Fish-Shell (temporär):
set -gx OLLAMA_FLASH_ATTENTION 1

Permanent: O.a. Zeile in ~/.config/fish/config.fish eintragen.

Für Bash- oder Zsh-Shell:
export OLLAMA_FLASH_ATTENTION 1

Permanent: O.a. Zeile in ~/.bashrc oder ~/.zshrc eintragen. Die ganze Zeile, mit export.

Bestehende Modelle auf mehr (oder weniger) Tokens umstellen

Die Anzahl der Tokens wird über num_ctx <Tokens> festgelegt. Die Beispiele hier sind speziell auf meinen Hardwarekontext ausgelegt (nvidia 4080 Super, 16 GB VRAM).
Daher: YMMV! (Your Mileage May Vary! - Eure Erfahrung kann abweichen!)

Tipp

Die Anzahl der Tokens, die ein Modell verwendet kann in Aider bei geladenem Modell mit /tokens überprüft werden.
Weiterhin mit ollama show <Modelname>, Beispiel: ollama show qwen2.5-coder:14b -> context length 32768 für 32k Tokens.

Kontextlänge meiner Modellauswahl (theoretische Maxima):

Modell Context Length
nomic-embed-text:latest 2048
deepseek-r1:14b 131072
deepseek-coder-v2:latest 163840
qwen3-vl:8b-instruct 262144
qwen2.5-coder:14b 32768
qwen2.5:14b 32768
qwen3.8:27b 262144
64k Token Modell für qwen2.5-coder:14b

Modelfile erstellen Modelfile-14b-64k:

FROM qwen2.5-coder:14b
PARAMETER num_ctx 65536

Das Modell bauen:
ollama create qwen2.5-coder:14b-64k -f Modelfile-14b-64k

Metadaten anlegen in ~/.aider.model.metadata.json:

{
  "ollama_chat/qwen2.5-coder:14b-64k": {
    "max_input_tokens": 65536,
    "max_output_tokens": 8192,
    "max_tokens": 8192,
    "input_cost_per_token": 0.0,
    "output_cost_per_token": 0.0,
    "litellm_provider": "ollama_chat",
    "mode": "chat"
  }
}

Aider-Modellverhalten konfigurieren (optional) in ~/.aider.model.settings.yml:

- name: ollama_chat/qwen2.5-coder:14b-64k
  edit_format: diff

Aider mit dem neuen Modell qwen2.5-coder:14b-64k starten und verifizieren:
aider --model ollama_chat/qwen2.5-coder:14b-64k

Auszug:

❯ aider --model ollama_chat/qwen2.5-coder:14b-64k
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
You should probably run aider in your project's directory, not your home dir.
Warning: ollama_chat/qwen2.5-coder:14b-64k expects these environment variables
- OLLAMA_API_BASE: Not set
You can skip this check with --no-show-model-warnings

https://aider.chat/docs/llms/warnings.html
Open documentation url for more info? (Y)es/(N)o/(D)on't ask again [Yes]: D                                                                                              

Aider v0.86.2
Model: ollama_chat/qwen2.5-coder:14b-64k with diff edit format
Git repo: none
Repo-map: disabled
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
> /tokens                                                                                                                                                                

Approximate context window usage for ollama_chat/qwen2.5-coder:14b-64k, in tokens:

$ 0.0000    1,941 system messages 
==================
$ 0.0000    1,941 tokens total
           63,595 tokens remaining in context window
           65,536 tokens max context window size

Performantes 8k Token Modell für qwen3.8:27b

Modelfile erstellen Modelfile-27b-8k:

FROM qwen3.8:27b
PARAMETER num_ctx 8192

Das Modell bauen:
ollama create qwen3.8:27b-8k -f Modelfile-27b-8k

Metadaten anlegen/erweitern in ~/.aider.model.metadata.json:

{
  "ollama_chat/qwen2.5-coder:14b-64k": {
    "max_input_tokens": 65536,
    "max_output_tokens": 8192,
    "max_tokens": 8192,
    "input_cost_per_token": 0.0,
    "output_cost_per_token": 0.0,
    "litellm_provider": "ollama_chat",
    "mode": "chat"
  },
  "ollama_chat/qwen3.8:27b-8k": {
    "max_input_tokens": 8192,
    "max_output_tokens": 8192,
    "max_tokens": 8192,
    "input_cost_per_token": 0.0,
    "output_cost_per_token": 0.0,
    "litellm_provider": "ollama_chat",
    "mode": "chat"
  }
}

Aider-Modellverhalten konfigurieren/erweitern (optional) in ~/.aider.model.settings.yml:

- name: ollama_chat/qwen2.5-coder:14b-64k
  edit_format: diff
- name: ollama_chat/qwen3.8:27b-8k
  edit_format: diff

Aider mit dem neuen Modell qwen3.8:27b-8k starten und verifizieren:
aider --model ollama_chat/qwen3.8:27b-8k

Auszug:

❯ aider --model ollama_chat/qwen3.8:27b-8k
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
You should probably run aider in your project's directory, not your home dir.
Warning: ollama_chat/qwen3.8:27b-8k expects these environment variables
- OLLAMA_API_BASE: Not set
You can skip this check with --no-show-model-warnings

https://aider.chat/docs/llms/warnings.html
Open documentation url for more info? (Y)es/(N)o/(D)on't ask again [Yes]: D                                                                                              

Aider v0.86.2
Model: ollama_chat/qwen3.8:27b-8k with diff edit format
Git repo: none
Repo-map: disabled
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
> /tokens                                                                                                                                                                

Approximate context window usage for ollama_chat/qwen3.8:27b-8k, in tokens:

$ 0.0000    1,941 system messages 
==================
$ 0.0000    1,941 tokens total
            6,251 tokens remaining in context window
            8,192 tokens max context window size

Aktuelle Ausgabe von ollama list:

❯ ollama list
NAME                        ID              SIZE      MODIFIED       
qwen3.8:27b-8k              999690a38387    17 GB     4 minutes ago     
qwen2.5-coder:14b-64k       4828733707be    9.0 GB    21 minutes ago    
nomic-embed-text:latest     0a109f422b47    274 MB    8 hours ago       
deepseek-r1:14b             c333b7232bdb    9.0 GB    20 hours ago      
deepseek-coder-v2:latest    63fb193b3a9b    8.9 GB    20 hours ago      
qwen3-vl:8b-instruct        0533d74300e4    6.1 GB    20 hours ago      
qwen2.5-coder:14b           9ec8897f747e    9.0 GB    20 hours ago      
qwen2.5:14b                 7cdf5a0187d5    9.0 GB    20 hours ago      
qwen3.8:27b                 22130167c4c2    17 GB     20 hours ago     

Speichertabelle für qwen3.8:27b bei Reduzierung auf 8k Tokens

Speicheraufschlüsselung bei 8k Token für das qwen3.8:27b-Modell auf nvidia RTX 4080 Super:

Kontextgröße (num_ctx) KV-Cache Bedarf Gesamter Speicherbedarf Verhalten auf RTX 4080 Super
4.096 Tokens (4k) ~0,5 GB ~16,0 GB Vollständig im VRAM (maximale Geschwindigkeit)
8.192 Tokens (8k) ~1,2 GB ~16,7 GB Leichte CPU-Auslagerung ~1-2 Schichten im RAM
16.384 Tokens (16k) ~2,5 GB ~18,0 GB Spürbare CPU-Auslagerung (langsamer)
32.768 Tokens (32k) ~5,0 GB ~20,5 GB Hohe CPU-Auslagerung (sehr langsam)
Speichertabelle für qwen3.8:27b bei Erhöhung auf 64k Tokens

Beim Qwen3.8 27B Modell (geladen als 4-Bit-Quantisierung Q4_K_M) belegen die reinen Modellgewichte bereits knapp 15 GB des Grafikspeichers.

Bei einer Erhöhung auf 64k Tokens und Setzen der Umgebungsvariablen OLLAMA_FLASH_ATTENTION=1 ergibt sich folgende Speicheraufschlüsselung:

Kontextgröße (num_ctx) Modellgewichte (Q4) KV-Cache Bedarf Gesamter Speicherbedarf Verhalten auf RTX 4080 Super
4.096 Tokens (4k) ~15,0 GB ~0,6 GB ~15,6 GB 100 % im VRAM (Maximale Geschwindigkeit)
8.192 Tokens (8k) ~15,0 GB ~1,2 GB ~16,2 GB Minimaler Überlauf im System-RAM (~0,2 GB)
16.384 Tokens (16k) ~15,0 GB ~2,5 GB ~17,5 GB ~1,5 GB Auslagerung in System-RAM (DDR4)
32.768 Tokens (32k) ~15,0 GB ~5,0 GB ~20,0 GB ~4,0 GB Auslagerung in System-RAM (DDR4)
65.536 Tokens (64k) ~15,0 GB ~10,0 GB ~25,0 GB ~9,0 GB Auslagerung in System-RAM (Flaschenhals)

Was passiert bei 64k Tokens mit dem 27B-Modell auf dem PC?

  • Massiver RAM-Überlauf: Von den benötigten 25 GB Gesamtspeicher passen nur 16 GB in den VRAM der RTX 4080 Super. Rund 9 GB des KV-Caches müssen in den Hauptspeicher (DDR4 RAM) verschoben werden
  • Geschwindigkeitseinbruch: Die Speicherbandbreite des DDR4-RAMs (~40–50 GB/s) ist um ein Vielfaches langsamer als die des VRAMs der RTX 4080 Super (736 GB/s). Die Antwortgenerierung bricht von flüssigen ~30 Tokens/Sekunde auf zähe 1 bis 3 Tokens/Sekunde ein
  • Empfehlung: Für 64k Tokens Kontextfenster ist auf einer 16-GB-GPU das qwen2.5-coder:14b das optimale Modell, da dessen Modellgewichte nur ~9 GB VRAM belegen und der 64k KV-Cache vollständig im verbleibenden VRAM Platz findet

Notiz- und Ideensammlung

root42: "Für das LLM Coding kann ich Claude-CLI empfehlen. Das kann verschiedenste Modelle benutzen. Und es ist halt ein Agent auf dem Rechner, und kann direkt den Code editieren. Kann man einfach im Terminal neben seinen Lieblingseditor -- also Emacs -- aufmachen."