Startseite / Artikel

Warum Dual-RTX-3090-Systeme mit SGLang und DFlash2 nur 55 Tok/s erreichen statt der angegebenen 220 Tok/s.

Ein praktischer Überblick über den Betrieb von Qwen3.8-27B auf zwei RTX 3090-Grafikkarten mit SGLang sowie darüber, wie PCIe x4-Verbindungen, das Fehlen von P2P-Funktionen, ein begrenzter KV-Cache und Stromspitzen die angegebenen Geschwindigkeiten beeinträchtigten.

Verlag: REACTAPP.TOP7 Min. Lesezeit1634 Wörter

Quellen: Community reports 220 tokens/second Qwen3.8–27B on two RTX 3090s. I tried it — and figured out why most people can’t reach it

Ein kurzes Video hat sich unter den Anhängern der Selbsthosting-Lösungen verbreitet. Es zeigt Qwen3.8-27B dabei, Code auf einem Paar RTX 3090-Karten zu erstellen, wobei die Generierungsgeschwindigkeit bei 220 Tokens pro Sekunde liegt. Die Konfiguration verwendet SGLang in Kombination mit DFlash2, einem spekulativen Entwurfswerkzeug. Dieses kleine Modell schlägt gleichzeitig sieben mögliche Tokens vor, und das große Modell akzeptiert oder lehnt sie in einer einzigen Durchlaufphase ab, sodass richtige Vorhersagen keinerlei Kosten verursachen.

Der Reiz geht über eine einfache Demo hinaus. Bei dieser Geschwindigkeit könnte ein Agent auf ein lokal gehostetes Modell verweisen, das mehrere Programmieragenten nebeneinander auf einem Gerät ausführt – ohne API-Schlüssel, ohne gebührenbasierte Abrechnung und ohne dass Daten das Gebäude verlassen. Ein Heimserver mit zwei Ersatz-GPUs der 3090-Klasse scheint ideal zu sein: Doppelte Speicherkapazität bedeutet sicherlich 220 Tokens pro Sekunde.

Es funktioniert jedoch nicht so. Die Gründe sind aufschlussreicher als das Video selbst.

SGLang installieren

Jeder, der mit llama.cpp vertraut ist – bei dem ein Binärdatei und eine Modelldatei bereits nach zehn Minuten ein Chatmöglichkeit bieten –, wird feststellen, dass SGLang demütigend ist. Hier ist die Vorgehensweise auf einem Ubuntu-Server.

Schritt 1: Die Python-Umgebung. SGLang verfügt über einen großen Abhängigkeitsbaum, weshalb nach dem Klonen des Repositoriums eine eigene virtuelle Umgebung unter Python 3.12 erstellt wird.

git clone https://github.com/sgl-project/sglang.git
cd sglang
python3.12 -m venv .venv
source .venv/bin/activate

Schritt 2: eine hartnäckige Installation. Der erste echte Installationsversuch dauerte etwa 39 Minuten, füllte einen Cache mit den meisten Paketen von PyPI und wurde anschließend aufgrund eines Fehlers abgebrochen.

RuntimeError: cargo is required to discover the Rust extension modules

Ein Rust-Compiler für einen GPU-Inferenzserver. Neuere SGLang-Versionen enthalten optionale Rust-Erweiterungen, und die Build-Tools weigern sich, ohne Cargo weiterzumachen – obwohl der Engine diese nie verwendet werden. Durch das Setzen einer Umgebungsvariable können sie übersprungen werden.

SGLANG_BUILD_RUST_EXTS=none pip install -e "./python[all]"

Die zweite Ausführung benötigte etwa weitere 30 Minuten, bevor sie ohne Probleme abgeschlossen werden konnte. Das Ergebnis war sglang 0.5.20; Torch wurde für CUDA 13 kompiliert und Triton automatisch auf 3.7.1 festgelegt – genau das, was Ampere-GPUs benötigen, um einen bekannten Kernel-Bug zu umgehen.

Schritt 3: Die Modelle. Es folgen zwei Downloads gemäß dem Vorgehen der Community:

  • cyankiwi/Qwen3.8-27B-AWQ-INT4 ist das Hauptmodell. Durch INT4-Quantisierung wird es auf etwa 20 GB in 5 Teilen verkleinert, was ausreicht für zwei 24-GB-GPUs.
  • z-lab/Qwen3.8-27B-DFlash2 ist eine experimentelle Version, die etwa 3,9 GB groß ist.

Schritt 4: Der Startvorgang. Ein Befehl reicht aus, wobei TP=2 für Tensor-Parallelismus auf beiden 3090-GPUs steht.

CUDA_VISIBLE_DEVICES=1,2 python -m sglang.launch_server \
  --model-path /mnt/models/Qwen3.8-27B-AWQ-INT4 \
  --tp 2 \
  --speculative-algorithm DFLASH \
  --speculative-draft-model-path /mnt/models/Qwen3.8-27B-DFlash2 \
  --speculative-num-draft-tokens 8 \
  --mamba-radix-cache-strategy extra_buffer \
  --disable-prefill-cuda-graph \
  --cuda-graph-max-bs-decode 16 \
  --mem-fraction-static 0.85 \
  --context-length 65536 \
  --reasoning-parser qwen3 \
  --host 0.0.0.0 --port 30000

Achten Sie auf zwei Fallstricke. Eine Flagge mit dem Namen --cuda-graph-max-bs verhindert den Start, da neuere Versionen sie in getrennte Varianten für Dekodierung und Vorausfüllung umbenannt haben. Zudem führt das Auslassen von --reasoning-parser qwen3 dazu, dass die API das rohe Denkprozess des Modells zusammen mit verschachtelten think-Taggen im normalen Inhaltsfeld zurückgibt. Durch Hinzufügen des Parsers wird die Ausgabe in ordentliche Felder reasoning_content und content aufgeteilt, genau wie es andere OpenAI-kompatible Server verwenden – was jedem, der einen Client schreibt, zugutekommt.

Schritt 5: Abwarten. Der Start beim ersten Mal dauerte etwa sechs Minuten, bis die Meldung „Bereit“ angezeigt wurde. Es gab keinen Fehler: SGLang kompiliert die GPU-Kerne rechtzeitig über Triton und speichert die CUDA-Graphen auf jeder Karte. Spätere Starts nutzen den Cache und sind in etwa 35 Sekunden abgeschlossen. Die Fortschrittsleiste, die während dieses ersten Starts scheinbar stecken bleibt, zeigt lediglich den laufenden Kompilierprozess an.

Was die Messungen zeigten

Der Server funktionierte. Er beantwortete Anfragen, streamte reasoning_content und nahm Verbindungen von Agenten entgegen. Hier sind die Werte, die direkt auf dem Gerät gemessen wurden:

| Metric                  | Min      | Max       | Average   |
|-------------------------|----------|-----------|-----------|
| Decode speed            | 47 t/s   | ~100 t/s  | ~55 t/s   |
| Prompt prefill          | 342 t/s  | 466 t/s   | ~428 t/s  |
| Accept length (DFlash)  | 3.2      | 6.8       | ~4        |

Und das ist das Bild der Kapazität:

| Capability              | Advertised limit          | Reality               |
|-------------------------|---------------------------|-----------------------|
| Max context per request | 65,536 (I set it)         | fits, one at a time   |
| Total KV pool           | ~131K tokens max          | 103K at default flags |
| Parallel slots          | 48 concurrent requests    | 3–6 with real prompts |

Das sind im Durchschnitt etwa 55 Tokens pro Sekunde und nicht 220. Das Problem ist, dass eine einzige RTX 3090 Ti mit llama.cpp bei derselben Modellfamilie etwa 75 Tokens pro Sekunde erreicht. Zwei Karten mit Tensor-Parallelismus sowie einem fortschrittlichen Drafter unterlagen einer Karte, die den älteren Ansatz nutzte.

Die Akzeptanzraten erklären einen Teil dieser Schwankungen. Wenn DFlash richtig ratet – mit 6,8 von 8 Tokens akzeptiert – erreichen die Batch-Verarbeitungen kurzzeitig etwa 100 t/s. Wenn es falsch liegt, sinkt die Geschwindigkeit auf 47. Selbst der beste Batch blieb weit unter 220 zurück, sodass der Drafter nicht der Hauptgrund war. Etwas anderes bremste die Geschwindigkeit aus.

Der Hinweis im Protokoll

Der Hinweis lag die ganze Zeit über im Startprotokoll:

Setup Custom allreduce failed with CUDART error: peer access is not
supported between these two devices.

Beim Tensor-Parallelismus wird jede Gewichtsmatrix auf die beiden GPUs aufgeteilt, und die teilweisen Ausgaben müssen nach jedem Attention-Block sowie jedem MLP-Block zusammengeführt werden. In einem 27B-Modell führt das zu etwa 100 Hin- und Rückwegübertragungen pro erzeugtem Token. Eine solche Geschwindigkeit erfordert eine schnelle Verbindung zwischen den Karten – diese beiden Karten verfügten jedoch nicht darüber. Zwei separate Fehler verstärkten sich gegenseitig.

Kein Peer-to-Peer. GeForce-Karten unterstützen offiziell nicht mehr GPU-direct P2P, wodurch jede Übertragung über den System-RAM abgewickelt wird: von GPU 1 in den Speicher, zu GPU 2 und zurück. Dadurch verdoppeln sich die Kopien und die Latenzzeit verdreifacht sich.

Die enge Verbindung – der eigentliche Flaschenhals. Ein Blick auf den Zustand der physischen Verbindung zeigt das Problem:

/sys/bus/pci/devices/05:00.0/current_link_width → 4   (max 16)
/sys/bus/pci/devices/09:00.0/current_link_width → 4   (max 16)

Sowohl die GPUs nutzten PCIe x4, nicht x16. Jede GPU verfügt über vier Datenkanäle, die über die Host-Brücke geleitet werden und lediglich eine Übertragungsgeschwindigkeit von 1 bis 2 GB/s zwischen den Karten ermöglichen. Dennoch wird diese Verbindung bei jeder Aufgabe etwa 100 Mal überlastet. Die GPUs standen in Wartezeiten still, während sie auf Kopien warteten. Das erklärt, warum die Vorausbefüllung nur bei 428 t/s abläuft, obwohl mit 1.200 oder mehr erwartet wurde, und warum die Dekodierung bei etwa 55 stagniert.

GPUs sind schnell. Die Verbindung zwischen ihnen ist langsam. Unter Tensor-Parallelismus wird die Verbindung faktisch zum Modell selbst.

Wie jemand auf 220 kommt

Niemand zeigt bei einer Demonstration seine Hauptplatine. Ein gut verdrahtetes x16-Paar mit funktionierendem P2P bietet etwa das Zehnfache an Bandbreite zwischen den Karten im Vergleich zu dieser Konfiguration. Die angegebene Zahl resultierte höchstwahrscheinlich aus einem kurzen Ausführungszyklus vorhersehbarer Codeabschnitte mit vorgeheizten Caches und unter idealen Bedingungen – nicht aus einer konstanten Leistung. Der Software-Aspekt des Rezepts ist solide, geht aber stillschweigend von einer Hardware aus, die nur wenige Heimrechner besitzen.

Zwei weitere Erkenntnisse für den täglichen Gebrauch

Der Kontextraum ist kleiner als erwartet. Zwei Karten mit 24 GB scheinen ausreichend zu sein. Dann müssen jedoch die INT4-Gewichte (etwa 10 GB pro Karte), der Drafter, die CUDA-Graphen sowie – da Qwen3.8 eine hybride Aufmerksamkeitsmechanik verwendet – das rekursive Zustandsgedächtnis abgezogen werden. Für den KV-Cache blieben insgesamt etwa 103 K Token übrig, oder 131 K bei riskanteren Einstellungen. Eine Anfrage mit 65 K Token passt problemlos hinein. Die angegebenen 48 parallelen Slots reichen nicht aus, da jede aktive Konversation ebenfalls einen Teil des Mamba-Zustandspools beansprucht; realistisch passen nur drei bis sechs Sitzungen hinein. Im Gegensatz dazu weist llama.cpp fast den gesamten VRAM einem einzigen Modell zu und kann große Kontexte problemlos verarbeiten.

Ein Neustart aufgrund von Stromproblemen. Wenn beide 3090-Karten gleichzeitig arbeiten, bei voller Tensor-Parallellast Vorausdaten laden und Entwürfe erstellen, steigt der Stromverbrauch innerhalb von Millisekunden von einem Ruhewert auf etwa 700 Watt an. Netzteilanbieter kommen damit nur schwer zurecht. Ein solcher Anstieg löste den Schutzmechanismus aus und schaltete den Server mitten in der Arbeit ab. Er startete von selbst wieder, doch die Lektion bleibt: Zwei Karten bei dieser Arbeitslast erfordern ein hochwertiges Netzteil mit ausreichend Pufferkapazität für kurzfristige Spitzenlasten – die Angabe der Leistung in der Verpackung reicht dabei nicht aus.

Sollte man das zu Hause ausprobieren?

SGLang plus DFlash2 ist eine beeindruckende Ingenieursleistung, sofern ihre Annahmen zutreffen – und diese Annahmen bilden im Grunde eine Checkliste für die Hardware:

  1. Sowohl GPUs in echten x16-Slots. Consumer-Gaming-Plattformen bieten in der Regel nur eine GPU an, wobei eine zweite möglicherweise über den Chipset mit x4-Anschluss verbunden ist. Lesen Sie vor dem Kauf unbedingt die Anleitung, denn dies bestimmt zwischen 220 und 55 Leistungswerten.
  2. Funktionierende GPU-zu-GPU-Kommunikation. Das bedeutet Einstellungen wie Resizable BAR und ACS im BIOS sowie Community-Driver-Varianten für GeForce oder NVLink außerhalb des Consumer-Bereichs. Realistisch gesehen gehört das eher zum Bereich der Server-Motherboards.
  3. Eine hochwertige Stromversorgung mit ausreichend Pufferkapazität. Spitzenlasten durch zwei 350-Watt-Karten können letztendlich eine schwache Stromversorgung überlasten.
  4. Vier RTX 3090 für Qwen3.8-27B im vollen Kontext. Mit diesem Budget wird der KV-Cache nicht mehr zu einer Einschränkung, im Gegensatz zu einem eingeschränkten 103K-Pool.

Software wie diese verzeiht billige Hardware nicht – sie läuft an Stellen, an denen niemand nachgesehen hat, langsamer.

Auch ohne die notwendige Hardware bleibt llama.cpp aufgrund des fehlenden Software- und Hardware-Komplettsets die sinnvolle Wahl. Man benötigt nur eine Grafikkarte, ein einziges Binärdatei, etwa 75 Tokens pro Sekunde über herkömmliche PCIe-Verbindungen, keine Rust-Entwicklungsumgebung und keine unplanmäßigen Neustarts. Die beiden GPUs können wieder unabhängig im x4-Modus arbeiten.

Die Zahl 220 war real – sie gilt jedoch nicht für dieses System und vermutlich auch nicht für viele andere, solange die Verbindung zwischen den Karten nicht überprüft wurde.