GPT-OSS w dniu premiery
Wsparcie w dniu premiery dla GPT‑OSS na H200 poprzez adaptację ROSE przy użyciu FP8, sink attention i optymalizacji MoE
Perplexity angażuje się w udostępnianie najlepszych modeli AI osobom ciekawym świata, które wymagają dokładnych i godnych zaufania odpowiedzi oraz działań agentowych. OpenAI niedawno ogłosiło swoje modele o otwartych wagach (gpt-oss-20b i gpt-oss-120b). Podobnie jak w przypadku wielu wiodących modeli, Perplexity jest jedną z pierwszych organizacji, które oceniają gpt-oss-20b i gpt-oss-120b.
W tym wpisie dzielimy się decyzjami dotyczącymi infrastruktury naszego wewnętrznego stosu wnioskowania, które umożliwiły obsługę w dniu premiery (Day-0). Skupiamy się na obsłudze tych modeli na procesorach graficznych NVIDIA H200, szczegółowo omawiając zmiany w jądrze (kernel), wybory dotyczące wdrożenia oraz kompromisy między szybkością a kosztem.
GPT-OSS na Hopperze
Modele o otwartych wagach są dostarczane przy użyciu kwantyzacji MXFP4, co pomaga im zmieścić się w pamięci sprzętu klasy konsumenckiej lub osiągnąć szczytową przepustowość na architekturze NVIDIA Blackwell. Jednak do wstępnej oceny chcieliśmy uruchomić je na istniejących klastrach H200 Hopper przy minimalnych zmianach w silniku wnioskowania. Hopper nie posiada dedykowanych rdzeni tensorowych FP4, które zostały wprowadzone w architekturze Blackwell. W konsekwencji zdecydowaliśmy się obsługiwać modele przy użyciu precyzji FP8, aby zminimalizować potrzebę tworzenia niestandardowych jąder i jak najlepiej wykorzystać dostępny sprzęt.
Na wysokim poziomie, modele LLM typu transformer są strukturalnie proste:
Osadzanie wejściowe (Input embedding)
Sekwencja warstw transformera. Każda zawiera blok uwagi (attention block) oraz gęsty blok MLP lub rzadki blok MoE.
Projekcja logitów wyjściowych
Podczas wdrażania nowego modelu transformera interesuje nas przede wszystkim ustalenie, w jakim stopniu odpowiadają one istniejącym modelom, aby zidentyfikować wymagane zmiany. OpenAI uprzejmie udostępniło przegląd swoich modeli z wyprzedzeniem, co pozwoliło nam odpowiednio dostosować naszą konfigurację wnioskowania. Stwierdziliśmy, że model wymagał szeregu drobnych zmian.
Uwaga: GQA z mechanizmem Attention Sinks
Każda głowica uwagi ma wstępnie wytrenowany parametr typu sink, który jest uwzględniany w funkcji Softmax
Kodowanie pozycyjne YaRN, podobne do DeepSeek-V3
Projekcje QKV mają obciążenia (biases), podobnie jak Qwen 2
Projekcja wyjściowa ma obciążenie (bias)
MLP: Rzadka mieszanka ekspertów (Sparse Mixture-of-Expert, MoE)
Funkcja aktywacji SwiGLU, inna niż w większości modeli o otwartych wagach
Softmax po Top-K dla wag ekspertów
Eksperci mają obciążenia (biases)
Spodziewając się liczby parametrów w zakresie 100B-200B, zdecydowaliśmy się:
Rozszerzyć FlashInfer o obsługę mechanizmu attention sinks
Ponownie wykorzystać implementację MoE stworzoną już dla rodziny modeli DeepSeek-V3
Wybrać skalowanie blokowe FP8 128x128 jako schemat kwantyzacji, rozszerzając jądra DeepGEMM o obsługę składnika obciążenia (bias term).
Wewnętrzny silnik wnioskowania
Nasze działania związane z wnioskowaniem są wspierane przez nasz wewnętrzny silnik wnioskowania, ROSE. Rozwijamy ROSE tak, aby był elastycznym frameworkiem pozwalającym na szybkie wdrażanie nowych modeli. Ponadto, gdy zdecydujemy się na obsługę modelu na dużą skalę, stanowi on platformę do optymalizacji i zwiększania wydajności w celu osiągnięcia stanu techniki.
ROSE udostępnia silnik LLM, który może załadować model i generować zdekodowane tokeny za pośrednictwem wielu dekoderów, w tym dekoderów spekulatywnych i MTP. Same modele składają się z niestandardowego zestawu warstw, które obsługują różne parametry konfiguracyjne dla kwantyzacji i równoległości. Pod warstwami łączy zarówno jądra Triton, jak i CUDA, które wydajnie implementują i łączą obliczenia wykonywane przez poszczególne warstwy.
ROSE jest zbudowany głównie w języku Python i opiera się na PyTorch w definicjach modeli. Chociaż Python pozwala nam na elastyczność i dostosowywanie się do nowych modeli, migrujemy większość komponentów wrażliwych na wydajność, takich jak serwowanie i szeregowanie zadań (batch scheduling), do języka Rust.
Aby wdrożyć nowy model, najpierw definiujemy hierarchię modelu i budujemy dla niego konwerter wag. Po uwzględnieniu wszystkich wag definiujemy przejście w przód (forward pass) dla najprostszego przypadku użycia TP=1 i upewniamy się, że działa on od końca do końca. Na koniec implementujemy i testujemy obsługę różnych form równoległości przed wydaniem nowego kontenera i wdrożeniem modelu w klastrze w celu oceny. Następnie iterujemy wydajność, opierając się na informacjach zwrotnych z pulpitów nawigacyjnych i bardziej szczegółowych testów porównawczych.
ROSE operuje na tokenach numerycznych, mając niewielką wiedzę o formatach czatu poza testami integracyjnymi. Równolegle musieliśmy przyjrzeć się dostosowaniu otaczającej infrastruktury, aby wspierała tokenizer Harmony i poprawnie mapowała żądania na sekwencje tokenów wejściowych.
W przypadku modelu GPT OSS stwierdziliśmy, że nie są wymagane żadne zmiany w naszych dekoderach, oprzyrządowaniu wykresów CUDA ani otaczającej infrastrukturze, co pozwoliło nam skoncentrować wysiłki na dostosowaniu jąder wspierających model.
Jądra dla GPT-OSS
Uwaga typu Sink (Sink Attention)
Sink attention wprowadza dodatkowy czynnik obciążenia przed funkcją softmax, akumulowany z iloczynem Q i K:
attn = softmax(q @ k^T * sm_scale + sink) * VPodczas gdy naiwna implementacja jest dość trywialna, wydajne implementacje uwagi, takie jak FlashInfer, zrównoleglają obliczenia uwagi zarówno w niezależnych głowicach, jak i wzdłuż długości sekwencji KV. Ponadto, dla stabilności numerycznej, Softmax jest obliczany online przy jednoczesnym odjęciu elementu maksymalnego w celu zapewnienia stabilności numerycznej:
softmax(x) = e^x / sum(e^x) = e ^ (x - max(x)) / sum(e ^ (x - max(x))Implementacja online śledziła bieżącą wartość maksymalną m zainicjowaną na -inf oraz skalę d na 1. Inicjując wartość maksymalną na wartość sink bieżącej głowicy, zapewniliśmy jej prawidłową akumulację w funkcji Softmax. Musieliśmy zadbać o to, aby ta wartość maksymalna była uwzględniana tylko w pierwszym bloku, jeśli uwaga była dzielona na wiele bloków wzdłuż długości sekwencji.
Dostosowaliśmy szablon jądra FlashInfer FA2, używany zarówno do prefill, jak i decode, aby uwzględnić parametry sink.
MoE
Główną różnicą między warstwą MoE różnych modeli jest zazwyczaj schemat routingu. Spodziewając się znacznej zmienności, ROSE implementuje to jądro w Triton, co pozwala nam łatwo dostosować je do nowych modeli. W przypadku GPT-OSS musieliśmy dostosować ocenianie wag, aby wykonywać softmax tylko dla wybranych ekspertów Top-K, w przeciwieństwie do niektórych modeli w stylu DeepSeek, które obliczają softmax dla wszystkich ekspertów. Uznaliśmy, że dla zachowania dokładności kluczowe jest obliczanie wag ekspertów z precyzją bfloat16.
Wyzwanie związane z warstwą MoE wynikało z naszej decyzji o ponownym użyciu istniejących jąder DeepGEMM, które nie miały wsparcia dla członów obciążenia (bias terms). Na szczęście z czasem zintegrowaliśmy się z nimi głębiej, dokonując zmian w celu lepszego połączenia ich z naszymi niestandardowymi jądrami dyspozytorskimi all-to-all. Dodaliśmy obsługę obciążenia (bias) do DeepGEMM poprzez inicjalizację rejestrów akumulatora WGMMA wartościami obciążenia ładowanymi z pamięci. Jest to operacja jednorazowa podczas przetwarzania bloku MN, więc koszt jest prawdopodobnie pomijalny, a pamięć podręczna L2 zapewnia wydajny dostęp do tych wartości.
Ponieważ modele te nie korzystają łatwo z równoległości danych (Data Parallelism), ponownie wykorzystaliśmy jądra dyspozytorskie i kombajnujące tylko dla TP. Dane wejściowe do dyspozycji są replikowane we wszystkich rangach, więc dyspozytor wybiera tokeny dla lokalnie dostępnych ekspertów. Funkcja Combine wykonuje ważoną akumulację przez NVLink.
Poprawki wydajności
Mimo że naszym celem było szybkie uruchomienie modeli, chcieliśmy mieć pewność, że wydajność będzie nadal zadowalająca. Po upewnieniu się co do poprawności względem implementacji referencyjnej dostarczonej przez OpenAI, sprofilowaliśmy naszą implementację za pomocą domyślnego profilera PyTorch, uważając na alokacje bloków i wątków w jądrach. Ponownie wykorzystana przez nas funkcjonalność została zbudowana dla modeli, które miały większy wymiar ukryty wynoszący 7168, znacznie większy niż 2880 w modelach GPT-OSS. Specjalizowaliśmy launchery jąder, aby dobierać lepsze wymiary siatki, co znacząco zwiększyło przepustowość.
Tokenizacja i kanały
Format czatu Harmony zawiera kilka ważnych koncepcji dotyczących strukturyzowania konwersacji, treści wnioskowania i wywołań funkcji. W tym formacie każda wiadomość składa się z nagłówka i treści:
<|start|>{header}<|message|>{content}<|end|>Nagłówek zawiera metadane — co najważniejsze rolę (system, programista, użytkownik, asystent, narzędzie), kanał oraz odbiorcę, np. to=functions.<function-name> przy wywoływaniu narzędzia.
Kanały (takie jak analiza, komentarz i odpowiedź końcowa) sprawiają, że dane wyjściowe modelu są bardziej przejrzyste i segmentowane. Ponieważ format wymusza to rozdzielenie, model może wnioskować w sposób przejrzysty (i być sprawdzany pod kątem debugowania) bez ujawniania toku rozumowania w środowisku produkcyjnym. Tagi odbiorców (nagłówki to=) informują środowisko uruchomieniowe dokładnie, który podmiot powinien otrzymać kolejny ładunek — niezależnie od tego, czy jest to funkcja, sam model, czy użytkownik końcowy.
Nasz silnik wnioskowania ROSE jest agnostyczny pod względem formatu. Otrzymuje partię tokenów wejściowych i przesyła strumieniowo tokeny wyjściowe. W naszym stosie wnioskowania zadanie formatowania danych wejściowych w stokenizowany prompt i parsowanie wyników LLM z powrotem do ustrukturyzowanych wiadomości jest obsługiwane przez dedykowaną usługę frontendową JSON API. Zintegrowaliśmy Harmony, dodając nowy wtykowy formater/parser do tego frontendu, co pozwoliło na pozostawienie backendu bez zmian, podczas gdy frontend czysto zarządza regułami formatu czatu, wywołaniami narzędzi i przesyłaniem strumieniowym. To rozdzielenie odpowiedzialności utrzymuje architekturę modułową, umożliwiając szybkie przyjmowanie nowych formatów i funkcji odpowiedzi przy minimalnych zmianach w backendzie.
Koszt i wydajność
Biorąc pod uwagę stosunkowo niewielki rozmiar modeli GPT-OSS, ograniczamy wdrożenie repliki do pojedynczego węzła, aby uniknąć narzutu komunikacji między węzłami. Nasza implementacja obsługuje elastyczne kombinacje równoległości ekspertów (EP), równoległości danych (DP) i równoległości tensorowej (TP). Aby określić optymalną konfigurację wdrożenia, przeprowadziliśmy testy porównawcze wszystkich kombinacji strategii sharding dla rozmiarów partii od 1 do 128. Dzięki Prefill-Decode Disaggregation możemy oddzielnie omówić kompromis między wydajnością a kosztem dla prefill i decode.
Dla szacunków kosztów przyjmujemy nominalną stawkę rynkową H200 w wysokości 3,00 USD/godzinę (uwaga: nie jest to nasza rzeczywista cena za GPU).
Odkryliśmy, że ustawienie batch_size=1 i DP=1 zapewnia najlepszą wydajność prefill. Różne konfiguracje TP zapewniają następnie zróżnicowane kompromisy między opóźnieniem pierwszego tokena a kosztem. Poniższe tabele przedstawiają opóźnienie prefill i koszt na milion tokenów wejściowych dla różnych konfiguracji i długości danych wejściowych.
GPT-OSS 120B:
Długość wejścia | 8192 | 32768 | 65536 | 128000 |
EP1 DP1 TP1 | 0.364s, $0.037 | 2.421s, $0.062 | 7.375s, $0.094 | 23.902s, $0.156 |
EP2 DP1 TP2 | 0.190s, $0.039 | 1.245s, $0.063 | 3.786s, $0.096 | 12.116s, $0.158 |
EP4 DP1 TP4 | 0.165s, $0.067 | 0.900s, $0.092 | 2.449s, $0.125 | 7.165s, $0.187 |
EP8 DP1 TP8 | 0.179s, $0.145 | 0.839s, $0.171 | 2.003s, $0.204 | 5.119s, $0.267 |
Opóźnienie prefill i koszt na milion tokenów wejściowych dla GPT-OSS 120B według konfiguracji paralelizacji i długości wejścia.
GPT-OSS 20B:
Długość wejścia | 8192 | 32768 | 65536 | 128000 |
EP1 DP1 TP1 | 0.239s, $0.024 | 1.610s, $0.041 | 4.895s, $0.062 | 16.037s, $0.104 |
EP2 DP1 TP2 | 0.127s, $0.026 | 0.833s, $0.042 | 2.522s, $0.064 | 8.143s, $0.106 |
EP4 DP1 TP4 | 0.108s, $0.044 | 0.593s, $0.060 | 1.616s, $0.082 | 4.766s, $0.124 |
EP8 DP1 TP8 | 0.116s, $0.094 | 0.547s, $0.111 | 1.310s, $0.133 | 3.365s, $0.175 |
Opóźnienie prefill i koszt na milion tokenów wejściowych dla GPT-OSS 20B według konfiguracji paralelizacji i długości wejścia.
Dla dekodowania (decode) odkryliśmy, że EP4 DP1 TP4 jest najlepszą konfiguracją dla większości przypadków. Cztery poniższe wykresy ilustrują kompromis między szybkością dekodowania a kosztem miliona wyjściowych tokenów dla modeli 120B i 20B przy długościach sekwencji 8192 i 32768.
Rozważaliśmy również użycie mniejszego modelu 20B jako modelu roboczego do dekodowania spekulatywnego dla większego modelu 120B. Jednak ponieważ liczba aktywnych parametrów jest podobna (3B vs. 5B), wynikowe przyspieszenie było marginalne.
Referencje
Zdezintegrowany Prefill i Decode
Przyspieszenie Sonar poprzez spekulację
Niższe opóźnienia i wyższa przepustowość dzięki wielowęzłowemu wdrożeniu DeepSeek
Wydajna i przenośna komunikacja Mixture-of-Experts
FlashInfer: Wydajny i konfigurowalny silnik uwagi do serwowania wnioskowania LLM
Wydajne strumieniowe modele językowe z Attention Sinks
Raport techniczny DeepSeek-V3
Format odpowiedzi OpenAI Harmony | OpenAI Cookbook
Cytat
@online{perplexity2025GptOssOnDay0,
author = {{Perplexity} and Nandor Licker and Lequn Chen and Vladimir Zaytsev and Kevin Hu and Lauren Kolm and Jesse Dwyer},
title = {{GPT-OSS on day 0}},
date = {2025-08-08},
url = {https://www.perplexity.ai/hub/blog/gpt-oss-on-day-0}
}