AI & AutomationAI-generated

Hoe tekst getallen wordt: de tokenizer

Een taalmodel leest geen letters. Het rekent met getallen, en vóór het eerste rekenwerk moet je tekst dus vertaald worden naar een rij getallen. Dat doet de tokenizer. Hij bepaalt hoeveel je betaalt per vraag, hoeveel tekst in het context window past en waarom een model soms struikelt over iets simpels als het tellen van letters.

Dit is het eerste deel van een nieuwe reeks, Hoe AI echt werkt. Hieronder volg je één zin van tekens tot token-id's. Alle getallen komen uit de openbare tokenizers van OpenAI. Die van Claude is niet openbaar, maar werkt volgens hetzelfde principe.

TokenizerDe kat zat op de mat.
De·kat·zat·op·de·mat.

21 tekens · voor een netwerk alleen betekenisloze symbolen

Een rij symbolen

"De kat zat op de mat." is voor jou een zin. Voor een neuraal netwerk zijn het 21 symbolen zonder betekenis. Het netwerk kan alleen vermenigvuldigen en optellen, dus elk stukje tekst moet een nummer krijgen.

De vraag is welke stukjes. Je kunt elke letter een nummer geven, of elk woord. Allebei hebben een groot nadeel.

Bytes: altijd raak, maar lang

De computer heeft al een nummering: UTF-8. Elke letter van a tot z is één byte, een getal tussen 0 en 255. Een é kost twee bytes, een euroteken drie en een emoji vier.

Met 256 mogelijke bytes kun je elke tekst in elke taal opschrijven. Maar de rijen worden lang, en een model moet dan ontdekken dat k, a en t samen een dier zijn. Dat kost rekenwerk bij elke zin opnieuw. Elk woord een eigen nummer geven werkt ook niet: dan heb je miljoenen nummers nodig, en een nieuw woord of een tikfout past nergens in.

Eerst in woorden knippen

Moderne tokenizers zitten ertussenin. Ze beginnen met bytes en voegen veelvoorkomende combinaties samen tot grotere stukken. Het algoritme heet Byte Pair Encoding (BPE). Philip Gage bedacht het in 1994 als compressiemethode, Sennrich en collega's gebruikten het in 2016 voor vertaalmodellen, en sinds GPT-2 gebruiken de meeste grote taalmodellen BPE of een variant ervan.

Eerst knipt de tokenizer de tekst grof op met een vaste regel: woorden, getallen en leestekens apart. De spatie hoort bij het woord dat erop volgt. Daardoor is " kat" midden in een zin een ander stuk dan "kat" aan het begin.

Paren tellen

We trainen een mini-tokenizer op drie zinnetjes. Binnen elk woord tellen we welke twee symbolen het vaakst naast elkaar staan. Hier is dat a gevolgd door t: acht keer, in kat, zat, mat en rat.

Dat paar krijgt een nieuw nummer. Overal waar a en t naast elkaar staan, wordt het één symbool: at.

Samenvoegen, en nog eens

Daarna tellen we opnieuw. Nu wint d+e, daarna de spatie met de. Elke ronde heet een merge, en elke merge wordt een regel in de woordenlijst van de tokenizer. Na drie merges zijn de 62 symbolen van het corpus al 43 tokens.

De volgorde van de merges is de tokenizer. Wil je later nieuwe tekst omzetten, dan pas je dezelfde regels in dezelfde volgorde toe.

Waar het stopt

We gaan door tot geen enkel paar nog twee keer voorkomt. Na elf merges zijn er 24 tokens over. Woorden die vaak voorkwamen, zoals " kat", " zat" en " de", zijn één token geworden.

Kijk naar " mat" en " zag". Die kwamen maar één keer voor en blijven in stukken staan. Dat is het hele idee: veelgebruikte woorden worden goedkoop, zeldzame woorden worden opgebouwd uit kleinere stukken. Niets is onschrijfbaar, want in het ergste geval val je terug op losse bytes.

Een echte tokenizer

GPT-4o gebruikt o200k_base, getraind op een enorme hoeveelheid tekst. De woordenlijst telt 200.019 tokens. Onze zin wordt zeven getallen: De is 1923, kat is 9015, de punt is 13. Dat rijtje gaat het model in, niets anders.

Schakel naar cl100k_base, de tokenizer van GPT-4, met half zoveel tokens. Daar is " zat" nog geen eigen woord en valt het in z en at. Een grotere woordenlijst kent meer woorden in één stuk, ook in andere talen dan het Engels.

Nederlands is duurder

Tokenizers worden getraind op een mengsel van teksten waarin Engels de overhand heeft. Engelse woorden worden daardoor vaker één token. Artikel 1 van de Universele Verklaring van de Rechten van de Mens kost in het Engels 33 tokens. De officiële Nederlandse tekst kost er in o200k 41, bijna een kwart meer. In het oudere cl100k zijn het er 53, ruim zestig procent meer.

Omdat je per token betaalt en een context window in tokens wordt gemeten, is dezelfde vraag in het Nederlands dus iets duurder en vult hij het geheugen sneller.

Rare gevolgen

Het model ziet tokens, geen letters. Midden in een zin is strawberry voor GPT-4o één token; los, zonder spatie ervoor, wordt het st, raw en berry. Vraag hoeveel r'en erin zitten, en het model moet iets tellen dat het nooit los heeft gezien. Dat is een belangrijke verklaring voor een bekende misser.

In deze tokenizers worden getallen per drie cijfers geknipt: 1234567 wordt 123, 456, 7. Rekenen over die grenzen heen is lastiger dan het lijkt. En aansprakelijkheidsverzekering wordt zes tokens, waar het Engelse insurance er één is.

Speel zelf

Dit is de echte o200k_base, dezelfde als die van GPT-4o, nu in je browser geladen. Typ een zin en zie hoe hij in stukken valt. Met de knop ids zie je de getallen die het model werkelijk krijgt.

Probeer een lang Nederlands samengesteld woord, je eigen naam, een emoji of een rij cijfers. Let ook op de spatie: "hallo" en " hallo" zijn verschillende tokens.

Wat niet in het plaatje past

Elk model heeft zijn eigen tokenizer. Tokens van GPT-4o, Claude en Llama zijn niet uitwisselbaar, en dezelfde tekst telt per model anders. Anthropic publiceert de tokenizer van Claude niet, maar heeft wel een API die telt hoeveel tokens een bericht kost.

Het getal zegt het model niets. Token 9015 is gewoon een rijnummer. Het model zoekt bij elk nummer een lange rij kommagetallen op, een embedding, en daar zit de betekenis in. Hoe dat werkt, is een volgend deel van deze reeks.

Speciale tokens. Naast tekst kent een tokenizer tokens die je nooit typt, zoals een markering voor "einde van de tekst" of de grens tussen jouw bericht en het antwoord. Zo weet het model wie er aan het woord is.

Zelf proberen

Met Python meet je zelf hoeveel tokens een tekst kost:

pip install tiktoken
python3 -c "import tiktoken; e = tiktoken.get_encoding('o200k_base'); print(len(e.encode('De kat zat op de mat.')))"

Dat geeft 7. Met e.decode([9015]) krijg je kat terug.

Tot slot

Dat Nederlands meer tokens kost, wist ik. Hoeveel het per tokenizer scheelt, had ik nooit gemeten. Van ruim zestig procent extra naar een kwart, alleen doordat de woordenlijst twee keer zo groot werd. Het model zelf heeft daar nog niets voor gedaan.

Mijn favoriet blijft strawberry. Een model dat moeiteloos een juridische brief schrijft, ziet in dat woord geen enkele losse r. Als je dat eenmaal weet, verbaast die fout je niet meer.