Dwie maszyny klasy DGX Spark, jeden model: kopia na każdej bije podział
Operacje
Przez siedem tygodni nasz lokalny model językowy działał podzielony na dwóch maszynach NVIDIA GB10 z równoległością tensorową. 22 października zastąpiliśmy to pełną kopią modelu na każdej maszynie i proxy z równoważeniem obciążenia po metodzie least-connection przed nimi. Przepustowość wzrosła z około 180 do 210 tokenów na sekundę, a każda z maszyn może teraz zostać uruchomiona ponownie bez utraty modelu.
Dlaczego go podzieliliśmy
Każda maszyna ma 128 GB pamięci współdzielonej przez CPU i GPU. We wrześniu model mieścił się na jednej maszynie, ale jego cache ledwo: używana kompilacja vLLM nie potrafiła utrzymać cache KV tego modelu w FP8, więc cache był w BF16, a jedna maszyna miała miejsce na 499 110 tokenów. To mniej niż dwa żądania przy pełnym kontekście modelu wynoszącym 262 144 tokeny. Po podziale na obie maszyny z równoległością tensorową przez ich bezpośrednie łącze sieciowe cache urósł do ponad trzech milionów tokenów. Podzieliliśmy model dla miejsca, nie dla szybkości.
Co się zmieniło
Nowsza kompilacja 4-bitowa tego samego modelu (NVFP4, utworzona z destylacji z uwzględnieniem kwantyzacji) zajmuje około 71 GiB pamięci, a społecznościowa kompilacja vLLM dla GB10 (eugr/spark-vllm-b12x) uruchamia jego cache w FP8. Teraz jedna maszyna mieści cały model i cache od 0,76 do 1,08 miliona tokenów, czyli miejsce na 2,9 do 4,1 żądań przy pełnym kontekście, w zależności od tego, ile pamięci maszyna mu przydzieli. Obie kopie wykorzystują własne spekulatywne dekodowanie modelu (MTP, cztery tokeny robocze). Przed przełączeniem uruchomiliśmy nasz test wywoływania narzędzi na nowej kompilacji: uzyskała 86 punktów, a zastąpiona przez nią kompilacja 75.
Co zmierzyliśmy
| Podział na dwie maszyny | Kopia na każdej maszynie | |
|---|---|---|
| Cache KV | ponad 3 miliony tokenów | 0,76 i 1,08 miliona tokenów |
| Żądania przy pełnym kontekście | około 12 | 2,9 i 4,1 |
| Przepustowość pod naszym obciążeniem | około 180 tokenów/s | 210 tokenów/s (111 + 99) |
| Uruchomienie | około 20 minut | 5 do 13 minut na kopię |
| Restart jednej maszyny | model jest niedostępny | druga kopia obsługuje żądania |
Jedna kopia sama w sobie, w zależności od liczby obsługiwanych jednocześnie żądań: przy krótkich promptach 1 żądanie dawało 36 tokenów na sekundę, 4 dawały 82, 8 dawały 109, a 16 dawało 150. Przy dłuższych promptach naszego agenta e-mailowego 4 dawały 81, 8 dawały 113, a 16 tylko 87: powyżej ośmiu takich żądań więcej oznaczało wolniejsze działanie.
Przez jedno popołudnie jedna kopia obsługiwała maksymalnie 12 żądań jednocześnie, a druga 8. Pierwsza generowała od 91 do 93 tokenów na sekundę i kończyła od 26 do 27 żądań na minutę; druga od 81 do 83 tokenów na sekundę i 22 żądania na minutę. Teraz obie obsługują po 12.
Z czego zrezygnowaliśmy
Z miejsca. Model podzielony miał jedną pamięć podręczną o pojemności ponad trzech milionów tokenów; kopie mają dwie mniejsze, a żądanie może korzystać tylko z pamięci podręcznej tej kopii, która je obsługuje. Nasi agenci wysyłają wiele krótkich i średnich żądań, więc dla nich przepustowość była ważniejsza. Jeśli obsługujesz kilka bardzo długich kontekstów jednocześnie, podział może nadal być lepszym wyborem. Zachowaliśmy konfigurację z podziałem, a powrót do niej to jeden skrypt.
Co poszło nie tak podczas przełączania
- Jedenaście minut bez modelu, potem 30 sekund błędów 502. Model podzielony musiał się zatrzymać, zanim kopia mogła się załadować na tych samych maszynach, więc nasi agenci czekali około jedenastu minut. Gdy pierwsza kopia wstała, proxy nadal odpowiadało „502 no live upstreams” przez około 30 sekund dłużej: oznaczyło backendy jako awaryjne, gdy były wyłączone (max_fails=3, fail_timeout=30s) i nie próbowało ich ponownie, aż ten czas upłynął.
- Klient, który pominął proxy. Jedno z naszych zadań wsadowych miało w konfiguracji adres pierwszej maszyny, więc cały jego ruch trafił do jednej kopii, podczas gdy druga miała zapas mocy. Teraz każdy klient przechodzi przez proxy; adresy samych maszyn służą tylko do kontroli stanu.
- Skaczące metryki. Nasz monitor przepustowości czytał /metrics z vLLM przez proxy. Przy dwóch backendach kolejne odczyty pochodziły z różnych kopii, liczniki skakały tam i z powrotem, a monitor zgłaszał model jako wygłodzony. Teraz czyta każdą kopię bezpośrednio i sumuje wyniki.
- Stary watchdog, który zatrzymałby nową kopię. Konfiguracja rozdzielona miała watchdoga, który restartował usługę, gdy model przestawał odpowiadać. Nowa usługa deklaruje Conflicts= ze starą, więc taki restart zatrzymałby nową kopię. Trzeba ją było wyłączyć poleceniem
systemctl disable --now, a nie tylko zatrzymać, ponieważ nasza warstwa operacyjna restartuje jednostki, które są włączone, ale nie działają. - Pamięć na maszynie wykonującej inne zadania. Jedna maszyna obsługuje również nasze bazy danych i budowanie stron. Przy ustawieniu gpu-memory-utilization dla vLLM na 0,78 pozostawało tylko 5 GiB na wszystko inne, więc obniżyliśmy je do 0,74 i zrestartowaliśmy tylko tę kopię, podczas gdy druga przejęła obciążenie. Na tych maszynach udział pamięci przydzielany vLLM pochodzi z tej samej pamięci co wszystko inne.
Jeśli masz dwie takie maszyny
- Jeśli model i potrzebny Ci cache mieszczą się na jednej maszynie, uruchom kopię na każdej z nich za proxy z równoważeniem obciążenia opartym na najmniejszej liczbie połączeń. Dziel model tylko wtedy, gdy jedna maszyna nie pomieści potrzebnego cache.
- Kieruj wszystkich klientów do proxy, w tym zadania wsadowe i stare konfiguracje.
- Odczytuj metryki bezpośrednio z każdej kopii.
- Przydziel każdej maszynie własny udział pamięci, w zależności od tego, co innego na niej działa.
- Ustaw liczbę obsługiwanych jednocześnie żądań przez kopię na podstawie własnego ruchu: dla naszego miksu 12 było lepsze niż 8.
- Spodziewaj się, że przełączenie zabierze model na czas potrzebny na uruchomienie kopii, i uwzględnij to w planie.
Factory Floor zawiera proxy i konfiguracje uruchomienia dla jednej lub dwóch maszyn, kontrolę stanu oraz test użycia narzędzi, które wykonujemy przed przełączeniem modeli, wraz z watchdogami i zabezpieczeniami pamięci dla reszty maszyny.