Modele hybrydowe: co Gated DeltaNet zastępuje w cache KV
Atencja liniowa zamienia cache KV w stan o stałym rozmiarze. Reguła delta naprawia to, że taki stan źle zapomina, a bramkowanie dodaje zanik. Dlaczego hybrydy zachowują kilka warstw z pełną atencją i co stan rekurencyjny zmienia przy ponownym użyciu prefiksu, wycofywaniu przy dekodowaniu spekulatywnym i w długich sesjach.
- deep dive
- Gated DeltaNet
- atencja liniowa
- architektura
- cache KV
Model na mojej maszynie do inferencji nie jest czystym transformerem. Większość jego warstw to Gated DeltaNet, odmiana atencji liniowej ze stanem o stałym rozmiarze, a tylko część warstw to pełna atencja z cache KV. To dzięki temu kontekst 262K w ogóle się mieści. I z tego samego powodu w moim forku jest kilka poprawek: stan rekurencyjny zachowuje się bardzo odmiennie od cache.
Zastępowany koszt
Atencja softmax trzyma każdy wcześniejszy klucz i każdą wcześniejszą wartość. Cache rośnie liniowo z kontekstem, a każdy krok dekodowania czyta go w całości. Jedna głowica o wymiarze 128 dodaje 2 × 128 = 256 wartości w cache na token, na zawsze.
Atencja liniowa: stan o stałym rozmiarze
Usuń softmax i zastosuj do zapytań i kluczy mapę cech φ (Katharopoulos i in., 2020). Atencja staje się łączna, więc sumę po wcześniejszych tokenach można trzymać jako bieżącą macierz:
S_t = S_{t−1} + v_t · φ(k_t)ᵀ stan: d_v × d_k na głowicę
o_t = S_t · φ(q_t)
Stan to macierz o stałym rozmiarze d_v × d_k na głowicę: 128 × 128 = 16 384 wartości, niezależnie od długości kontekstu. Cache jednej głowicy atencji softmax przekracza ten rozmiar po 16 384 / 256 = 64 tokenach. Przy 262K tokenów jest 4096 razy większy.
Haczyk polega na tym, że czysto addytywny stan nie potrafi zapominać. Każde powiązanie klucz–wartość jest do niego dodawane, stare powiązania zakłócają nowe, a przywoływanie pogarsza się w miarę, jak sekwencja rośnie.
Reguła delta: zapisz poprawkę, nie sumę
DeltaNet (Schlag i in., 2021; zrównoleglenie: Yang i in., 2024) aktualizuje stan na wzór regresji online. Odczytuje, co stan w tej chwili przewiduje dla k_t, a potem zapisuje tylko błąd:
S_t = S_{t−1} + β_t · (v_t − S_{t−1} k_t) · k_tᵀ
Jeśli stan już odwzorowuje k_t na v_t, nic się nie zmienia. Jeśli odwzorowuje go na coś nieaktualnego, stare powiązanie zostaje nadpisane. β_t ∈ (0, 1) to wyuczona siła zapisu per token.
Gated DeltaNet: dodaj zanik
Gated DeltaNet (Yang, Kautz i Hatamizadeh, 2025) dodaje wyuczoną bramkę zaniku α_t ∈ (0, 1), więc stan może też zapominać hurtem, gdy kontekst przechodzi dalej:
S_t = α_t · S_{t−1} · (I − β_t k_t k_tᵀ) + β_t · v_t k_tᵀ
Zanik obsługuje „ta sekcja się skończyła”; aktualizacja delta obsługuje „ten konkretny fakt się zmienił”. Razem sprawiają, że stanu o stałym rozmiarze da się używać na długich sekwencjach.
Dlaczego hybrydy zachowują część atencji
Stan o stałym rozmiarze to stratne podsumowanie. Do dokładnego wyszukiwania, takiego jak skopiowanie nazwy zmiennej zdefiniowanej 100K tokenów wcześniej, atencja softmax po pełnym cache wciąż jest lepsza. Dlatego hybrydy przeplatają oba mechanizmy: głównie warstwy Gated DeltaNet plus mniejszość warstw z pełną atencją, które trzymają cache KV. Qwen3-Next opublikował proporcję 3:1 warstw Gated DeltaNet do warstw atencji.
Dlatego cache mojego modelu 27B wychodzi na 32 768 wartości na token. Dokładają się do niego tylko warstwy atencji; warstwy DeltaNet nie dodają niczego, co rośnie.
Co stan rekurencyjny zmienia przy serwowaniu
Cache łatwo modyfikować: usuwasz wpisy i cofasz się w czasie. Stan rekurencyjny to jedna macierz, która już wszystko w siebie wchłonęła. Trzy konsekwencje:
- Ponowne użycie prefiksu wymaga migawek. Przy czystym cache ponowne użycie wspólnego prefiksu oznacza zachowanie pierwszych N wpisów. Przy stanie rekurencyjnym stanu z pozycji N nie da się odtworzyć z późniejszego. Potrzebujesz migawki zrobionej na pozycji N. Mój fork trzyma do 32 checkpointów promptu po około 149 MiB każdy. Przerobiłem ich zapis na asynchroniczny, żeby przestały blokować prefill; opisuje to notatka o forku.
- Dekodowanie spekulatywne wymaga prawdziwego wycofywania. Gdy tokeny szkicu zostają odrzucone, cache po prostu wyrzuca ich wpisy. Stan rekurencyjny już je wchłonął, więc silnik potrzebuje z powrotem stanu sprzed szkicu. Kilka moich poprawek MTP to dokładnie to: wycofywanie odrzuconych szkiców i ponowna synchronizacja oczekującego stanu, gdy zmienia się pamięć.
- Długie przebiegi obnażają numerykę. Stan aktualizowany przez setki tysięcy kroków kumuluje błąd w sposób, w jaki nie kumuluje go cache złożony z niezależnych wpisów. Jedna z moich poprawek zatrzymuje dryf stanu rekurencyjnego przy dużym kontekście, który bez niej kończył się martwą pętlą.
Prefill idzie ścieżką z podziałem na fragmenty (chunked). W obrębie fragmentu tokenów aktualizacje są liczone jako gęste mnożenia macierzy, a sekwencyjne jest tylko przejście z fragmentu do fragmentu. Dodałem połączone jądro dla tego potoku fragmentów (chunk pipeline).
Zasady, których się trzymam
- Licz tylko warstwy atencji, gdy wyznaczasz rozmiar cache hybrydy. Wzór na cache KV nadal obowiązuje;
n_warstw_atencjijest po prostu mniejsze. - Zaplanuj pamięć RAM hosta na checkpointy, jeśli serwujesz na hybrydzie sesje wieloturowe. Ponowne użycie prefiksu nie jest już darmowe.
- Testuj dekodowanie spekulatywne osobno na hybrydach. To w wycofywaniu chowają się błędy silnika.
- Sprawdzaj dokładne przywoływanie przy długim kontekście. W tym stan o stałym rozmiarze jest najsłabszy i to dlatego istnieją warstwy atencji.
Źródła: Katharopoulos i in., Transformers are RNNs (ICML 2020). Schlag i in., Linear Transformers Are Secretly Fast Weight Programmers (ICML 2021). Yang i in., Parallelizing Linear Transformers with the Delta Rule (NeurIPS 2024). Yang, Kautz i Hatamizadeh, Gated Delta Networks (ICLR 2025). Poprawki są w moim forku.