← Insights

EN — English version

#10 · Das LLM-Rig aus eBay-Teilen: 2 x RTX 3060, 120 Euro für Board und CPU, 77 tok/s

· Filip

Unser großes lokales LLM-Rig besteht aus acht RTX 3090, einem EPYC und einer Stromversorgung, mit der man vermutlich auch kleinere Metallarbeiten erledigen könnte. Dort war die Materialschlacht Teil der Aufgabe. Diesmal wollten wir das Gegenteil wissen: Wie billig darf ein lokaler LLM-Rechner werden, bevor er nur noch als Bastelprojekt taugt?

Die Antwort fiel günstiger aus als erwartet. Ein Mainboard für 59 Euro, ein Xeon für 25 Euro, zwei gebrauchte RTX 3060 mit je 12 GB – und darauf läuft Qwen3.6-35B-A3B mit 35 Milliarden Gesamtparametern, seinem nativen Kontext von 262.144 Tokens und rund 77 tok/s.

Das ist kein Rechner, dessen Schwächen wir uns mit dem niedrigen Preis schönreden müssen. In unserem realen, mehrstufigen DOCX-Workflow wurde er zu einem brauchbaren Agentenrechner. Der Weg dorthin war allerdings lehrreicher als die Einkaufsliste. Ein vermeintlich zu grober 4-Bit-Quant erwies sich als Serving-Problem, ein Modell mit 107 tok/s verlor gegen ein langsameres mit 91 tok/s, und eine Serie angeblicher Modellfehler war am Ende ein falsch gesetztes $PWD.

Alte Serverhardware macht den Unterbau fast lächerlich billig

Das Zentrum des Rechners ist ein ASUS Z10PA-U8 aus der LGA-2011-3-Generation. Der Intel-C612-Chipsatz, acht DDR4-Steckplätze und die für eine Workstation ausgelegte PCIe-Anbindung stammen aus einer Zeit, in der solche Boards in Firmenrechnern standen. Heute stehen sie zu Preisen auf dem Gebrauchtmarkt, die bei aktueller Hardware manchmal nicht einmal für den Versand reichen würden.

Komponente Ausführung eBay-Preis
Mainboard ASUS Z10PA-U8 (LGA 2011-3, Intel C612, 8 x DDR4) ca. 59 Euro
CPU Intel Xeon E5-2680 v4 (14C/28T) ca. 25 Euro
RAM 32 GB DDR4 ECC gebraucht, wenige Euro
GPU0 RTX 3060 12 GB (GA104), PCIe Gen3 x16 gebraucht
GPU1 RTX 3060 12 GB (GA106), PCIe Gen3 x16 gebraucht
Betriebssystem Ubuntu 26.04 0 Euro

Der Xeon klingt mit 14 Kernen und 28 Threads wichtiger, als er in diesem Aufbau ist. Beim Decode liegt das MoE-Modell vollständig auf den GPUs. Die CPU muss die Runtime versorgen und darf den Ablauf nicht ausbremsen, verrichtet aber keine Arbeit, die einen modernen Hochleistungssockel rechtfertigen würde. Ein E5-2680 v4 genügte dafür mühelos. Auch eine noch günstigere CPU aus der v3-Generation wäre denkbar.

Wichtiger war das Board. Beide RTX 3060 arbeiten an echten PCIe-Gen3-x16-Verbindungen. Wir brauchten weder Bifurcation-Kunststücke noch einen x1-Riser aus einer ausrangierten Mining-Kiste. Ein Layer-Split erzeugt ohnehin wenig Verkehr zwischen den Karten, weil jede GPU ihre eigenen Layer nacheinander berechnet. Volle x16-Links nehmen trotzdem eine ganze Fehlerklasse aus dem Aufbau, bevor die eigentliche Fehlersuche beginnt.

Die Karten verwenden trotz gleicher Produktbezeichnung unterschiedliche Chips, einmal GA104 und einmal GA106. Das ist bei gebrauchten RTX 3060 nichts Ungewöhnliches und störte den Layer-Split nicht. Für die Runtime zählen hier vor allem die jeweils 12 GB VRAM und eine funktionierende CUDA-Unterstützung.

Auch 16 statt 32 GB Arbeitsspeicher würden für diesen reinen GPU-Pfad reichen. Beim Mainboard würden wir dagegen nicht einfach zum billigsten Angebot greifen. Acht DDR4-Steckplätze, stabile PCIe-Lanes und das integrierte ASPEED-BMC sind in diesem Aufbau mehr wert als ein paar gesparte Euro.

Board und CPU kosteten zusammen ungefähr 120 Euro. Die beiden gebrauchten 3060 waren der einzige größere Posten. Für unseren Einkauf waren sie der günstigste unkomplizierte Weg zu insgesamt 24 GB VRAM, ohne auf Spezialkarten oder fragwürdige Adapterkonstruktionen auszuweichen.

Treiber, Docker und ein notwendiger Neustart

Die Grundinstallation war unspektakulär. Zwei Details entschieden später allerdings darüber, ob ein Fehler nach Quantisierung oder nach Systemzustand aussah.

Wir installierten bewusst den proprietären NVIDIA-Treiber 580 und starteten den Rechner danach vollständig neu:

sudo apt-get install -y nvidia-driver-580   # → proprietär 580.173.02 (license NVIDIA, DKMS), CUDA-UMD 13.0
sudo reboot                                  # NICHT per Live-modprobe verlassen — echter Reboot

Auf unserem Stand wollte ubuntu-drivers stattdessen 595-open installieren. Für die dokumentierte Konfiguration blieben wir beim proprietären 580. Der Neustart war keine bloße Höflichkeit gegenüber Linux. Ein im laufenden System mit modprobe erzwungener Treiberwechsel hinterließ bei uns einen Zwischenzustand, in dem bestimmte Quantisierungspfade unbrauchbare Ausgabe erzeugten. Nach dem sauberen Neustart war dieses Fehlerbild verschwunden.

Danach folgten Docker und das NVIDIA Container Toolkit:

sudo apt-get install -y docker.io
# nvidia-container-toolkit via NVIDIA-Repo, dann:
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
# GPU-Test:
docker run --rm --gpus all --entrypoint nvidia-smi ghcr.io/ggml-org/llama.cpp:server-cuda -L

Sobald nvidia-smi beide 3060 im Container auflistete, war die Hardwareseite weitgehend erledigt. Als Runtime verwendeten wir das offizielle CUDA-Server-Image von llama.cpp, ghcr.io/ggml-org/llama.cpp:server-cuda. Das von uns eingesetzte Image basierte auf CUDA 12.8. Diese Versionsangabe wurde später unerwartet wichtig.

Weshalb ausgerechnet ein 35B-Modell auf zwei RTX 3060 passt

Die Modellarchitektur und unsere ausführlicheren Versuche mit Qwen3.6-35B-A3B behandeln wir in einem eigenen Serienartikel. Für den Budget-Rechner ist vor allem der hintere Teil des Namens entscheidend: A3B.

Qwen3.6-35B-A3B besitzt rund 35 Milliarden Parameter, aktiviert als Mixture-of-Experts-Modell pro Token aber nur ungefähr drei Milliarden. Beim Decode müssen daher nicht sämtliche Expertengewichte für jedes neue Token gelesen werden. Das drückt den Speicherbandbreitenbedarf erheblich. Eine RTX 3060 erreicht etwa 360 GB/s und liegt damit deutlich unter einer RTX 3090. Mit nur rund drei Milliarden aktiven Parametern bleibt der Betrieb trotzdem schnell genug, um Freude zu machen.

Wir verwendeten den UD-IQ4_XS-Quant von Unsloth mit ungefähr 17,7 GB und diesen vollständigen Startbefehl:

docker rm -f qwen36_a3b_q4 2>/dev/null
docker run -d --name qwen36_a3b_q4 \
  --entrypoint /app/llama-server \
  --gpus all --ipc host --shm-size 16g \
  -p 8080:8080 \
  -v /home/lyth/models:/models \
  ghcr.io/ggml-org/llama.cpp:server-cuda \
  --model /models/Qwen3.6-35B-A3B-UD-IQ4_XS.gguf \
  --host 0.0.0.0 --port 8080 \
  --ctx-size 262144 --parallel 1 \
  -fa on -ngl 999 -sm layer \
  -ctk q8_0 -ctv q8_0 \
  -b 2048 -ub 256 \
  --jinja --swa-full --threads 14

Die entscheidenden Parameter haben jeweils eine konkrete Aufgabe:

  • -sm layer verteilt vollständige Layer auf beide GPUs. Das ist kein Tensor Parallelism. Karte 0 berechnet den unteren Teil des Modells, Karte 1 danach den oberen. Bei nur etwa drei Milliarden aktiven Parametern blieb diese sequenzielle Verteilung schnell genug.
  • -ctk q8_0 -ctv q8_0 legt Key- und Value-Cache in 8 Bit ab. Dadurch passt der lange Kontext in den verbleibenden VRAM.
  • --ctx-size 262144 reserviert den nativen Kontext des Modells mit 262.144 Tokens.
  • -ub 256 begrenzt den physischen Micro-Batch. Das kostet etwas Prefill-Leistung, schafft aber den notwendigen VRAM-Spielraum.
  • -fa on aktiviert Flash Attention, --jinja verwendet das im GGUF hinterlegte Chat-Template über Jinja.
  • --swa-full weist llama.cpp an, für Sliding Window Attention einen Cache in voller Größe zu verwenden. Der Schalter ist vom allgemeinen Prompt-Cache zu unterscheiden. In unseren mehrstufigen Läufen wurde er für eine stabile Konversation wichtig.

Woher der kleine KV-Cache tatsächlich kommt

Die Modellgewichte belegten pro Karte ungefähr 8,85 GB. Trotzdem passte ein q8-KV-Cache für 262.144 Tokens in die insgesamt 24 GB VRAM. Ein dichtes 27B-Modell geriet in unserer Konfiguration schon weit früher an die Speichergrenze.

Der Grund liegt genauer betrachtet in zwei verschiedenen Teilen der Architektur. Das MoE reduziert die pro Token aktiven Gewichte und damit vor allem die Decode-Arbeit. Der kleine KV-Cache folgt dagegen Qwens hybrider Attention-Struktur: Von 40 Layern verwenden nur zehn vollständige Gated Attention; die übrigen arbeiten mit Gated DeltaNet. Die Full-Attention-Layer besitzen zudem nur zwei KV-Heads. Laut unseren Runtime-Werten benötigte der GQA-KV-Cache in f16 ungefähr 0,69 GiB je 32k Tokens. Mit q8_0 ließ sich deshalb der gesamte native Kontext unterbringen.

Für die Hardwareplanung müssen beide Effekte getrennt betrachtet werden. Das MoE spart Rechenarbeit, während die Attention-Architektur den Cache verkleinert. Bei Qwen3.6 wirken wenig aktive Expertengewichte, wenige Full-Attention-Layer und zwei KV-Heads in dieselbe günstige Richtung: wenig Rechenarbeit pro Token, viel Kontext und überschaubarer VRAM-Bedarf.

77 tok/s im Single-Stream

Mit kurzem Prompt erreichte der Aufbau rund 77 tok/s Decode im Single-Stream. Für ein Modell mit 35 Milliarden Gesamtparametern auf zwei RTX 3060 ist das mehr als eine hübsche Benchmarkzahl. Die Ausgabe entsteht schneller, als man sie lesen kann; in einer interaktiven Sitzung wirkt das unmittelbar.

Der Wert lag erstaunlich nahe an unserem FP8-Pfad mit demselben Basismodell auf zwei RTX 3090 aus dem großen Rig. Als allgemeiner Vergleich zwischen 3060 und 3090 taugt diese einzelne Messung nicht. Sie zeigt, wie günstig diese konkrete A3B-Architektur auf die kleinere Speicherbandbreite reagiert und wie stark Quant, Engine und Parallelisierung das Ergebnis mitbestimmen.

Als der 4-Bit-Quant plötzlich dumm wirkte

Die ersten agentischen Läufe sahen weit weniger erfreulich aus als der kurze Decode-Test. Unsere Prüfaufgabe war absichtlich unangenehm: Dateien lesen, Anweisungen auswerten, Tabellen und Rangfolgen bearbeiten, mehrere Werkzeugaufrufe koordinieren und am Ende ein DOCX-Dokument erzeugen.

Mit dem 4-Bit-A3B beobachteten wir dabei drei Fehlerbilder:

  • Wiederholungsschleifen, in denen das Modell denselben Gedankengang fortsetzte,
  • Halluzinationen mitten in Werkzeugaufrufen,
  • Ausgaben, die ausfransten und ohne sauberen Abschluss endeten.

Der naheliegende Verdacht lautete, UD-IQ4_XS sei zu aggressiv quantisiert. Ein größerer Quant hätte mehr VRAM verbraucht, den langen Kontext eingeschränkt und einen weiteren großen Download erfordert. Bevor wir diese Kosten akzeptierten, sichteten wir Community-Diskussionen, GGUF-Hinweise und Issue-Tracker.

Für unsere Konfiguration führten zwei Änderungen aus der Sackgasse:

  1. Wir ergänzten --swa-full im llama.cpp-Start. Die offizielle Argumentbeschreibung spricht präzise von einem „full-size SWA cache“. In unserem mehrstufigen Verlauf verhinderte diese Cache-Konfiguration die zuvor beobachteten Schleifen und abreißenden Antworten. Für unseren Serverstand war das eine klare Diagnose. Ein einzelner Aufbau belegt allerdings nichts über jeden beliebigen Qwen-Quant.
  2. Wir erlaubten OpenCode den Zugriff auf das tatsächliche Arbeitsverzeichnis. Ohne diese Freigabe lief der Agent im Headless-Betrieb bei Schreibzugriffen auf /tmp in eine automatische Ablehnung. Von außen sah das wie ein misslungener Modellschritt aus; tatsächlich war es Rechteverwaltung:
"external_directory": { "*": "ask", "/tmp/**": "allow" }

Der Unterschied war deutlich. Im DOCX-Benchmark stieg die Zahl sauberer Läufe von 0 von 3 auf 2 von 3. Modell und Quantdatei blieben identisch. Geändert hatten sich Serving und Berechtigung.

Auch die öffentlichen Diskussionen zu den Unsloth-GGUFs zeigen, dass Tool-Calling-Probleme selbst bei höherer Präzision auftreten können und dass Template, Sampling und Runtime bei der Diagnose berücksichtigt werden müssen. Einzelne Nutzer beseitigten Schleifen durch ein anderes Qwen-Chat-Template. Solche Berichte schließen den Quant als mögliche Ursache nicht aus. Sie reichen aber aus, um „mehr Bits“ nicht mehr als erste und einzige Antwort zu behandeln.

Ein zweiter Befund erklärte, weshalb wir die CUDA-Version des Containers festgehalten hatten. Für UD-IQ4_XS wurde mit CUDA 13.2 unbrauchbare Ausgabe gemeldet; CUDA 13.3 behob den Fehler wieder. Unser llama.cpp-Image mit CUDA 12.8 umging diesen konkreten Pfad. Auf einem frischen 13.2-Stack hätte dasselbe Fehlerbild erneut wie ein kaputter Quant ausgesehen.

Die praktische Lehre daraus ist enger gefasst und nützlicher: Wenn Qwen3.6 in einer agentischen Sitzung unzuverlässig wirkt, prüfen wir zuerst Serverstand, SWA-Cache, Chat-Template, Sampling, Treiberzustand, CUDA-Version und Werkzeugrechte. Erst danach erhöhen wir die Quantisierungspräzision. In diesem Fall sparte diese Reihenfolge VRAM, Downloadzeit und mehrere falsche Umbauten.

Speculative Decoding gegen den langen Kontext

Die 77 tok/s stammten aus einem kurzen Prompt. Sobald eine Agentensitzung mehr als 100.000 aktive Tokens umfasste, sank der Decode-Durchsatz auf ungefähr 35 tok/s. Das blieb brauchbar, war aber deutlich zu spüren. Bei langem Kontext muss für jedes neue Token mehr Cache gelesen werden; der KV-Cache wurde damit zum dominierenden Faktor für die Speicherbandbreite.

Aktuelle Versionen von llama-server bieten zwei Verfahren, die gut zu diesem Rechner passen. Beide gehören zum Speculative Decoding: Ein schneller Pfad schlägt mehrere Tokens vor, das Hauptmodell prüft sie gesammelt. Bei korrekter Verifikation bleibt die Ausgabeverteilung des Zielmodells erhalten; beschleunigt wird nur ihre Berechnung.

Prompt-Lookup per n-Gram kostet praktisch keinen VRAM

--spec-type ngram-simple benötigt kein Draft-Modell. Die Runtime sucht im vorhandenen Kontext nach bereits aufgetretenen n-Grams und übernimmt die darauf folgenden Tokens als Vorschlag. Besonders gut funktioniert das bei Ausgaben, die Passagen aus dem Prompt wörtlich wiederholen.

Unser DOCX-Workflow enthält davon reichlich: Checkbox-Beschriftungen, Texte aus Tabellenzellen und wiederkehrende Vorlagenstrings. Wir ergänzten diese Parameter:

--spec-type ngram-simple --spec-ngram-simple-size-n 8 \
--spec-ngram-simple-size-m 32 --spec-draft-n-max 32

In der realen Aufgabe lag die Akzeptanzrate bei 19 bis 37 Prozent. Bei einer Zusammenfassung mit überwiegend neu formuliertem Text waren es nur ungefähr fünf Prozent, was zum Mechanismus passt. Ein vollständiger sauberer DOCX-Lauf dauerte mit n-Gram 1.097 Sekunden statt ungefähr 1.900 Sekunden ohne Speculative Decoding. Das entspricht rund 1,7-facher End-to-End-Geschwindigkeit, ohne zusätzliches Modell und praktisch ohne weiteren VRAM-Bedarf.

MTP bringt den nächsten Sprung

Qwen3.6-35B-A3B wurde mit Multi-Token Prediction trainiert. Die Konfiguration enthält mtp_num_hidden_layers: 1. Der normale UD-IQ4_XS-GGUF lässt die zugehörigen Tensoren weg. Für diesen Pfad verwendeten wir deshalb den separaten Build unsloth/Qwen3.6-35B-A3B-MTP-GGUF. Seine IQ4_XS-Datei ist ungefähr 450 MB größer und enthält den zusätzlichen Next-Token-Head. llama-server aktiviert ihn mit --spec-type draft-mtp.

Anders als Prompt-Lookup kann MTP bei jedem Token Vorschläge liefern, auch innerhalb neu erzeugter Reasoning-Passagen. Beide Verfahren lassen sich kombinieren:

--spec-type draft-mtp,ngram-simple

Die zusätzlichen Gewichte und der kleine Draft-KV-Cache kosteten allerdings gerade genug VRAM, um den automatischen Layer-Split aus dem Gleichgewicht zu bringen. Bei 262k Kontext und q8-KV blieben auf GPU1 nur noch 357 MiB frei; der Prefill endete mit OOM. Ein manueller Split löste das Problem:

-sm layer -ts 52,48

Danach belegte GPU0 11.417 MiB und GPU1 11.541 MiB. Auf beiden Karten blieben ungefähr 380 MiB Reserve. Der volle 262k-Kontext und MTP passten damit gleichzeitig in den Speicher.

Eine zunächst plausible Idee half nicht: den Monitor an den VGA-Ausgang des Mainboards anschließen, um auf GPU0 Framebuffer-Speicher freizugeben. Das ASPEED-BMC des Boards führte die Konsole bereits. lspci | grep VGA zeigte 09:00.0 ASPEED Graphics Family, und auf beiden 3060 war /app/llama-server der einzige GPU-Prozess. Die ungleiche Belegung kam ausschließlich vom automatischen Split. Entsprechend konnte nur -ts etwas daran ändern.

Die gemessenen Decode-Raten sahen anschließend so aus:

Konfiguration kurzer Decode Decode bei 100k
Basis ohne Speculative Decoding 76 35
nur n-Gram 77 39
MTP + n-Gram 91 63

Bei 100.000 aktiven Tokens stieg der Durchsatz auf das 1,8-Fache. Auch kurze interaktive Prompts profitierten mit 91 statt 77 tok/s. Im vollständigen DOCX-Lauf war der zusätzliche Gewinn gegenüber n-Gram allein mit ungefähr zehn Prozent wesentlich kleiner. Prefill, Python-Arbeit und Wartezeiten zwischen Werkzeugaufrufen werden durch schnelleren Decode nicht kürzer. Die End-to-End-Zahl ist uns deshalb wichtiger als ein isolierter Spitzenwert.

Gemma war schneller und erledigte die Arbeit trotzdem schlechter

Als Gegenkandidat bot sich Gemma 4 26B-A4B an. Das Modell besitzt rund 25,2 Milliarden Gesamtparameter und aktiviert ungefähr 3,8 Milliarden. Damit passt es architektonisch viel besser zu zwei RTX 3060 als das dichte Gemma-4-Modell mit 31B.

Die Unsloth-GGUFs ließen sich ohne besondere Umwege laden:

Datei Größe
gemma-4-26B-A4B-it-UD-IQ4_XS.gguf 12,66 GiB
gemma-4-26B-A4B-it-UD-Q4_K_S.gguf 15,36 GiB
gemma-4-26B-A4B-it-UD-Q4_K_M.gguf 15,78 GiB

Mit Q4_K_M erhielten wir zunächst:

Konfiguration kurzer Decode Decode bei 100k
Gemma 26B-A4B Q4_K_M 69 33

Gemma lag damit bereits nahe bei Qwen-A3B. Zusätzlich existiert ein externer MTP-Sidecar, mtp-gemma-4-26B-A4B-it-Q8_0.gguf, mit nur ungefähr 0,43 GiB. Entscheidend war der richtige Speculative-Decoding-Pfad: draft-mtp, nicht draft-simple.

--spec-type draft-mtp \
--spec-draft-model /models/gemma26a4b/mtp/MTP/mtp-gemma-4-26B-A4B-it-Q8_0.gguf \
--spec-draft-n-max 2 \
--spec-draft-device CUDA1

Bei 131k Kontext erreichte dieser Aufbau:

Konfiguration kurzer Decode Decode bei 100k
Gemma 26B-A4B Q4_K_M + MTP 107 74

Nach weiterem Tuning schaffte Gemma auf dem Rechner sogar 240k Kontext. Beim reinen Durchsatz lag es damit vor Qwen-A3B. Für die Modellauswahl fehlte jedoch noch die entscheidende Messung: die eigentliche Arbeit.

Im identischen mehrstufigen DOCX-Benchmark brach Gemmas agentische Leistung ein:

Lauf Punktzahl Laufzeit
1 35 2.155 s
2 0 1.876 s

Keiner der beiden Läufe bestand vollständig. Im ersten fehlte nur das kombinierte Dokument, im zweiten war zusätzlich das Ranglistenformat beschädigt. Beide Ergebnisse lagen unter dem Qwen-Pfad, obwohl Gemma mehr Tokens pro Sekunde erzeugte.

Damit entstand auf diesem Rechner folgende Rangfolge:

Modell Konfiguration kurzer Decode Decode bei 100k Ergebnis der Agentenaufgabe
Qwen3.6-35B-A3B MTP + n-Gram, 262k 91 63 saubere Läufe, bester Arbeitspfad
Gemma 4 26B-A4B MTP, 240k 107 74 schnellerer Decode, schlechter im realen Auftrag

Gemma bleibt ein sehr schneller Kandidat. Für ein allgemeines Qualitätsurteil wären zwei Läufe ohnehin zu wenig. Auf diesem Rechner und in dieser strukturierten Werkzeugaufgabe gewann jedoch der Qwen-Pfad. Gemmas Nutzwert muss sich in weiteren realen Aufgaben zeigen; eine hohe TG-Zahl ersetzt diese Prüfung nicht.

Der Benchmark selbst täuschte einen Modellfehler vor

Während einer späteren Kampagne meldete unser Harness für neun Läufe keinen einzigen Erfolg. Wir suchten den Fehler stundenlang im Modellverhalten, weil die Symptome zu den früheren abreißenden Antworten zu passen schienen.

Der Fehler saß im Python-Wrapper. subprocess.run(cwd=X) setzt das Arbeitsverzeichnis des Prozesses, aktualisiert aber nicht automatisch die Umgebungsvariable $PWD. OpenCode las intern $PWD. Dadurch schrieb jeder Lauf sein DOCX in dasselbe Verzeichnis /home/lyth/benchmark/result/ und überschrieb den vorherigen. Der Scorer prüfte währenddessen die leeren Kampagnenverzeichnisse und meldete folgerichtig einen Fehler nach dem anderen.

Die Korrektur war klein:

subprocess.run(["opencode","run","--dir",wd, ...],
               cwd=wd, env={**os.environ, "PWD": wd})

Der unmittelbar folgende Lauf erreichte PASS mit 100 von 100 Punkten. Alle Diagnosen dieser Kampagne, die dem Modell abreißende Ausgaben anlasteten, beruhten auf Messartefakten. Seitdem bewerten wir bei Agentenläufen die erzeugten Dateien und ihre tatsächlichen Zielpfade, nicht nur Exit-Codes und Logausgaben. Wer Prozesse aus Python startet, muss cwd und $PWD gegebenenfalls gemeinsam setzen.

Zwei naheliegende Pfade, die auf diesem Rig nicht funktionierten

Negative Ergebnisse sind bei einem Budget-Aufbau besonders nützlich. Sie zeigen, welche vermeintlich günstigen Alternativen am Ende nur Zeit, Speicher oder beides kosten.

Das dichte Qwen3.6-27B war viermal langsamer

Ein Modell mit 27B klingt zunächst passender für 24 GB VRAM als eines mit 35B. Wir testeten deshalb einen Q4_K_M-GGUF von Bartowski mit ungefähr 17 GB, ebenfalls mit 8-Bit-KV und Layer-Split.

Das Ergebnis betrug ungefähr 18 tok/s.

Beim dichten Modell müssen für jedes Token alle Gewichte gelesen werden. Der Layer-Split verschärft den Engpass, weil beide 3060 ihre Modellhälften nacheinander berechnen. Zuerst arbeitet Karte 0, dann Karte 1. Zum Vergleich erreichte eine einzelne V100 mit demselben dichten Modell ungefähr 25 tok/s. In dieser Konfiguration begrenzte die Architektur den Durchsatz stärker als die nominelle Gesamtgröße des Modells.

Der dichte 27B-Pfad war damit auf derselben Hardware mehr als viermal langsamer als 35B-A3B. Auch beim Kontext verlor er: q8-KV reichte nur bis ungefähr 128k. Mit q4-KV und weiteren Anpassungen kamen wir auf etwa 192k, danach folgte OOM. Wir löschten das Modell wieder. Es war kleiner, langsamer und bot weniger Kontext, eine selten vollständige Niederlage.

EXL3 blieb instabiler als der optimierte llama.cpp-Pfad

Wir prüften außerdem eine EXL3-Variante mit 4,00 bpw und ungefähr 19,4 GB über ExLlamaV3 und TabbyAPI. In unseren Läufen war dieser Pfad deutlich instabiler als llama.cpp mit --swa-full. Eine direkt vergleichbare Option für einen SWA-Cache in voller Größe stand dort für unseren Versuch nicht zur Verfügung.

EXL3 bietet eine interessante Quantisierung und eine auf Consumer-GPUs ausgerichtete Runtime. Für die zuverlässig wiederholbare Agentenaufgabe führte unser funktionierender Weg trotzdem über llama.cpp. Auch das ist ein Ergebnis dieser Versionen und dieses Workloads, kein allgemeines Urteil über ExLlamaV3.

Leistungsaufnahme, Euro und die Grenzen des Vergleichs

Unter Last benötigte jede RTX 3060 ungefähr 170 Watt, im Leerlauf rund 26 Watt. Der vollständige Budget-Rechner lag mit beiden GPUs, Xeon, Board und übrigen Komponenten grob bei 400 Watt unter Last.

Das läuft ohne besondere Planung an einer normalen Steckdose. Der Aufbau braucht weder ein 1.600-Watt-Servernetzteil noch einen eigenen Stromkreis. Auch die Lastwechsel sind wesentlich unspektakulärer als bei unserem 3090-Rig, dessen Hardwarefehler bereits einen eigenen Artikel gefüllt haben.

Für Qwen3.6-35B-A3B ergab sich in unseren dokumentierten Pfaden dieser grobe Vergleich:

Rechner Quant Geschwindigkeit Kostenklasse
2 x RTX 3060, Budget-Rig UD-IQ4_XS auf llama.cpp ca. 77 tok/s ca. 120 Euro für Board und CPU plus zwei gebrauchte 3060
3090-Pfad auf dem großen Rig FP8 auf vLLM ca. 171 tok/s vollständiger Rechner im Bereich mehrerer Tausend Euro

Der schnellere Pfad war ungefähr 2,2-mal so schnell. Das Budget-Rig kostete und verbrauchte dabei nur einen Bruchteil dessen, was das große Rig verlangt, und lieferte trotzdem mehr als ein Drittel seines Durchsatzes. Dieser Vergleich ist bewusst keine universelle Preis-Leistungs-Tabelle: Gebrauchtpreise schwanken, die Quants unterscheiden sich, und der große Rechner kann Aufgaben übernehmen, die in 24 GB VRAM grundsätzlich nicht passen.

Für absolute Geschwindigkeit, große Modelle und echte Mehrbenutzerlast bleibt das 3090-Rig in einer anderen Klasse. Als einzelner schneller lokaler Agent ist der kleine Rechner wirtschaftlich wesentlich interessanter. Sein Vorteil entsteht aus der passenden Kombination von Architektur und Aufgabe. Eine RTX 3060 bleibt dabei eine RTX 3060.

Der billige Rechner funktionierte erst nach der teuren Diagnose

Die Hardwaregeschichte lässt sich auf 24 GB VRAM aus zwei gebrauchten RTX 3060, einen 25-Euro-Xeon und ein 59-Euro-Board verkürzen. Der entscheidende Teil beginnt danach. Qwen3.6-35B-A3B macht diesen Unterbau durch seine rund drei Milliarden aktiven Parameter schnell genug. Die hybride Attention-Architektur hält den KV-Cache so klein, dass selbst der native 262k-Kontext hineinpasst.

Zum brauchbaren Arbeitsrechner wurde das System erst, als wir die Ebenen getrennt prüften. --swa-full korrigierte den SWA-Cache für unseren langen Gesprächspfad. Eine OpenCode-Berechtigung beseitigte einen scheinbaren Modellfehler. Das CUDA-12.8-Image umging ein Problem von CUDA 13.2 mit IQ4_XS. Ein manueller Tensor-Split schuf die letzten hundert MiB für MTP, und die korrekte $PWD-Variable rehabilitierte schließlich ein Modell, das unser eigener Scorer zu Unrecht beschuldigt hatte.

Das Ergebnis ist kein Kuriosum für den Basteltisch. Mit 77 tok/s in der Basis, 91 tok/s mit MTP und n-Gram sowie 63 tok/s bei 100k aktivem Kontext erledigt der Rechner reale lokale Agentenarbeit. Billig war vor allem die Hardware. Die Diagnose kostete so viel wie bei jedem anderen Inferenzserver: genaue Messungen und die Bereitschaft, nicht zuerst dem Quant die Schuld zu geben.

Modelle, Quants und Engines

Kommentar schreiben

Deine E-Mail-Adresse wird nicht veröffentlicht. Der erste Kommentar wird manuell freigegeben.