Twee dozen van het DGX Spark-klasse, één model: een kopie op elk verslaat het splitsen
Ops
Zeven weken lang draaide ons lokale taalmodel gesplitst over twee NVIDIA GB10-machines met tensor parallelisme. Op 22 oktober vervingen we dat door een volledige kopie van het model op elke machine met een least-connection proxy ervoor. De doorvoer ging van ongeveer 180 naar 210 tokens per seconde, en elke machine kan nu herstarten zonder het model weg te halen.
Waarom we het hadden gesplitst
Elke machine heeft 128 GB geheugen dat de CPU en de GPU delen. In september paste het model op één machine, maar de cache nauwelijks: de vLLM-build die we gebruikten kon de KV-cache van dit model niet in FP8 houden, dus de cache was in BF16, en één machine had ruimte voor 499.110 tokens daarvan. Dat is minder dan twee aanvragen bij de volledige context van het model van 262.144 tokens. Gesplitst over beide machines met tensor parallelisme over hun directe netwerkverbinding, groeide de cache tot meer dan drie miljoen tokens. We hadden het model gesplitst voor ruimte, niet voor snelheid.
Wat is er veranderd
Een nieuwere 4-bit build van hetzelfde model (NVFP4, gemaakt met quantisatie-bewuste distillatie) neemt ongeveer 71 GiB geheugen in beslag, en een community vLLM-build voor GB10 (eugr/spark-vllm-b12x) draait de cache in FP8. Nu houdt één machine het hele model en een cache van 0,76 tot 1,08 miljoen tokens, ruimte voor 2,9 tot 4,1 aanvragen bij volledige context, afhankelijk van hoeveel geheugen de machine eraan geeft. Beide kopieën gebruiken de eigen speculatieve decoding van het model (MTP, vier draft-tokens). Voordat we overschakelden, draaiden we onze tool-calling test op de nieuwe build: deze scoorde 86, de build die het verving 75.
Wat we hebben gemeten
| Gesplitst over twee machines | Een kopie op elke machine | |
|---|---|---|
| KV-cache | meer dan 3 miljoen tokens | 0,76 en 1,08 miljoen tokens |
| Aanvragen bij volledige context | ongeveer 12 | 2,9 en 4,1 |
| Doorvoer onder onze belasting | ongeveer 180 tokens/s | 210 tokens/s (111 + 99) |
| Opstarten | ongeveer 20 minuten | 5 tot 13 minuten per kopie |
| Eén machine herstart | het model is uit | de andere kopie bedient |
Eén kopie op zichzelf, naar het aantal gelijktijdige verzoeken: met korte prompts gaf 1 verzoek 36 tokens per seconde, 4 gaven 82, 8 gaven 109 en 16 gaven 150. Met de langere prompts van onze e-mailagent gaven 4 verzoeken 81, 8 gaven 113 en 16 slechts 87: voorbij acht van zulke verzoeken werd het trager.
Gedurende een middag nam de ene kopie maximaal 12 gelijktijdige verzoeken en de andere 8. De eerste produceerde 91 tot 93 tokens per seconde en voltooide 26 tot 27 verzoeken per minuut; de tweede 81 tot 83 tokens per seconde en 22 verzoeken per minuut. Beide nemen nu 12.
Wat we opgegeven hebben
Ruimte. Het gesplitste model had één cache van meer dan drie miljoen tokens; de kopieën hebben twee kleinere, en een verzoek kan alleen de cache gebruiken van de kopie die het bedient. Onze agents sturen veel korte en middelgrote verzoeken, en voor hen was doorvoer belangrijker. Als je een paar zeer lange contexten tegelijk bedient, kan splitsen nog steeds de betere keuze zijn. We hebben de gesplitste opstelling bewaard, en terugschakelen is één script.
Wat er misging tijdens de overstap
- Elf minuten zonder model, daarna 30 seconden van 502-fouten. Het gesplitste model moest stoppen voordat een kopie op dezelfde machines kon laden, dus onze agents wachtten ongeveer elf minuten. Toen de eerste kopie omhoog kwam, bleef de proxy nog ongeveer 30 seconden "502 no live upstreams" beantwoorden: hij had de backends als mislukt gemarkeerd terwijl ze uit waren (max_fails=3, fail_timeout=30s) en probeerde ze niet opnieuw totdat die tijd verstreken was.
- Een client die de proxy oversloeg. Een van onze batchjobs had het adres van de eerste machine in zijn configuratie, dus ging de hele belasting naar één kopie terwijl de andere capaciteit overhad. Elke client gaat nu via de proxy; de eigen adressen van de machines zijn alleen voor health checks.
- Metrics die sprongen. Onze doorvoermonitor las de /metrics van vLLM via de proxy. Met twee backends kwamen opeenvolgende lezingen van verschillende kopieën, de tellers sprongen heen en weer, en de monitor meldde het model als uitgeput. Hij leest nu elke kopie direct en telt ze op.
- Een oude watchdog die de nieuwe kopie zou hebben gestopt. De gesplitste opstelling had een watchdog die zijn service opnieuw startte wanneer het model niet meer antwoordde. De nieuwe service verklaart Conflicts= met de oude, zodat die herstart de nieuwe kopie zou hebben gestopt. Deze moest worden uitgeschakeld met
systemctl disable --now, niet alleen gestopt, omdat onze ops-laag units opnieuw start die ingeschakeld zijn maar niet draaien. - Geheugen op de machine die ander werk doet. Eén machine draait ook onze databases en site-builds. Met vLLM's gpu-memory-utilization op 0,78 bleef er slechts 5 GiB over voor de rest, dus hebben we dat verlaagd naar 0,74 en alleen die kopie opnieuw gestart terwijl de andere de last droeg. Op deze machines komt de geheugentoewijzing aan vLLM uit hetzelfde geheugen als alles anders.
Als je twee van deze machines hebt
- Als het model en de cache die je nodig hebt op één machine passen, draai dan op elke machine een kopie achter een least-connection proxy. Splits het model alleen als één machine de cache die je nodig hebt niet kan bevatten.
- Richt elke client op de proxy, inclusief batchjobs en oude configuraties.
- Lees metrics rechtstreeks van elke kopie.
- Geef elke machine zijn eigen geheugentoewijzing, op basis van wat er verder op draait.
- Stel in hoeveel aanvragen een kopie tegelijk bedient op basis van je eigen verkeer: voor onze mix was 12 beter dan 8.
- Reken erop dat de wisseling het model wegneemt zolang een kopie nodig heeft om te starten, en plan daarvoor.
Factory Floor bevat de proxy- en startconfiguraties voor één of twee machines, de health check en de tool-use test die we uitvoeren voordat we van model wisselen, met de watchdogs en geheugenbewakers voor de rest van de machine.