Concepts / Tokenizers and Encoding Text

Tokenizers and Encoding Text

A Response header and the output text are added next, and the two parts are combined into a single full text.

  • Programming

Predicting What the Dataset Holds

Fine-tuning a model on instructions starts with a practical question: how do raw records become something a training loop can request one at a time? This article answers that question using one class, InstructionDataset. Instead of reading the class from top to bottom, you will first predict what it should do, then trace the code, then explain the mechanism behind the result.

What do you think happens?

An InstructionDataset is built from a list of raw records. Before looking at the code, what do you think the length method reports, and which list does it read from?

Reveal answer
Answer:

The length method returns the length of the original data list, so the count matches the number of records supplied when the dataset was created.

The class stores finished token sequences, but its length request is answered from the original data list. The constructor's work does not change how many records were supplied.

Compare your guess with the outcome. If you guessed that the dataset answers requests from stored sequences, you already have the central idea of the class: it stores finished token sequences, and it answers length and index requests from those stored sequences. The sections that follow explain how the constructor creates them and why that work is done ahead of time.

The Three Methods and Their Jobs

InstructionDataset inherits from torch.utils.data.Dataset and provides three methods with a clear division of labor. The constructor, __init__, receives the raw data and a tokenizer and performs the preparation work. The __getitem__ method takes an index and returns one stored item. The __len__ method reports how many items the dataset holds.

Notice that the constructor does the heavy lifting and the other two methods are light. __len__ and __getitem__ do not rebuild anything. They read from data that already exists. This is the key to understanding why the class behaves as it does. Once you can name which list each method reads from, you can predict most of its output.

Key point

The count comes from the original data list, while each returned item comes from the encoded list that was filled during construction.

What the Constructor Builds, Step by Step

The constructor stores the raw data, creates an empty list called encoded_texts, and then loops over every entry. For each entry, it builds the instruction portion with a helper named format_input. The helper is called in the listing but defined separately from the class code shown there. The constructor then adds a response section that begins with a Response header followed by the entry's output text. The two parts are joined into one full text string.

Trace one entry through this loop. The raw record goes into format_input, which produces the instruction portion. A Response header and the output text are added next, and the two parts are combined into a single full text. That full text is still just a string at this point. The last step of the loop is the one that matters most for what follows.

Raw recordone entry from the datalistformat_inputbuilds the instructionportionFull textinstruction plus Responseheader and outputtokenizer.encodeconverts the text to tokensencoded_textsfinished token sequencestoredjoined with responseone call per entryappended
How does raw text move from strings to token IDs once at construction? Each entry passes through these stages inside the loop, and the finished sequence is appended to encoded_texts.

The last step of each loop iteration is the important one. The full text is passed to tokenizer.encode, and the resulting token sequence is appended to self.encoded_texts. The text is therefore converted to tokens during construction, before any training begins. The source calls this conversion pretokenization: tokenizing texts using a tokenizer before training.

Why Pretokenizing Speeds Up Training

The source states that pretokenization accelerates training. The reasoning follows from where the tokenizing work happens. Because every entry is encoded once inside the constructor, the training loop later only has to look up a finished sequence when it asks for an item. The tokenizer does not need to run again on the same text each time that example is requested.

Training coderequests itemsInstructionDatasetstores encoded_textsTokenizerused once per entry1. construct with data and tokenizer (__init__)2. encode each full text, once3. ask for the count (__len__)4. ask for item i (__getitem__)
When is each method called, in what order, and what does a call like dataset[i] return? The constructor runs once and does the encoding; later requests only read stored results.

In this lifecycle, __init__ runs once and is the only method that calls the tokenizer. __len__ and __getitem__ run later, and each request reads from what was already stored. A call like dataset[i] therefore returns a stored token sequence rather than raw text. Each repeated request is cheap because no encoding happens during it.

Padding Examples into a Batch

Training usually processes several examples at once. The source explains that collecting multiple training examples in a batch accelerates training. Batching has a consequence, though: examples in the same batch need to line up, so all inputs are padded to a similar length. The source describes this padding as a necessity for batch collection rather than an optional tidy-up.

Worked example

Padding two examples of different lengths

Example 1 has five tokens and Example 2 has three tokens. How can both fit into one rectangular batch row length?

  1. 1
    Measure each example

    Example 1 has five tokens. Example 2 has three tokens, which is two short of the longer one.

  2. 2
    Choose the common length

    The batch must line up, so both examples are brought to the length of the longer one, five positions.

  3. 3
    Fill the missing positions

    Example 2 receives two padding positions at its end, filled with the padding token <|endoftext|>.

  4. 4
    Stack the rows

    Both rows now have five positions, so they can be collected into one batch with two rows and five columns.

Result

A rectangular batch in which every row has the same length, with the shorter example ending in padding positions.

The example above uses made-up token labels to show the shape of the change. The principle comes from the source: padding brings inputs to a similar length so they can be grouped. The padding token the source specifies is <|endoftext|>, which is used in the same way as in classification fine-tuning.

Example 1, position0tok AExample 2, position 0tok FExample 1, position1tok BExample 2, position 1tok GExample 1, position2tok CExample 2, position 2tok HExample 1, position3tok DExample 2, position 3<|endoftext|>Example 1, position4tok EExample 2, position 4<|endoftext|>
If examples have different token counts, where do the padding positions go in each row, and how do the rows line up as a rectangular batch? Shorter examples fill their trailing positions with the padding token.

Mistakes Beginners Make

  • Common mistake

    Assuming __getitem__ tokenizes the text each time it is called

    What it looks like

    Expecting every dataset[i] request to run the tokenizer on the raw record.

    Why it fails

    The source states that the constructor encodes each entry once and stores the result in encoded_texts, so requests only read finished sequences.

    Do this instead

    Locate the tokenizer call in the constructor loop, and treat __getitem__ as a lookup into the stored list.

  • Common mistake

    Reading the length from the encoded list when the count is needed

    What it looks like

    Predicting the length of the dataset from the number of stored token sequences, without checking which list __len__ reads.

    Why it fails

    In this class, __len__ returns the length of the original data list, so the count matches the number of records supplied at creation.

    Do this instead

    When a count looks wrong, check which list the length method reads from before looking elsewhere.

  • Common mistake

    Forgetting that batching requires examples of a similar length

  • Common mistake

    Treating padding as an optional cosmetic step

    Why it fails

    The source describes padding as a necessity for batch collection, because examples in the same batch need to line up.

    Do this instead

    Pad each example to a similar length before collecting examples into a batch.

Tracing an Unexpected Result

Suppose a teammate reports that an InstructionDataset built from ten records returns items that look like raw instruction text when they are indexed, and the length is correct. Use what you have learned to decide where to look.

MEDIUM

Which stage is most likely to produce the observed output: text loading, tokenization in __init__, indexing in __getitem__, or batch collation? Explain your reasoning by naming the list each method reads from.

Hints
  • The length is correct, so the original data list is being read correctly.
  • The source states that __getitem__ returns a stored item from encoded_texts, which is filled by the constructor.
  • Ask whether the tokenizer.encode step in the constructor loop was executed for these entries.

Key Takeaways

  • InstructionDataset inherits from torch.utils.data.Dataset and provides __init__, __getitem__, and __len__, each with a distinct job.
  • The constructor pretokenizes each entry by building a full text and calling tokenizer.encode, storing the results in encoded_texts, so the tokenizing work is done once.
  • __len__ reads the original data list, while __getitem__ returns items from the encoded list filled during construction.
  • Examples in a batch are padded to a similar length using the <|endoftext|> padding token, because batch collection requires rows that line up.

Related concepts

More concepts starting with T