Preparing Datasets for Fine-Tuning
Instantiating a PyTorch dataset means calling a custom class such as SpamDataset with a CSV file name and keyword arguments, producing a dataset object.
Where Dataset Preparation Fits
By the time fine-tuning begins, two large pieces of work are already finished. The first is a model that has been pre-trained and loaded into memory. The second is a dataset of instructions that has been cleaned, formatted, and turned into batches. Instruction fine-tuning is the step that connects these two pieces: the model is trained further, this time on instruction examples, so that it learns to respond to the kind of input the dataset describes.
This article looks at the moment just before that training step. The goal is to understand what has been prepared, what the fine-tuning step is allowed to rely on, and how to tell which part of the overall process you are looking at when something goes wrong. The work starts with a single setup listing that creates three dataset objects, so that is where we begin.
Reading the Setup Listing
The listing that prepares the data has three main parts. The first part is the imports: PyTorch, the Dataset base class from torch.utils.data, the tiktoken library, and the SpamDataset class, which the listing imports from a module named chapter06. The second part creates a single tokenizer object with tiktoken.get_encoding("gpt2"). The third part creates three dataset objects, one each for the training, validation, and test CSV files.
A useful habit when reading this listing is to follow it line by line before reading any explanation. For each line, note what the variable is and what the call receives. The tokenizer is created once and that same object is passed to every dataset call, so all three splits are prepared with the same tokenizer.
Calling SpamDataset Like Any Class
Creating a dataset object in this section means calling the custom SpamDataset class the same way you would call any Python class: you write the class name and pass arguments in parentheses. The source describes three arguments. The first is the name of a CSV file, such as train.csv. The second is max_length, a keyword argument. The third is tokenizer, another keyword argument that receives the tokenizer object created earlier.
The listing uses keyword arguments for max_length and tokenizer. A keyword argument names the parameter it fills, so the call reads as a set of labeled settings rather than a bare list of values. The file name is passed positionally, as the first argument. Keeping the names visible makes it easier, when you later trace a problem, to see which setting went where.
- The tokenizer is created once and shared by all three calls
- The training call passes
max_length=None - The validation and test calls read
max_lengthfrom the training dataset object - The
validation.csvandtest.csvfile names are placeholders chosen for this illustration; the source does not name them
Sharing max_length Across Splits
The training dataset is created with max_length=None. The validation and test datasets are created with max_length=train_dataset.max_length. The listing therefore reads a value from the training dataset object and reuses it for the other two splits. Reading an attribute from an existing object is a normal Python pattern: the dot followed by a name looks up a stored value on that object.
What do you think happens?
Before reading on: what value of max_length does val_dataset receive, and is it the same value that train_dataset received?
Reveal answer
val_dataset receives train_dataset.max_length. The test dataset does the same. This is the value read from the training dataset object, so the splits share one setting.
The listing creates train_dataset with max_length=None, then passes train_dataset.max_length to the validation and test calls. Those splits do not compute their own value.
The purpose of this pattern is consistency. If the validation and test datasets were created with a different maximum length than the training dataset, the splits could be prepared differently even though they come from the same data source. The source does not explain the internal logic inside SpamDataset that determines the max_length value, because the class is imported from chapter06 and its definition is not in the listing. What the listing does show is which value gets passed where, and that is what you can check when tracing a run.
Ordering Datasets Before Data Loaders
The source states that data loaders are created after the dataset objects have been instantiated. In this section's sequence, the three dataset objects come first, and the data loaders are built from them afterward. The dataset objects hold the prepared data; the data loaders are the next step that depends on those objects existing.
The source text at this point does not include the code that builds the data loaders, so this article does not show that code. What you can rely on is the ordering: a dataset object must exist before a data loader can be created from it. If a loader were built before its dataset existed, the name it refers to would not yet be defined.
Three Stages of Instruction Fine-Tuning
Instruction fine-tuning is organized as a three-stage process. Stage 1 prepares the dataset. Stage 2 fine-tunes the LLM. Stage 3 evaluates the LLM. The diagram below places the steps in order and groups each step under the stage it belongs to.
Notice where the fine-tuning work sits. Stage 1 and Stage 3 surround it. Stage 1 produces the batched data loaders that the training step will consume, and Stage 3 looks at the responses the tuned model produces afterward. Stage 2 is the only stage in which the model's weights are trained, and it contains three steps: loading the pretrained LLM, running instruction fine-tuning, and inspecting the modeling loss.
Inputs Meeting at the Fine-Tuning Step
Fine-tuning does not begin from random weights. It continues training the model that already exists, which is the model loaded in the previous section. The data side is the instruction dataset prepared earlier in the chapter. That dataset was processed at the start of the chapter, so the fine-tuning step does not repeat that preparation work.
| Input | What it already is | What it contributes to fine-tuning |
|---|---|---|
| Pre-trained LLM | A model that has been pre-trained and loaded into memory | The starting point: training continues from this model rather than from random weights |
| Instruction dataset | Instructions that have been cleaned, formatted, and turned into batches | The examples that the further training uses, so the model learns to respond to instruction-style input |
What each input brings to the fine-tuning step
Fine-tuning also needs a way to compute loss and run training. The chapter already contains code for both, so the fine-tuning code does not rewrite them. It imports the two functions calc_loss_loader and train_model_simple from chapter 5. Four inputs meet at the single fine-tuning step: the pre-trained LLM, the instruction dataset, and the two chapter 5 functions that supply the loss calculation and the training procedure.
Mistakes Beginners Make with Dataset Setup
Common mistake Recalculating max_length separately for the validation and test datasets
What it looks likePassing a freshly computed value to val_dataset and test_dataset instead of train_dataset.max_length
Why it failsThe splits could then be prepared differently even though they come from the same data source
Do this insteadRead train_dataset.max_length and pass that value to both the validation and test calls
Common mistake Passing the file name in the wrong slot
What it looks likeWriting SpamDataset(tokenizer=tokenizer, "train.csv", max_length=None), which places a positional argument after a keyword argument
Why it failsThe CSV file name is the first positional argument; placing it after keyword arguments breaks the call pattern the listing uses
Do this insteadPass the file name first, then the labeled keyword arguments max_length and tokenizer
Common mistake Creating a data loader before its dataset object exists
What it looks likeReferring to a loader built from train_dataset when train_dataset has not been assigned yet
Why it failsThe dataset name is not yet defined, so the loader has nothing to be built from
Do this insteadInstantiate the three dataset objects first, then build the data loaders from them
Common mistake Rewriting the loss and training functions in the fine-tuning code
What it looks likeCopying calc_loss_loader and train_model_simple into the fine-tuning file
Why it failsThose routines already exist in chapter 5 and are reused by import, so copies can drift out of sync with the original
Do this insteadImport calc_loss_loader and train_model_simple from chapter 5
Tracing a Failure Through the Stages
Suppose a run of the process produces a result you did not expect. The three-stage structure gives you a way to narrow the search. Ask first which stage the problem most likely belongs to. If the data loaders are wrong, the cause lies in Stage 1. If the model is loaded incorrectly or the loss and training steps misbehave, the cause lies in Stage 2. If the responses look wrong after training finishes, the cause may be in Stage 3, where responses are extracted and scored.
A run shows that the validation split was prepared with a different maximum length from the training split. Which line of the setup listing should you check first, and which stage does that line belong to?
Hints
- Look at the arguments passed to the validation dataset call
- Ask which argument should read
train_dataset.max_length - Dataset objects are created in Stage 1, the stage that produces the batched data loaders
Using the three-stage process, decide where each of these belongs: preparing the three dataset objects, loading the pretrained LLM, and extracting responses for scoring. Then name the stage that a loss-calculation fault would be checked against first, and explain why the chapter 5 functions are the first place to look.
Hints
- Stage 1 prepares the dataset and produces batched data loaders
- Stage 2 contains loading the pretrained LLM, running instruction fine-tuning, and inspecting the modeling loss
- Stage 3 evaluates the LLM and covers extracting and scoring responses
Key Takeaways
- Instantiating a PyTorch dataset means calling a custom class such as SpamDataset with a CSV file name as the first positional argument and labeled keyword arguments for
max_lengthand tokenizer - The training dataset is created with
max_length=None, and the validation and test datasets readtrain_dataset.max_length so that all splits share one setting - Instruction fine-tuning has three stages: Stage 1 prepares the dataset, Stage 2 fine-tunes the LLM, and Stage 3 evaluates the LLM
- Fine-tuning continues training a pre-trained LLM on the instruction dataset and imports the loss and training functions
calc_loss_loaderandtrain_model_simplefrom chapter 5 rather than rewriting them - When a result diverges, map the symptom to a stage first, then narrow the search to the steps inside that stage