Drugi kabel, którego nie kupiłem
Dwa NVIDIA DGX Spark, jeden szybki kabel między nimi i duży model językowy rozłożony na oba. Oczywistą modernizacją był drugi kabel. Pomiar przed zakupem zaoszczędził pieniądze — i zmusił mnie do wycofania czegoś, co wcześniej opowiadałem ludziom.
Punkt wyjścia
Dwie maszyny NVIDIA DGX Spark połączone bezpośrednio pojedynczym łączem QSFP 200 Gb/s — bez switcha, tylko kabel między adapterami ConnectX-7 obu skrzynek. Duży model działa rozłożony na obie, połowa wag na każdej, więc każdy wygenerowany token wymaga, żeby obie połowy ze sobą porozmawiały.
To ostatnie zdanie sprawia, że drugi kabel wygląda na oczywiście słuszny. Inferencja tensor-parallel jest gadatliwa z natury. Jeśli maszyny ciągle wymieniają dane, przewód między nimi jest wąskim gardłem, a więcej przewodu to więcej szybkości.
Zgrabna historia. Chciałem liczby, zanim na to wydam.
Pomiar pierwszy: zero
Próbkowałem liczniki interfejsu sieciowego w trakcie aktywnego generowania — bajty przychodzące i wychodzące w ustalonym odstępie czasu.
0,00 Gb/s.
Nie mała liczba. Zero. Model ewidentnie działał, tokeny płynęły, a według własnej księgowości systemu operacyjnego nic nie przechodziło przez przewód.
To dobry błąd, bo kryje się za całkowicie rozsądnym założeniem: że ruch opuszczający maszynę pojawia się w jej statystykach sieciowych.
Ten ruch to RDMA — jedna maszyna zapisuje bezpośrednio do pamięci drugiej, celowo omijając stos sieciowy jądra dla niższych opóźnień. Liczniki, które czytałem, żyją w tym stosie. Nigdy go nie widzą. Nie mylą się; są ślepe, a ślepota czyta się dokładnie jak zero.
Prawdziwe liczby żyją we własnych licznikach adaptera sieci, katalog obok. Z własną pułapką: są wyrażone w 4-bajtowych słowach, a nie w bajtach. Przeczytaj je naiwnie, a zaniżysz przepustowość dokładnie 4× — na tyle wiarygodnie, że można to opublikować bez mrugnięcia okiem.
Pomiar drugi: zbyt dobry
Z właściwymi licznikami zmierzyłem, jak szybko łącze potrafi przepchnąć dane od końca do końca, a potem jak szybko model potrafi przetworzyć długi prompt wejściowy.
Szybkość przetwarzania promptu wyszła 113 000 tokenów na sekundę.
Każdy, kto ma wyczucie tego sprzętu, wie, że to nie jest prawdziwa liczba. To było trafienie w cache. Użyłem tego samego promptu w kolejnych uruchomieniach, a serwer zapamiętał przetworzony prefiks — więc mierzyłem wyszukiwanie, a nie obliczenia. Unikalna treść w każdym uruchomieniu i pojawiła się uczciwa liczba.
Warto się zastanowić, który z tych dwóch błędów był groźniejszy. Zero było oczywiście złe i naprawione w kilka minut. 113 000 było pochlebne, a pochlebne liczby się publikuje. Jeśli wynik sprawia, że twoja konfiguracja wygląda świetnie, to właśnie jego atakuj najmocniej.
Faktyczna odpowiedź
Z uczciwym oprzyrządowaniem pytanie odpowiedziało samo:
jeden kabel daje ~185 Gb/s ──────────────────────────────────────────────────── podczas generowania tokenów 3.17 Gb/s 1.7% ▏ podczas dużego wejścia 24.40 Gb/s 13.2% ██▍
Szczytowe zapotrzebowanie, w fazie najbardziej głodnej przepustowości, to 13% jednego kabla.
Drugi kabel zmieniłby wykorzystanie z 13% na 7%. Nic nie daje. Generowanie tokenów jest tu ograniczone opóźnieniem — obie połowy wymieniają małe wiadomości i czekają na siebie — a żadna dodatkowa szerokość nie skraca podróży w obie strony.
To nie jest uniwersalny werdykt. Drugi kabel miałby znaczenie przy rozłożeniu na więcej maszyn, przy układzie potokowym przesyłającym duże wyniki pośrednie między etapami albo przy trenowaniu, gdzie synchronizacja gradientów naprawdę jest głodna przepustowości. Dla tego obciążenia, na parze DGX Spark, to pieniądze za wykres, który zostaje płaski.
Odwołanie
Oto część, która podobała mi się najmniej.
Wcześniej, konfigurując łącze, znalazłem coś prawdziwego: w DGX Spark każdy port QSFP prezentuje się jako dwa logiczne urządzenia w dwóch domenach PCIe, a sterowanie tylko jednym kończy się na około 109 Gb/s — mniej więcej połowie nominalnej wartości portu. Steruj oboma, a dojdziesz do ~185. Opisałem to jako zysk 1,7×, z jednej linii konfiguracji.
Każda liczba w tym zdaniu jest poprawna. Ujęcie jest błędne.
Przy 13% szczytowego wykorzystania przejście ze 109 na 185 Gb/s to nie przyspieszenie. To zapas. Wdrożenie nie jest dzięki temu szybkie. Byłoby szybkie i bez tego. Warto to skonfigurować — to jedna linia i ubezpieczenie na wypadek przyszłego obciążenia, które faktycznie będzie ograniczone przepustowością — ale nie dlatego cokolwiek działa dobrze.
Przez jakiś czas oba twierdzenia stały na tej samej stronie: „zysk 1,7×” kilka akapitów nad „13% wykorzystania”. Czytelnik musiałby je pogodzić, a one się nie godzą. Poprawa pojemności jest poprawą wydajności tylko wtedy, gdy obciążenie było tą pojemnością ograniczone. Moje nie było, a ja cytowałem mikrobenchmark, jakby był wynikiem.
Wskazówki dla czytelnika
Zmierz zapotrzebowanie, zanim kupisz pojemność. Modernizacja była intuicyjna, na tyle tania, że łatwo ją uzasadnić, i bezcelowa. Jedno popołudnie pomiarów pobiło wiarygodny argument architektoniczny — a argument był mój, więc byłem w świetnej pozycji, żeby w niego uwierzyć.
Zero może znaczyć „nie zmierzone”, a nie „nie dzieje się”. Za każdym razem, gdy licznik pokazuje dokładnie zero, a system jest widocznie zajęty, podejrzewaj najpierw instrument, a potem system. Ścieżki obejścia — RDMA, sieć z pominięciem jądra, bezpośredni dostęp do urządzeń — istnieją właśnie po to, żeby pominąć warstwę, w której żyje twój monitoring.
Sprawdzaj jednostki wszystkiego, co czytasz z surowego licznika. Błąd 4× z liczenia w słowach zamiast bajtach daje złą liczbę, która wygląda całkowicie wiarygodnie.
Pochlebny wynik atakuj najmocniej. Pomiar wyglądający na niemożliwy zostaje poprawiony. Tylko znakomity zostaje opublikowany. Cache, rozgrzany stan i powtarzane dane wejściowe zawsze przesuwają wynik w kierunku, na który liczyłeś.
Zapas to nie szybkość. Liczba pojemności staje się liczbą wydajności dopiero wtedy, gdy coś było nią faktycznie ograniczone. Jeśli cytujesz „poprawę 1,7×” na łączu pracującym w 13%, cytujesz benchmark, a nie wynik — a jeśli liczba wykorzystania jest na tej samej stronie, opublikowałeś sprzeczność.