Due box di classe DGX Spark, un modello: una copia su ciascuna batte la suddivisione

Ops

Per sette settimane il nostro modello linguistico locale è stato eseguito suddiviso su due macchine NVIDIA GB10 con parallelismo dei tensori. Il 22 ottobre abbiamo sostituito quella configurazione con una copia completa del modello su ciascuna macchina e un proxy least-connection davanti. La produttività è passata da circa 180 a 210 token al secondo, e ora una delle due macchine può riavviarsi senza togliere il modello.

Perché lo avevamo suddiviso

Ogni macchina ha 128 GB di memoria condivisa tra CPU e GPU. A settembre il modello stava su una sola macchina, ma la sua cache a malapena: la build vLLM che usavamo non poteva mantenere la cache KV di questo modello in FP8, quindi la cache era in BF16, e una macchina aveva spazio per 499.110 token di cache. Sono meno di due richieste al contesto completo del modello di 262.144 token. Suddiviso su entrambe le macchine con parallelismo dei tensori sul loro collegamento di rete diretto, la cache è cresciuta a oltre tre milioni di token. Avevamo suddiviso il modello per lo spazio, non per la velocità.

Cosa è cambiato

Una build più recente a 4 bit dello stesso modello (NVFP4, realizzata con distillazione consapevole della quantizzazione) occupa circa 71 GiB di memoria, e una build vLLM della community per GB10 (eugr/spark-vllm-b12x) esegue la sua cache in FP8. Ora una macchina contiene l'intero modello e una cache da 0,76 a 1,08 milioni di token, spazio per 2,9-4,1 richieste a contesto completo, a seconda di quanta memoria la macchina le concede. Entrambe le copie usano il decoding speculativo del modello stesso (MTP, quattro token di bozza). Prima di passare, abbiamo eseguito il nostro test di tool-calling sulla nuova build: ha ottenuto 86, la build sostituita 75.

Cosa abbiamo misurato

Suddiviso su due macchineUna copia su ciascuna macchina
Cache KVoltre 3 milioni di token0,76 e 1,08 milioni di token
Richieste a contesto completocirca 122,9 e 4,1
Produttività sotto il nostro caricocirca 180 token/s210 token/s (111 + 99)
Avviocirca 20 minuti5-13 minuti per copia
Una macchina si riavviail modello è giùl'altra copia serve

Una copia da sola, in base al numero di richieste che gestisce contemporaneamente: con prompt brevi, 1 richiesta generava 36 token al secondo, 4 ne generavano 82, 8 ne generavano 109 e 16 ne generavano 150. Con i prompt più lunghi del nostro agente email, 4 ne generavano 81, 8 ne generavano 113 e 16 solo 87: oltre otto richieste di questo tipo, aggiungerne altre rallentava il sistema.

Per un pomeriggio, una copia ha gestito al massimo 12 richieste contemporaneamente e l'altra 8. La prima ha prodotto da 91 a 93 token al secondo e ha completato da 26 a 27 richieste al minuto; la seconda da 81 a 83 token al secondo e 22 richieste al minuto. Ora entrambe ne gestiscono 12.

Cosa abbiamo sacrificato

Spazio. Il modello diviso aveva una cache di oltre tre milioni di token; le copie ne hanno due più piccole e una richiesta può usare solo la cache della copia che la gestisce. I nostri agenti inviano molte richieste brevi e medie e per loro la produttività contava di più. Se servi pochi contesti molto lunghi contemporaneamente, la divisione potrebbe ancora essere la scelta migliore. Abbiamo mantenuto la configurazione divisa e il ritorno a essa richiede uno script.

Cosa è andato storto durante la migrazione

  1. Undici minuti senza modello, poi 30 secondi di errori 502. Il modello diviso doveva fermarsi prima che una copia potesse caricarsi sulle stesse macchine, quindi i nostri agenti hanno atteso circa undici minuti. Quando la prima copia è partita, il proxy ha continuato a rispondere "502 no live upstreams" per circa altri 30 secondi: aveva segnato i backend come falliti mentre erano giù (max_fails=3, fail_timeout=30s) e non li ha riprovati fino alla scadenza di quel tempo.
  2. Un client che saltava il proxy. Uno dei nostri job batch aveva l'indirizzo della prima macchina nella sua configurazione, quindi tutto il suo carico andava a una copia mentre l'altra aveva capacità disponibile. Ora ogni client passa attraverso il proxy; gli indirizzi delle macchine servono solo per i controlli di salute.
  3. Metriche che saltavano. Il nostro monitor della produttività leggeva i /metrics di vLLM attraverso il proxy. Con due backend, le letture consecutive provenivano da copie diverse, i contatori saltavano avanti e indietro e il monitor segnalava il modello come affamato. Ora legge direttamente ogni copia e le somma.
  4. Un vecchio watchdog che avrebbe fermato la nuova copia. La configurazione divisa aveva un watchdog che riavviava il servizio quando il modello smetteva di rispondere. Il nuovo servizio dichiara Conflicts= con il vecchio, quindi quel riavvio avrebbe fermato la nuova copia. Doveva essere disattivato con systemctl disable --now, non solo fermato, perché il nostro livello di riavvia le unità abilitate ma non in esecuzione.
  5. Memoria sulla macchina che fa altro lavoro. Una macchina esegue anche i nostri database e le build del sito. Con gpu-memory-utilization di vLLM a 0.78, restavano solo 5 GiB per tutto il resto, quindi l'abbiamo abbassato a 0.74 e riavviato solo quella copia mentre l'altra reggeva il carico. Su queste macchine, la quota che dai a vLLM esce dalla stessa memoria di tutto il resto.

Se hai due di queste macchine

Factory Floor include il proxy e le configurazioni di avvio per una o due macchine, il controllo di integrità e il test di uso degli strumenti che eseguiamo prima di cambiare modelli, con i watchdog e le guardie di memoria per il resto della macchina.

Aggiornato: