Data Loaders and Batching
Dataset choice is therefore a matter of goal and constraints.
Why Start With a Small Public Domain Text
Before a model can be trained, its data has to exist in a form the training code can use. This article picks up at that step. The goal is to prepare a training dataset and a validation dataset, and then to compute the cross entropy loss on both. Those two losses are the central quantities of model training, so the data pipeline has to be correct before any loss value means anything.
The running text is 'The Verdict', a short story by Edith Wharton. It was already used in chapter 2. Choosing it solves two problems at once. Because the story is in the public domain, there is no question about usage rights. Because it is small, the code runs on a standard laptop in a matter of minutes, even without a high-end GPU.
To see how far this example is from production work, consider the 7 billion parameter Llama 2 model, which the source describes as a relatively popular openly available LLM. Training it required 184,320 GPU hours on A100 GPUs and processed 2 trillion tokens. Dataset choice is therefore a matter of goal and constraints. A small public domain text answers the questions of licensing and speed, which matters when the aim is to learn mechanics. A larger corpus is the route toward more extensive training, at a much higher computational cost.
Reading the Text and Counting Two Units
The first code step reads the whole story into a single string. The file is opened in read mode with UTF-8 encoding, and file.read() returns its full contents. The file name below is a placeholder used for illustration.
open(...)with mode "r" opens the file for reading only- encoding="utf-8" tells Python how to turn the bytes in the file into characters
file.read()returns the full contents as one string, stored intext_datalen(text_data)counts characters in the string
Once the text is in memory, the code measures it in two different units. The number of characters comes from len(text_data). The number of tokens comes from passing the same text through the tokenizer and measuring the result. Both measurements are compared directly, because they describe the same span of text in different units.
| Unit | How it is obtained | What it describes | Value in this text |
|---|---|---|---|
| Characters | len(text_data) | The raw text as typed | Not stated numerically in the source |
| Tokens | Run the same text through the tokenizer and measure the result | How the model will actually read the text | 5,145 tokens |
Two counts for the same text
Computing the Split Index
Next the text is divided into a training part and a validation part. The validation part is used for model evaluation during training, so it must be kept separate from the training part. The split is set by a train_ratio of 0.90, which means 90 percent of the characters go to training and the remaining 10 percent go to validation. The division point is called split_idx, and it is computed from train_ratio. Roughly, if N is the total number of characters, the split index sits near $\approx 0.90 \times N$, with training covering the characters before it and validation covering the characters from it onward.
Computing the split for a hypothetical text
Suppose a text has 10,000 characters and train_ratio is 0.90. Where does the split fall, and how many characters go to each part? The numbers here are hypothetical, chosen only to show the arithmetic.
- 1Identify the total
The total is N = 10,000 characters.
- 2Apply the ratio
The training part takes 90 percent, so
split_idxis about 0.90 times 10,000, which is 9,000. - 3Assign the characters
Characters 0 through 8,999 form the training part, which is 9,000 characters. Characters 9,000 through 9,999 form the validation part, which is 1,000 characters.
- 4Check the parts
9,000 plus 1,000 equals 10,000, so no character is lost or counted twice.
split_idx is 9,000. Training gets 9,000 characters and validation gets 1,000.
Tracing Split Text Into Batches
A split subset is still plain text, and the model needs fixed-size tensors. The source describes the preparation as a chain of operations: the subset is tokenized, the token sequence is divided into chunks of a user-specified length, the chunks are shuffled, and the shuffled chunks are organized into batches. The batch size sets how many chunks are processed together. Follow one training subset through each stage in the diagram below.
Notice that the shape of the data changes at each stage. Text is a string, the token sequence is a flat list of token IDs, a chunk is a fixed-length window of that list, and a batch stacks several chunks together. The final batch shape is (batch_size, context_length), so with a batch size of 2 and 256-token chunks, each batch holds two samples of 256 tokens.
Each loader yields a pair: an input x and a target y. Each chunk therefore serves two roles, and the two windows are always taken from the same token sequence. The explanation below depends on that pairing.
The one-position shift is the usual convention for next-token prediction: the target is the same stream of tokens as the input, moved forward by one position, so the model is asked to predict each next token. The source's loop confirms that each batch is an input and target pair, so the shift is the part to check when you inspect the windows.
Configuring the Training and Validation Loaders
The loaders are built with the create_dataloader_v1 function from chapter 2. Both calls use the same batch size, max_length, and stride. The settings that differ are drop_last and shuffle, and those differences encode the training-versus-validation distinction. The code also fixes a random seed so the shuffling is repeatable.
| Setting | Training loader | Validation loader | Reason given in the source |
|---|---|---|---|
| Batch size | 2 | 2 | Same for both loaders |
| max_length | 256 | 256 | Same for both loaders |
| stride | Same as validation | Same as training | Same for both loaders |
| shuffle | On | Off | Training order should vary; a fixed validation order keeps evaluation consistent |
| drop_last | True | False | Training drops an incomplete final batch; validation keeps every chunk |
Matching settings and the settings that differ
Setting drop_last to True on the training loader discards an incomplete final batch, so every training batch has the full size. The validation loader sets it to False, so no validation chunk is silently thrown away. Shuffling is switched on for training, where the order of examples should vary, and off for validation, where a fixed order keeps evaluation consistent.
Checking Batch Shapes by Printing
The source suggests an optional check. Iterate through each loader and print the shape of every input and target tensor. Each loader yields a pair, so the loop unpacks two values per batch. Try predicting the result before you read the answer.
- The loop unpacks each batch into an input x and a target y
print(x.shape, y.shape)reports the tensor dimensions of each batch- The same check runs on the validation loader to confirm its batches
What do you think happens?
How many batches does the training loader yield, and what shape does each input x have?
Reveal answer
Nine batches, each of shape (2, 256)
The source reports that the training loader yields nine batches, each with two samples of 256 tokens. The validation loader yields one batch of two input examples, which matches the expectation that only about a tenth of the text goes to validation.
Mistakes Beginners Make With Splits and Loaders
Common mistake Counting characters and assuming the model reads that many units
What it looks likeReporting the len(text_data) result as the size of the model's input
Why it failsThe character count describes the raw text, while the token count describes how the model reads it.
Do this insteadReport token counts when describing what the model reads, and keep character counts for the raw text.
Common mistake Shuffling the validation loader
What it looks likeSetting shuffle to True on both loaders for consistency
Why it failsValidation uses a fixed order so evaluation stays consistent from run to run.
Do this insteadSet shuffle to True only on the training loader, as the source does.
Common mistake Setting drop_last to True on validation
What it looks likeCopying the training loader's settings to the validation loader
Why it failsValidation would silently lose its incomplete final batch.
Do this insteadKeep drop_last False on the validation loader.
Common mistake Letting the subsets overlap
Why it failsValidation would then measure text the model has already seen during training.
Do this insteadUse one split_idx so the training part and the validation part never share characters.
Common mistake Treating a batch size of 2 as a model-scale setting. The source uses 2 only because the dataset is small.
Choosing a Batch Size
Batch size is also a resource choice. The source uses a batch size of 2 because the dataset is small. It notes that training LLMs with batch sizes of 1,024 or larger is not uncommon in practice. The batch size used in a small exercise should therefore be read as a classroom setting that keeps runs quick, not as a recommendation for real training.
Practice: Compute a Split and Its Batches
A hypothetical text has 8,000 characters and train_ratio is 0.75. Find split_idx, the number of characters in the training part, and the number in the validation part. Then say how many characters of the validation part would be in the training loader if the subsets were mixed by mistake.
Hints
- Multiply the total character count by
train_ratioto getsplit_idx - Characters before
split_idxbelong to training, and the rest belong to validation - Mixing the subsets would give the training loader characters that belong to validation
Key Takeaways
- A small public domain text such as 'The Verdict' lets the whole pipeline run on a laptop in minutes, which suits learning, while larger corpora and real pretraining scale up the cost sharply.
- Characters and tokens are different counts. The token count describes what the model reads, so it is the count that matters for the model.
- A
split_idxcomputed fromtrain_ratiodivides the text into a training part and a validation part that are never mixed. - Split text is tokenized, chunked into fixed-length windows, shuffled, and grouped into batches of shape (
batch_size,context_length). - The training loader shuffles and drops incomplete batches, while the validation loader keeps a fixed order and keeps every chunk; printed batch shapes confirm the loaders were built correctly.