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.
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
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.
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.
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.
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.
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?
- 1Measure each example
Example 1 has five tokens. Example 2 has three tokens, which is two short of the longer one.
- 2Choose the common length
The batch must line up, so both examples are brought to the length of the longer one, five positions.
- 3Fill the missing positions
Example 2 receives two padding positions at its end, filled with the padding token <|endoftext|>.
- 4Stack the rows
Both rows now have five positions, so they can be collected into one batch with two rows and five columns.
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.
Mistakes Beginners Make
Common mistake Assuming __getitem__ tokenizes the text each time it is called
What it looks likeExpecting every dataset[i] request to run the tokenizer on the raw record.
Why it failsThe source states that the constructor encodes each entry once and stores the result in
encoded_texts, so requests only read finished sequences.Do this insteadLocate 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 likePredicting the length of the dataset from the number of stored token sequences, without checking which list __len__ reads.
Why it failsIn this class,
__len__returns the length of the original data list, so the count matches the number of records supplied at creation.Do this insteadWhen 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 failsThe source describes padding as a necessity for batch collection, because examples in the same batch need to line up.
Do this insteadPad 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.
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 fromencoded_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.