Dlaczego LLM-y szaleją na punkcie emoji konika morskiego?

Źródło: vgel.me

Dlaczego LLM-y szaleją na punkcie emoji konika morskiego?

To zredagowana i rozszerzona wersja wpisu z Twittera, pierwotnie będącego odpowiedzią dla @arm1st1ce – oryginał znajdziesz tutaj: https://x.com/voooooogel/status/1964465679647887838

Czy istnieje emoji konika morskiego? Zapytajmy GPT-5 Instant:

Co jest? Zapytajmy w takim razie Claude Sonnet 4.5:

Co tu się dzieje? Może Gemini 2.5 Pro poradzi sobie lepiej?

No dobrze, coś tu ewidentnie jest na rzeczy. Sprawdźmy dlaczego.

LLM-y naprawdę wierzą, że emoji konika morskiego istniejeodnośnik do sekcji: LLM-y naprawdę wierzą, że emoji konika morskiego istnieje

Oto odpowiedzi, jakie dostaniemy, pytając kilka modeli sto razy, czy emoji konika morskiego istnieje – tak czy nie:

Is there a seahorse emoji, yes or no? Respond with one word, no punctuation.

Jak widać, popularne modele językowe są bardzo pewne, że emoji konika morskiego istnieje. I nie są w tej pewności osamotnione – oto wątek z Reddita z setkami komentarzy od ludzi, którzy wyraźnie pamiętają, że emoji konika morskiego kiedyś było:

Takich rzeczy jest mnóstwo – wpiszcie w Google „seahorse emoji", a znajdziecie TikToki, filmy na YouTubie, a nawet (już martwe) memecoiny zbudowane wokół rzekomego zniknięcia emoji konika morskiego, które – wszyscy są tego niemal pewni – kiedyś istniało. Tyle że oczywiście nigdy nie istniało.

Może LLM-y wierzą w emoji konika morskiego, bo wierzy w nie tylu ludzi w danych treningowych. A może to przekonanie zbieżne – skoro w Unicode jest tyle innych wodnych zwierząt, to i ludzie, i LLM-y mają prawo założyć (a wręcz wygeneralizować), że tak urocze stworzenie też się tam znajdzie. Emoji konika morskiego zostało nawet w pewnym momencie oficjalnie zgłoszone, ale w 2018 roku propozycję odrzucono.

Niezależnie od pierwotnej przyczyny, wiele LLM-ów zaczyna każde nowe okno kontekstu z błędnym, utajonym przekonaniem, że emoji konika morskiego istnieje. Ale czemu prowadzi to do tak dziwnych zachowań? Sama kiedyś wierzyłam, że taki emoji istnieje – ale gdybym spróbowała wysłać go znajomemu, po prostu poszukałabym go na klawiaturze i zorientowała się, że go tam nie ma. Nie wysłałabym niewłaściwego emoji, żeby potem wpaść w spiralę emoji-spamu. Co więc dzieje się we wnętrzu LLM-a, że zachowuje się właśnie tak?

Patrzymy przez logit lensodnośnik do sekcji: Patrzymy przez logit lens

Przyjrzyjmy się temu za pomocą ulubionego, niedocenianego narzędzia interpretowalności – logit lens!

Użyjemy tego prefiksu promptu – szablonu czatu z domyślnym promptem systemowym llama-3.3-70b, pytaniem o emoji konika morskiego i częściową odpowiedzią modelu, uciętą tuż przed samym emoji:

<|begin_of_text|><|begin_of_text|><|start_header_id|>system<|end_header_id>
Cutting Knowledge Date: December 2023
Today Date: 26 Jul 2024

<|eot_id|><|start_header_id|>user<|end_header_id|>

Is there a seahorse emoji?<|eot_id|><|start_header_id|>assistant<|end_header_id|>

Yes, there is a seahorse emoji:

Możemy wziąć z modelu

lm_head
– zwykle stosowaną tylko do wyjścia ostatniej warstwy – i przyłożyć ją do każdej warstwy, uzyskując pośrednie predykcje tokenów. W ten sposób powstaje poniższa tabela: dla co czwartej warstwy pokazuje najbardziej prawdopodobny token na trzech kolejnych pozycjach po prefiksie (tokeny 0, 1 i 2) oraz pięć najbardziej prawdopodobnych predykcji dla pierwszej pozycji (token 0, topk 5):
warstwatokenytokenytoken 0
012scalone(topk 5)
083244'ĠBail'15591'ĠHarr'5309'Ġvert'Bail Harr vert['ĠBail', 'ĠPeanut', 'ĠãĢ', 'orr', 'ĠâĢĭâĢĭ']
4111484'emez'26140'abi'25727'avery'emezabiavery['emez', 'Ġunm', 'ĠOswald', 'Ġrem', 'rix']
8122029'chyb'44465'ĠCaps'15610'iller'chyb Capsiller['chyb', 'ĠSund', 'ترÛĮ', 'resse', 'Ġsod']
121131'...'48952'ĠCliff'51965'ĠJackie'... Cliff Jackie['...', 'ages', 'dump', 'qing', 'Ġexp']
161131'...'12676'365'31447'ĠAld'...365 Ald['...', '...Ċ', 'Ġindeed', 'Ġboth', 'ĠYes']
201131'...'109596'éļĨ'51965'ĠJackie'...隆 Jackie['...', '...Ċ', 'Z', 'Ġboth', 'ĠHust']
2412'-'31643'ï¸ı'287'ing'-️ing['-', '...', 'â̦', '...Ċ', 'em']
281131'...'96154'ĠGaut'51965'ĠJackie'... Gaut Jackie['...', '-', '...Ċ', '-Ċ', 'Ġ']
321131'...'96154'ĠGaut'6892'Ġing'... Gaut ing['...', 'â̦', '...Ċ', 'O', 'zer']
361131'...'12'-'88'y'...-y['...', 'â̦', '...Ċ', 'Ġ', 'u']
401131'...'31643'ï¸ı'88'y'...️y['...', 'u', 'â̦', 'Âł', '...Ċ']
4480435'ĠScor'15580'Ġhorse'15580'Ġhorse'Scor horse horse['ĠScor', 'u', 'ĠPan', 'in', 'Ġhttps']
4815580'Ġhorse'15580'Ġhorse'15580'Ġhorse'horse horse horse['Ġhorse', 'Âł', 'ĠPan', 'ĠHomes', 'ĠHorse']
529581'Ġsea'15580'Ġhorse'15580'Ġhorse'sea horse horse['Ġsea', 'Ġhorse', 'ĠHorse', 'ĠSea', 'âĢij']
569581'Ġsea'43269'ĠSeah'15580'Ġhorse'sea Seah horse['Ġsea', 'ĠSea', 'ĠSeah', 'Ġhippoc', 'Ġhorse']
6015580'Ġhorse'15580'Ġhorse'15580'Ġhorse'horse horse horse['Ġhorse', 'Ġsea', 'ĠSeah', 'Ġse', 'horse']
6415580'Ġhorse'15580'Ġhorse'15580'Ġhorse'horse horse horse['Ġhorse', 'Ġse', 'ĠHorse', 'horse', 'Ġhors']
6860775'horse'238'IJ'15580'Ġhorse'horse� horse['horse', 'Ġse', 'Ġhorse', 'Ġhippoc', 'ĠSeah']
72513'Ġse'238'IJ'513'Ġse'se� se['Ġse', 'Ġhippoc', 'horse', 'ĠðŁ', 'Ġhorse']
76513'Ġse'238'IJ'513'Ġse'se� se['Ġse', 'Ġhippoc', 'hip', 'Ġhorse', 'ĠHipp']
8011410'ĠðŁ'238'IJ'254'ł'🐠['ĠðŁ', 'ðŁ', 'ĠðŁĴ', 'Ġ', 'ĠðŁij']

Na tym właśnie polega logit lens: bierzemy z modelu

lm_head
i używamy jej do wyliczenia logitów (prawdopodobieństw tokenów), żeby zajrzeć w jego stany wewnętrzne. Uwaga: tokeny i prawdopodobieństwa uzyskane tutaj przez logit lens nie są równoważne pełnym stanom wewnętrznym modelu! Do tego potrzebowalibyśmy bardziej złożonej techniki, jak representation reading czy rzadkie autoenkodery. To tylko soczewka nałożona na ten stan – pokazuje, jaki token wyszedłby na wyjściu, gdyby dana warstwa była ostatnią. Mimo tego ograniczenia logit lens wciąż jest użyteczna. Stany wczesnych warstw bywają przez nią trudne do interpretacji, ale posuwając się w górę stosu, widzimy, jak model iteracyjnie doszlifowuje te stany w stronę ostatecznej predykcji – emoji ryby.

(Czemu niescalone tokeny wyglądają jak bełkot w stylu 'ĠðŁ', 'IJ', 'ł'? To dziwactwo tokenizera – te tokeny kodują bajty UTF-8 emoji ryby. Dla tego wpisu nie ma to znaczenia, ale jeśli jesteście ciekawi, poproście Claude'a albo swój ulubiony LLM o wyjaśnienie tego akapitu i tej linijki kodu:

bytes([bpe_byte_decoder[c] for c in 'ĠðŁIJł']).decode('utf-8') == ' 🐠'
)

Spójrzcie jednak, co dzieje się w środkowych warstwach – to ani dziwactwa wczesnych warstw, ani bajty emoji z finalnej predykcji! Zamiast tego dostajemy słowa związane z użytecznymi pojęciami, konkretnie z pojęciem konika morskiego. Na przykład w warstwie 52 mamy „sea horse horse" – trzy pozycje rezydualne z rzędu kodujące pojęcie „konika morskiego". Dalej, w top-k dla pierwszej pozycji, pojawia się mieszanka „sea", „horse" i prefiksu sekwencji bajtów emoji, „ĠðŁ".

O czym więc myśli model? O „koniku morskim + emoji"! Próbuje zbudować rezydualną reprezentację konika morskiego połączonego z emoji. Po co miałby budować taką kombinację? Cóż, przyjrzyjmy się, jak tak naprawdę działa

lm_head
.

odnośnik do sekcji: lm_head
lm_head

W modelu językowym

lm_head
to ogromna macierz wektorów o rozmiarze rezyduum, powiązanych z identyfikatorami tokenów – po jednym na każdy token słownika (~300 000). Gdy trafia do niej rezyduum – czy to po normalnym przepłynięciu przez cały model, czy wcześniej, bo ktoś patrzy przez logit lens na wcześniejszą warstwę –
lm_head
porównuje wejściowe rezyduum z każdym wektorem w tej wielkiej macierzy i (we współpracy z samplerem) wybiera identyfikator tokenu powiązany z tym wektorem, który jest najbardziej podobny do wejściowego rezyduum.

(Bardziej technicznie:

lm_head
to warstwa liniowa bez biasu, więc
x @ w.T
liczy iloczyny skalarne z każdym wektorem unembeddingu, dając surowe wyniki. Potem standardowy log_softmax i argmax albo próbkowanie z temperaturą.

To znaczy, że jeśli model chce wypisać słowo „hello" – na przykład w odpowiedzi na przyjazne powitanie użytkownika – musi zbudować rezyduum możliwie podobne do wektora tokenu „hello", które

lm_head
zamieni potem na identyfikator tego tokenu. I dzięki logit lens widzimy, że dokładnie tak się dzieje w odpowiedzi na „Hello :-)":
warstwatokenytokenytoken 0
012scalone(topk 5)
00'!'0'!'40952'opa'!!opa['"', '!', '#', '%', '$']
8121495'ÅĻiv'16'1'73078'iae'řiv1iae['ÅĻiv', '-', '(', '.', ',']
1634935'Ġconsect'7341'arks'13118'Ġindeed'consectarks indeed['Ġobscure', 'Ġconsect', 'äºķ', 'ĠпÑĢоÑĦеÑģÑģионалÑĮ', 'Îŀ']
2467846'<['24748'Ġhello'15960'Ġhi'<[ hello hi['<[', 'arks', 'outh', 'ĠHam', 'la']
3215825'-back'2312'ln'14451'UBL'-backlnUBL['ÂŃi', '-back', 'Ġquestion', 'ln', 'ant']
4015648'Ġsmile'14262'Welcome'1203'Ġback'smileWelcome back['Ġsmile', 'ĠÑĥлÑĭб', 'Ġsmiled', 'ĠSmile', 'etwork']
4815648'Ġsmile'21694'ĠHi'1203'Ġback'smile Hi back['Ġsmile', 'Ġsmiled', 'ĠHello', 'Ġsmiling', 'Ġhello']
5622691'ĠHello'15960'Ġhi'1203'Ġback'Hello hi back['ĠHello', 'Ġhi', 'Ġsmile', 'Ġhello', 'Hello']
644773'-sm'24748'Ġhello'1203'Ġback'-sm hello back['-sm', 'ĠHello', 'ĠSm', 'sm', 'Hello']
7222691'ĠHello'22691'ĠHello'1203'Ġback'Hello Hello back['ĠHello', 'Ġhello', 'Hello', 'ĠHEL', 'Ġhel']
80271'ĊĊ'9906'Hello'0'!'

Hello!

['ĊĊ', 'ĊĊĊ', '<|end_of_text|>', 'ĊĊĊĊ', '"ĊĊ']

('Ċ' to kolejne dziwactwo tokenizera – oznacza znak nowej linii. 'Ġ' analogicznie oznacza spację.)

Analogicznie: jeśli model chce wypisać emoji konika morskiego, musi zbudować rezyduum podobne do wektora tokenu (czy tokenów) wyjściowych tego emoji – który w teorii mógłby mieć dowolną wartość, ale w praktyce jest to „konik morski + emoji", w stylu word2vec. Możemy zobaczyć to w akcji na prawdziwym emoji – emoji ryby:

<|begin_of_text|><|begin_of_text|><|start_header_id|>system<|end_header_id|>

Cutting Knowledge Date: December 2023
Today Date: 26 Jul 2024

<|eot_id|><|start_header_id|>user<|end_header_id|>

Is there a fish emoji?<|eot_id|><|start_header_id|>assistant<|end_header_id|>

Yes, there is a fish emoji:
warstwatokenytokenytoken 0
012scalone(topk 5)
083244'ĠBail'15591'ĠHarr'5309'Ġvert'Bail Harr vert['ĠBail', 'ĠPeanut', 'ĠãĢ', 'orr', 'ĠâĢĭâĢĭ']
8122029'chyb'44465'ĠCaps'15610'iller'chyb Capsiller['chyb', '...', 'ترÛĮ', 'ĠSund', 'resse']
161131'...'12676'365'65615'ĠSole'...365 Sole['...', '...Ċ', 'Ġboth', 'Ġindeed', 'ĠYes']
2412'-'31643'ï¸ı'51965'ĠJackie'-️ Jackie['-', '...', 'â̦', 'em', '...Ċ']
321131'...'96154'ĠGaut'88'y'... Gauty['...', 'â̦', '...Ċ', 'O', 'u']
40220'Ġ'6"'"7795'Ġfish''fish['Ġ', '...', 'â̦', 'Âł', 'u']
487795'Ġfish'7795'Ġfish'7795'Ġfish'fish fish fish['Ġfish', 'ĠFish', 'ĠBerk', 'â̦', 'Âł']
567795'Ġfish'7795'Ġfish'7795'Ġfish'fish fish fish['Ġfish', 'ĠFish', 'fish', 'Fish', 'é±¼']
647795'Ġfish'238'IJ'7795'Ġfish'fish� fish['Ġfish', 'ĠFish', 'ĠPis', 'Fish', 'ĠÙħاÙĩ']
727795'Ġfish'238'IJ'253'Ł'fish��['Ġfish', 'ĠFish', 'ĠðŁ', 'Ġ', 'ÂŁ']
8011410'ĠðŁ'238'IJ'253'Ł'🐟['ĠðŁ', 'ðŁ', 'Ġ', 'ĠĊĊ', 'ĠâĻ']

Tutaj wszystko działa idealnie. Model buduje rezyduum „ryba + emoji" – spójrzcie na topk warstwy 72, gdzie mamy zarówno „fish", jak i prefiks bajtów emoji „ĠðŁ" – czyli rezyduum jest w tym momencie podobne jednocześnie do „ryby" i do „emoji", dokładnie tak, jak byśmy się spodziewali. A gdy ten wektor po ostatniej warstwie trafia do

lm_head
, widzimy 🐟 – dokładnie tak, jak model się spodziewał.

Ale w odróżnieniu od 🐟, emoji konika morskiego nie istnieje. Model próbuje zbudować wektor „konik morski + emoji" dokładnie tak, jak zrobiłby to dla prawdziwego emoji – i w warstwie 72 dostajemy nawet konstrukcję bardzo podobną do tej z emoji ryby: „ se", „horse" i prefiks bajtów emoji:

warstwatokenytokenytoken 0
012scalone(topk 5)
72513'Ġse'238'IJ'513'Ġse'se� se['Ġse', 'Ġhippoc', 'horse', 'ĠðŁ', 'Ġhorse']

Niestety, nie istnieje żadna kontynuacja ĠðŁ odpowiadająca konikowi morskiemu, więc rachunek podobieństwa w

lm_head
osiąga maksimum na bajtach emoji związanych z koniem albo morskimi zwierzętami – i próbkowane jest emoji, którego nikt nie zamawiał.

A takie próbkowanie to dla modelu cenna informacja! Widać to np. w poniższym przykładzie z Claude 4.5 Sonnet: gdy tokeny autoregresyjnie doklejają się do kontekstu, model potrafi rozpoznać, że nie tworzą zamierzonego emoji konika morskiego. Wcześniejsze, rozmyte pojęcie „konik morski + emoji" zostało przez

lm_head
„przyciągnięte" do emoji, które naprawdę istnieje – jak rybka tropikalna czy koń.

Co dalej – to już zależy od modelu. Niektóre, jak 4.5 Sonnet, próbują jeszcze raz i w końcu wyciągają wnioski z dowodów, zmieniając w połowie odpowiedzi kurs na stwierdzenie, że emoji konika morskiego nie istnieje. Inne, jak gpt-5-chat, kręcą się w spirali dłużej i czasem nigdy z niej nie wychodzą. Jeszcze inne błogo ignorują to, że emoji jest błędne, a niektóre poprawiają się natychmiast, po zobaczeniu jednej jedynej nietrafionej próbki.

Ale dopóki model nie dostanie z

lm_head
błędnego tokenu wyjściowego, po prostu nie wie, że jego początkowe przekonanie o istnieniu emoji konika morskiego było mylne. Może jedynie zakładać, że „konik morski + emoji" wyprodukuje tokeny, o które mu chodzi.

Trochę spekulacjiodnośnik do sekcji: Trochę spekulacji

Spekulując dalej: zastanawiam się, czy ten problem nie tłumaczy części korzyści, jakie LLM-om daje uczenie ze wzmocnieniem – dostarcza ono modelowi informacji o jego własnej

lm_head
, do których inaczej trudno mu się dobrać, bo siedzi ona na samym końcu stosu warstw.

(Pamiętajcie, że modele bazowe nie są trenowane na własnych wyjściach ani rolloutach. To dzieje się dopiero w RL.)

Kododnośnik do sekcji: Kod

Jeśli chcecie sprawdzić to na własną rękę, startowy skrypt znajdziecie na GitHubie: https://gist.github.com/vgel/025ad6af9ac7f3bc194966b03ea68606