Naiwne wykonanie grafu obliczeniowego wygląda tak: przeczytaj dane z pamięci, wykonaj operację, zapisz wynik do pamięci. Przeczytaj ten wynik z powrotem, wykonaj następną operację, zapisz. I tak kilkadziesiąt razy na jedną warstwę modelu.
To magazynier, który po każdej pojedynczej czynności odwozi paletę na regał, żeby zaraz po nią wrócić. Sama czynność trwa sekundę, przejazd dwie minuty.
Czym jest fuzja operatorów
Fuzja operatorów to optymalizacja, w której runtime rozpoznaje sekwencję sąsiadujących operacji grafu i wykonuje je za jednym podejściem, trzymając wyniki pośrednie w szybkiej pamięci przy rdzeniach zamiast odsyłać je do pamięci głównej. Wynik liczbowy jest identyczny co do bitu. Zmienia się wyłącznie liczba odczytów i zapisów.
Dlaczego to daje tak duży zysk
Ponieważ wąskim gardłem przy wykonywaniu modelu jest przepustowość pamięci, a nie moc obliczeniowa. Każdy zaoszczędzony przejazd między pamięcią a rdzeniami przekłada się wprost na czas. Fuzja nie skraca obliczeń — skraca czekanie.
Właśnie stąd biorą się różnice rzędu kilkunastu czy dwudziestu razy między implementacjami wykonującymi ten sam graf. Jedna jeździ na regał po każdej palecie, druga nie.
Najbardziej znany przykład
Przepisany mechanizm uwagi, znany jako FlashAttention, jest podręcznikowym przypadkiem fuzji. Liczy dokładnie tę samą matematykę co klasyczna implementacja, ale w innej kolejności — tak, żeby nigdy nie tworzyć w pamięci głównej pełnej macierzy pośredniej porównującej każdą pozycję z każdą.
Przy krótkim kontekście różnica jest umiarkowana. Przy długim staje się decydująca, bo to właśnie ta macierz rośnie kwadratowo z długością sekwencji.
Gdzie to widać w praktyce
Nigdzie — i to jest jej zaleta. Fuzja jest optymalizacją całkowicie przezroczystą: nie zmienia odpowiedzi, nie ma parametrów do ustawienia, nie wymaga zmian w modelu. Widać ją wyłącznie w benchmarku i na rachunku.