Trefon.
← Wszystkie notatki 10 min

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.

Jacek Trefon
  • 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:

  1. 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.
  2. 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ęć.
  3. 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

  1. Licz tylko warstwy atencji, gdy wyznaczasz rozmiar cache hybrydy. Wzór na cache KV nadal obowiązuje; n_warstw_atencji jest po prostu mniejsze.
  2. Zaplanuj pamięć RAM hosta na checkpointy, jeśli serwujesz na hybrydzie sesje wieloturowe. Ponowne użycie prefiksu nie jest już darmowe.
  3. Testuj dekodowanie spekulatywne osobno na hybrydach. To w wycofywaniu chowają się błędy silnika.
  4. 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.

Masz podobny problem?

Opisz swój problem

30-minutowa rozmowa techniczna z inżynierem, który to buduje. Bez działu sprzedaży, bez slajdów.