Building a Simple Tokenizer with encode and decode
The encode method converts a text string into a list of integer token IDs, and the allowed_special parameter tells it which special tokens to accept.
Running the Tokenizer Before Explaining It
Before looking at how a BPE tokenizer works inside, it helps to run one and watch what happens. The code in this article takes a sentence that contains a special marker, turns it into a list of integers, and then turns that list back into text. Every step is visible in the code and its printed output, so you can judge the behavior yourself before reading the explanation.
The tokenizer used here is the BPE tokenizer from the previous step. Its usage is similar to the SimpleTokenizerV2 class you implemented earlier, and it exposes the same style of encode method. This article is about two calls. The encode call goes from text to token IDs, and the decode call goes from token IDs back to text.
What do you think happens?
The input text is built from two adjacent string literals, "In the sunlit terraces" and "of someunknownPlace.". Python joins adjacent literals with no separator. Between 'terraces' and 'of', does the original text contain a space?
Reveal answer
No space. The original text contains the characters terracesof with nothing between them.
Python joins adjacent string literals without inserting anything between them, so the input is terracesof. The printed output in the example reads terraces of with a space. The example does not settle whether a space was typeset in the sample output that the code does not contain, or whether the round trip changed the text. Keep this question in mind; it is the central check of this article.
Reading the Encoded Output
The example runs the encode call on a sentence containing <|endoftext|>, passes the resulting list to decode, stores the returned text in a variable named strings, and prints it. The sequence of operations is shown in the code below. The variable names are the ones used in the example, and the sample text is a generated variant that includes the special marker so you can follow what happens to it.
- The two adjacent string literals are joined with no separator, the same way the source example joins its literals.
- The
allowed_specialargument is passed explicitly in the encode call. - The integer list produced by encode is passed directly to decode.
Read the first printed line as a list of integers, not as words. Each integer is a token ID the tokenizer produced for a piece of the input. Two things stand out. The list is not the same length as the sentence in characters, because a token can cover more or fewer characters than one. Also, the value sitting where the special marker appeared is 50256 in the source output. That single integer stands for the whole marker, which is the key idea behind the special-token behavior explained later.
Encode: From Text to Token IDs
The encode method converts a text string into a list of integer token IDs. Its allowed_special parameter tells the tokenizer which special tokens it is permitted to recognize in the input.
Encoding works on the input text and produces one integer per token. Ordinary text is converted into ordinary token IDs. A special marker such as <|endoftext|> is a different case, because it is not meant to be read as plain characters. Whether the tokenizer treats it as a special token depends on the allowed_special argument. The diagram below shows the decision that encode makes when the input contains the marker.
Following the diagram, the marker takes the left-hand branch only when the allowed_special argument names it. In that case the marker becomes one special ID, and the output list contains the value 50256 at that position. If the marker is not allowed, the text is handled as ordinary characters and split into ordinary text tokens. The source states this directly: the special marker is handled as a special token rather than being ignored as plain characters when the argument is passed.
The same contrast can be seen by comparing the two token sequences for the marker. The diagram below sets them side by side.
Decode: From Token IDs Back to Text
The decode method runs the process in reverse. It takes a list of token IDs and returns the text they represent. Encode takes text to integers, and decode takes integers to text. Together they form a round trip. The sample code passes the integers produced by encode directly to decode, and the returned text is stored in strings and printed.
The diagram shows the path that a check must follow. The returned text is compared with the original input. Any mismatch appears at the end of the path, but its cause could be at any stage: the input construction, the encode step, or the decode step. This is why the next section treats the comparison as a careful, character-level task rather than a glance at the printed output.
Checking the Round Trip
The most useful check is whether decode(encode(text)) reproduces the original text exactly. Return to the adjacent-literal question from the opening. The input contains terracesof with no space, because Python joins the two literals directly. The printed output in the example shows terraces of with a space. Two explanations are possible. Either a space was typeset in the sample output that the code does not contain, or the round trip changed the text. The example does not settle which, so do not assume either one.
Locating where a round trip differs
Given an input string built from adjacent literals, check whether decode(encode(text)) returns the same string, and if not, find the first position where they differ.
- 1Build the input exactly as the code does
Join the literals in the order written. Python adds nothing between them, so the input is a single string with no space at the join.
- 2Encode, then decode
Pass the input to encode with
allowed_specialset as required, store the list, then pass that list to decode and store the returned text. - 3Compare character by character
Do not judge by eye from the printed output. Compare the two strings position by position, because a single missing or added space is easy to overlook when reading a line of text.
- 4Decide which stage changed the text
If the strings match, the round trip is exact. If they differ, first inspect how the input was built. Only after ruling out the input should you suspect the tokenizer.
The round trip is judged by the exact string comparison, not by how the printed output looks. In the terraces example, the input text itself contains no space at the join, so a space in the printed output must be checked before it is explained.
Mistakes Beginners Make with Round Trips
Common mistake Leaving out allowed_special when the input contains a special marker
What it looks liketokenizer.encode(text) on a string that contains <|endoftext|>Why it failsThe marker is then not accepted as a special token and is broken into ordinary text tokens, so the list no longer carries the special ID at that position.
Do this insteadPass the marker explicitly, for example tokenizer.encode(text, allowed_special={"<|endoftext|>"}).
Common mistake Judging the round trip from the printed text alone
What it looks likeReading terraces of in the output and assuming the input had the same space
Why it failsThe input built from adjacent literals may contain no space at that point, so a visual check can hide a real difference.
Do this insteadCompare the original and returned strings character by character.
Common mistake Reading the token ID list as words
What it looks likeExpecting the integers to look like the letters of the sentence
Why it failsEach integer is a lookup key for a token, and the list length does not match the character count.
Do this insteadUse decode to recover text from the IDs, and do not read the integers directly.
Practice the Round Trip
Write a short script that builds a string from two adjacent literals, encodes it with allowed_special containing <|endoftext|>, decodes the result, and checks whether the returned string equals the original. If they differ, print the first index where the characters disagree, and state whether the difference comes from how the input was built or from the round trip.
Hints
- Build the input first and print it with repr so hidden or missing spaces are visible.
- Compare the two strings with a loop over indices, not by eye.
- Run the same check once with and once without
allowed_specialfor a string that contains the marker, and compare the lists.
Key Takeaways
- The encode method converts a text string into a list of integer token IDs, and decode takes those IDs back to text.
- The
allowed_specialparameter tells encode which special tokens it may accept. Passed as a set containing <|endoftext|>, the marker becomes a single special ID, such as 50256 in the source output. - Without
allowed_special, the marker is not treated as a special token and is split into ordinary text tokens, producing a different list. - A round trip is exact only if
decode(encode(text))reproduces the original text character by character. When it differs, inspect how the input was built before assuming the tokenizer is at fault.