Kategorie Bild KI
Zurück zur Übersicht

Ollama-Tuning auf meinem Spark: lokale Coding-Modelle praxistauglich betreiben

Wie ich lokale Coding-Modelle mit Ollama auf meinem Spark betreibe, Kontextgrößen begrenze und zwischen Daily-Driver und Qualitätsmodell unterscheide.

Lokale Modelle klingen schnell nach Freiheit. Keine API-Limits, eigene Hardware, volle Kontrolle über Daten und Laufzeit.

In der Praxis kommt aber sehr schnell die nächste Frage. Wie betreibe ich große Coding-Modelle so, dass sie nicht nur starten, sondern im Alltag stabil bleiben?

Genau das habe ich auf meinem Spark mit Ollama getestet.

Warum ich lokale Coding-Modelle nicht einfach ungeprüft starte

Ein großes Modell kann auf dem Papier gut zur Hardware passen und trotzdem beim Laden scheitern. Der Grund ist selten nur die Modellgröße.

Entscheidend ist auch, mit welcher Kontextgröße das Modell gestartet wird. Ollama hat auf dem Spark wegen der großen verfügbaren GPU-Speichermenge teilweise automatisch ein sehr großes Kontextfenster gewählt.

Konkret ging es um:

default_num_ctx = 262144

Das sind 262k Tokens Kontext. Das klingt erst einmal gut, ist bei sehr großen Modellen aber schnell zu viel.

Was funktioniert hat

Als Daily-Business-Modell nutze ich aktuell dieses Modell:

qwen3-coder-next:q8_0

Das ist die Q8-Variante von Qwen Coder Next. Sie ist langsamer als die Q4-Variante, aber bisher schnell genug für meine tägliche Arbeit mit opencode. Wichtiger war mir an dieser Stelle die gefühlt bessere Codegenerierung.

Im Test lag die Generierung der Q8-Variante bei ungefähr:

36 Tokens/s

Zusätzlich habe ich heute eine optimierte Q4-Variante in opencode eingetragen:

qwen3-coder-next:latest

Sie lag im Kurztest bei ungefähr:

45 Tokens/s

Ob sie nur schneller ist oder ob ich Unterschiede in der Qualität des generierten Codes merke, werde ich in den nächsten Tagen im Alltag testen.

Gut funktioniert hat außerdem, eigene Ollama-Varianten für große Modelle anzulegen. Die Originalmodelle bleiben unverändert, aber die neuen Varianten bekommen eine feste Kontextgröße.

Beispiele:

mistral-medium-3.5-96k:latest
devstral-2-96k:latest

Beide Varianten verwenden die bestehenden Modellgewichte, setzen aber explizit:

num_ctx 98304

Damit laufen sie nicht mehr automatisch mit 262k Kontext los.

Was nicht funktioniert hat

Nicht funktioniert hat, die großen Modelle einfach mit dem automatisch gewählten Kontext zu starten.

Bei mistral-medium-3.5 kam es zuerst zu Runner- und CUDA-Fehlern. Bei devstral-2:123b war das Problem noch klarer sichtbar.

Mit 262k Kontext wollte das Modell ungefähr diesen Speicherbereich nutzen:

total memory: ca. 160 GiB

Das ist für den Spark zu viel. Das Ergebnis waren Swap, OOM und abgebrochene Ladevorgänge.

Auch das reine Setzen von num_ctx in der opencode-Konfiguration war nicht der verlässlichste Hebel. Der robustere Weg war, direkt in Ollama eigene Modellvarianten mit festem PARAMETER num_ctx anzulegen.

Was die Caches grob machen

Beim Laden eines Modells tauchen verschiedene Speicherbereiche auf. Für die Praxis reicht mir eine einfache Einordnung.

Model Weights sind das Modell selbst. Hier liegen die gelernten Parameter, also das, was beim Training entstanden ist und später für jede Antwort verwendet wird. Wenn ein Modell 70B, 120B oder 127B Parameter hat, dann beschreibt diese Zahl im Kern genau diesen Bereich.

Die Gewichte ändern sich während einer normalen Anfrage nicht. Sie werden geladen und dann immer wieder benutzt. Wie viel Speicher sie brauchen, hängt vor allem von der Modellgröße und der Quantisierung ab. Ein Q8-Modell braucht mehr Speicher als eine Q4-Variante, kann dafür aber in der Ausgabequalität besser wirken.

KV Cache ist der Speicher für den aktuellen Kontext. Bei jedem Token im Prompt entstehen Zwischenergebnisse, die das Modell für die nächsten Tokens wiederverwenden kann. Diese Zwischenergebnisse heißen vereinfacht Key- und Value-Werte, daher KV Cache.

Der KV Cache ist wichtig, weil das Modell bei der Generierung nicht bei jedem neuen Token den kompletten bisherigen Chat von vorne berechnen soll. Stattdessen kann es auf gespeicherte Zwischenergebnisse zurückgreifen. Das macht lange Gespräche und lange Prompts überhaupt erst praktikabel.

Der Preis dafür ist Speicher. Je größer das Kontextfenster ist, desto mehr Tokens können gleichzeitig im Kontext liegen und desto größer muss der KV Cache werden. Deshalb kann ein Modell mit denselben Gewichten bei 32k Kontext deutlich weniger Speicher brauchen als bei 96k oder 262k Kontext.

Compute Graph ist die Arbeitsfläche für die Berechnung. Während das Modell eine Antwort erzeugt, müssen Operationen vorbereitet, Zwischenergebnisse gehalten und Speicherbereiche für die eigentliche Inferenz reserviert werden. Dieser Bereich ist nicht das gelernte Modell und auch nicht der Chatverlauf, sondern eher der temporäre Rechenplan mit Arbeitsdaten.

In meinen Tests war der Compute Graph kleiner als Model Weights und KV Cache. Er ist trotzdem relevant, weil er zusätzlich in den Speicher passen muss. Wenn ein Modell rechnerisch knapp unter die verfügbare GPU-Grenze passt, kann genau dieser Arbeitsbereich den Unterschied machen, ob der Load stabil funktioniert oder abbricht.

High-level sieht es für mich so aus:

Model Weights = das Modell selbst
KV Cache      = Erinnerung an den aktuellen Kontext
Compute Graph = Arbeitsfläche für die Berechnung

Der wichtigste Hebel war in diesem Test der KV Cache. Das Modell selbst wird durch mehr Kontext nicht größer, aber der laufende Speicherbedarf steigt deutlich, weil der KV Cache mehr Zwischenergebnisse für den längeren Kontext vorhalten muss.

Die aktuelle Rollenverteilung

Für den Alltag nutze ich Qwen Coder Next als Arbeitsmodell, aktuell in der Q8-Variante:

qwen3-coder-next:q8_0

Das ist mein Daily-Business-Modell. Es ist schnell genug, stabil und liefert für normale Coding-Aufgaben bisher die für mich bessere Codequalität.

Die Q4-Variante bleibt als neue schnelle Option im Test:

qwen3-coder-next:latest

Sie ist messbar schneller, aber ich will nicht nur Tokens pro Sekunde bewerten. Entscheidend ist, ob die erzeugten Änderungen im Projekt weniger Nacharbeit brauchen.

Für schwerere Aufgaben bleiben größere Modelle interessant:

mistral-medium-3.5-96k:latest
devstral-2-96k:latest

Diese Modelle sind nicht für jede kleine Änderung gedacht. Ich sehe sie eher bei Architekturfragen, größeren Reviews, schwierigen Bugs oder Planungsaufgaben.

Die 96k-Varianten liegen auf dem Spark grob in dem Bereich, den ich sinnvoll nutzen kann:

mistral-medium-3.5-96k: ca. 117 GB geladen
devstral-2-96k:         ca. 110 GB geladen

Das ist nah am Limit, aber deutlich kontrollierter als ein ungebremster 262k-Load.

Wie ich die Custom-Modelle auf meinem Spark erstellt habe

Die Originalmodelle habe ich nicht verändert. Stattdessen habe ich auf dem Spark eigene Ollama-Varianten angelegt, die auf den vorhandenen Modellen basieren und nur bestimmte Parameter überschreiben.

Für mistral-medium-3.5-96k sieht das Prinzip so aus:

cat > Modelfile.mistral-medium-3.5-96k <<'EOF'
FROM mistral-medium-3.5:latest
PARAMETER num_ctx 98304
EOF
 
ollama create mistral-medium-3.5-96k -f Modelfile.mistral-medium-3.5-96k

Für devstral-2-96k entsprechend:

cat > Modelfile.devstral-2-96k <<'EOF'
FROM devstral-2:123b
PARAMETER num_ctx 98304
EOF
 
ollama create devstral-2-96k -f Modelfile.devstral-2-96k

Danach prüfe ich, ob der Parameter wirklich im neuen Modell steckt:

ollama show mistral-medium-3.5-96k --parameters
ollama show devstral-2-96k --parameters

Erwartet ist jeweils:

num_ctx 98304

Einen ersten Load-Test mache ich bewusst mit einer sehr kurzen Anfrage:

curl -sS -X POST http://127.0.0.1:11434/api/generate \
  -H "Content-Type: application/json" \
  -d '{
    "model": "devstral-2-96k:latest",
    "prompt": "Antworte nur mit ok.",
    "stream": false,
    "options": {
      "num_predict": 1
    }
  }'

Parallel oder direkt danach prüfe ich Speicher und geladenes Modell:

free -h
ollama ps

Bei devstral-2-96k sah das Zielbild danach so aus:

SIZE      110 GB
PROCESSOR 100% GPU
CONTEXT   98304

Wenn ein ungecappter Load hängt oder der Server in Swap läuft, entlade ich das Modell zuerst sauber über Ollama:

ollama stop devstral-2:123b

Oder über die API:

curl -sS -X POST http://127.0.0.1:11434/api/generate \
  -H "Content-Type: application/json" \
  -d '{
    "model": "devstral-2:123b",
    "keep_alive": 0
  }'

Wenn der Runner trotzdem hängt, hilft nur ein Restart des Dienstes auf dem Spark:

sudo systemctl restart ollama

Nach einem starken Swap-Ereignis kann ich den Swap zusätzlich leeren, aber nur wenn wieder genug RAM frei ist:

sudo swapoff -a
sudo swapon -a

Damit opencode die neuen Modelle nutzt, muss auch die lokale opencode-Konfiguration angepasst werden. Bei mir liegt sie hier:

~/.config/opencode/opencode.json

Der relevante Ausschnitt sieht für devstral-2-96k so aus:

{
  "provider": {
    "ollama": {
      "models": {
        "devstral-2-96k:latest": {
          "name": "Devstral-2 96k - Agent Code",
          "limit": {
            "context": 98304,
            "output": 8192
          },
          "options": {
            "maxTokens": 8192,
            "num_ctx": 98304,
            "temperature": 0.15,
            "top_p": 0.9
          }
        }
      }
    }
  }
}

Für mistral-medium-3.5-96k ist es dasselbe Muster:

{
  "provider": {
    "ollama": {
      "models": {
        "mistral-medium-3.5-96k:latest": {
          "name": "Mistral3.5 96k - General Fast",
          "limit": {
            "context": 98304,
            "output": 8192
          },
          "options": {
            "maxTokens": 8192,
            "num_ctx": 98304,
            "temperature": 0.25,
            "top_p": 0.95
          }
        }
      }
    }
  }
}

Wichtig ist danach ein Neustart von opencode. Die Konfiguration wird beim Start geladen und nicht zuverlässig während einer laufenden Session neu eingelesen.

Welche Optimierung wirklich geholfen hat

Der wichtigste Schritt war nicht, alles maximal groß zu konfigurieren. Der wichtigste Schritt war, Rollen klar zu trennen.

Für schnelle Agentenarbeit brauche ich ein Modell, das viele kleine Schleifen gut mitmacht. Dafür ist Qwen Coder Next aktuell die bessere Wahl, auch wenn ich zwischen Q8 und Q4 noch genauer vergleichen will.

Für Qualität bei komplexen Aufgaben darf ein Modell langsamer sein, solange es stabil lädt und genug Kontext bekommt. Dafür sind die gecappten 96k-Varianten sinnvoller als die ungecappte Standardvariante.

Mehr Kontext ist also nicht automatisch bessere Engineering-Arbeit. Oft ist der bessere Kompromiss:

schnelles Daily-Modell + gezielt eingesetztes großes Qualitätsmodell

Für die wirklich großen Fragen und für Grundlagen nutze ich weiterhin openai/gpt-5.5. Also dort, wo es um Architekturentscheidungen, schwierige Analyse, saubere Planung oder konzeptionelle Klärung geht.

Die lokalen Modelle übernehmen danach vor allem die Fleißarbeit:

  • Änderungen in kleinen Schritten umsetzen,
  • bestehende Muster im Projekt anwenden,
  • Varianten ausprobieren,
  • Tests und Fehlerausgaben einarbeiten,
  • wiederholbare Coding-Aufgaben erledigen.

Das trennt für mich Denken und Durcharbeiten besser. Nicht jede Aufgabe braucht das größte Modell, aber nicht jede Grundsatzentscheidung gehört an ein lokales Arbeitsmodell delegiert.

Noch zu testen

Auf meiner Liste stehen noch weitere Modellfamilien und Varianten. Interessant sind für mich vor allem Modelle, die Tool-Use, Codequalität, Kontextgröße und Geschwindigkeit gut ausbalancieren.

Noch zu testen:

  • DeepSeek Coder V2,
  • DeepSeek R1 in Coding-Workflows,
  • Codestral,
  • GLM-4.5 und GLM-4.5-Air,
  • Kimi K2,
  • Mistral Small 3.2,
  • Devstral 24B,
  • weitere Qwen3-Coder-Varianten,
  • Llama-basierte Coding-Modelle,
  • Gemma- oder CodeGemma-Varianten.

Fazit

Lokale Coding-Modelle werden erst dann alltagstauglich, wenn Speicher, Kontext und Modellrolle zusammenpassen.

Auf meinem Spark ist qwen3-coder-next:q8_0 aktuell mein Daily-Business-Modell. Die schnellere Q4-Variante teste ich in den nächsten Tagen gegen die gefühlte Codequalität.

Für Grundlagen, Planung und große Architekturfragen bleibt openai/gpt-5.5 mein bevorzugtes Modell. Die lokalen Modelle erledigen danach die Fleißarbeit im Projekt.

Die großen 96k-Varianten bleiben für Aufgaben, bei denen lokaler Kontext und große Analyse wichtiger sind als Antwortgeschwindigkeit.

Der entscheidende Punkt ist nicht, das größte Modell mit dem größten Kontext zu starten. Der entscheidende Punkt ist, für jede Aufgabe das passende Modell mit kontrollierbaren Betriebsgrenzen zu verwenden.

Wenn du magst, kannst du mit dem Projekt-Check prüfen, wie robust eure Architektur-, Test- und Betriebsbasis aktuell ist.

Wenn du tiefer rein willst

Passende Vertiefungen zu diesem Thema

Wenn du nach dem Artikel tiefer in Umsetzung, typische Probleme oder Grundlagen einsteigen willst, helfen dir diese Seiten weiter.

Weitere Artikel