Concepts / Training and Validation Set Losses

Training and Validation Set Losses

When the code is only measuring loss, that bookkeeping is not needed, so turning it off saves work.

  • Programming

Why One Loss Number Is Not Enough

Training a large language model produces a stream of numbers, and one of the most important is the loss. A single loss value on its own does not tell you very much. Is the model learning something useful, or is it only getting better at the particular text it has already seen? This article focuses on one practical answer to that question: computing the loss on two different sets of data, the training set and the validation set, and reading the two results together.

The source states the purpose directly: computing the training and validation set losses is a way to assess the quality of LLM-generated text during training. As you read, keep one question in mind: what does each of the two losses tell you that the other one cannot?

The script under study is small, but almost every line answers a practical question. Which hardware should run the computation? Should the program keep track of gradients? Which data goes into the loss calculation? The sections below answer those questions in the order the script asks them.

Choosing the Device at Run Time

The first line of the script selects a device. A device is the hardware that performs the computation. The source describes two options: a CPU, which is the general processor, and a GPU, which is the graphics processor supported by CUDA. The selection is written as follows.

python

The expression checks whether a CUDA-supported GPU is available by calling torch.cuda.is_available(). If that check returns true, the device is set to CUDA. If it does not, the device falls back to the CPU. The flowchart below shows the two branches and where the chosen device is applied next.

The source makes an important practical point: if the machine has a CUDA-supported GPU, the LLM trains on the GPU without any changes to the code. The same script therefore works on a laptop with no GPU and on a workstation with one. The selection happens at run time, so you do not need separate versions of the program.

Once the device is chosen, the script calls model.to(device). This moves the model's parameters onto the selected hardware. The source also states that the data must be loaded onto that same device. A model on the GPU with data still on the CPU would not be working on the same hardware, so the device value is passed explicitly into the loss function as well. The device setting is what keeps the model and the data together.

Switching Off Gradient Tracking

The next construct in the script is a with block. The source explains that the with torch.no_grad(): line wraps the two loss calculations and disables gradient tracking for efficiency, because the model is not being trained at this point in the script. The script is measuring loss, not learning from it.

Gradient tracking is the bookkeeping PyTorch normally does so that a model can be updated during training. It is necessary when weights are going to change. When the code is only measuring loss, that bookkeeping is not needed, so turning it off saves work. The source frames this as an efficiency choice; it does not change which data is measured or how the loss is defined.

python

Every line inside the with block runs with gradient tracking turned off. That means both loss values are computed under the same condition. If one loss were computed with tracking on and the other with tracking off, the two numbers would not be measured under identical conditions.

Tracing calc_loss_loader Twice

The two loss lines are nearly identical. Each one calls calc_loss_loader with a data loader, the model, and the device. Only the loader changes: train_loader is used for the training loss and val_loader for the validation loss. Because the function is the same in both calls, both numbers are computed the same way, which makes them directly comparable.

Read the diagram from left to right. Two loaders enter on the left, one carrying training data and one carrying validation data. The same model and the same device feed both calls. Each call produces one number, and those numbers are train_loss and val_loss. The source does not spell out the internal steps inside calc_loss_loader, so the diagram shows only its inputs and outputs. What matters for the lesson is that the same function, model, and device are used in both calls, and only the data changes.

Worked example

Tracing one run of the loss-evaluation script

Follow the script through the steps it takes, without assuming any particular numeric values. The source gives the sequence of steps but no measured results.

  1. 1
    Select the device

    torch.device checks torch.cuda.is_available(). If true, the device is CUDA; if false, it is the CPU.

  2. 2
    Move the model

    model.to(device) places the model's parameters on the selected hardware.

  3. 3
    Enter the no-gradient scope

    The with torch.no_grad(): block turns off gradient tracking for every line inside it.

  4. 4
    Compute the training loss

    calc_loss_loader receives train_loader, model, and device, and returns train_loss.

  5. 5
    Compute the validation loss

    calc_loss_loader receives val_loader, model, and device under the same conditions, and returns val_loss.

  6. 6
    Read the pair

    The two values are compared together to judge how the model handles data it learns from and data it is checked against.

Result

The script produces two loss values, train_loss and val_loss, computed on the same hardware, with the same model, and with gradient tracking off. Their difference in data is the only deliberate difference between the two measurements.

Where the Script Can Go Wrong

The device line and the loader calls depend on each other. The source's logic implies several ways the script can fail or mislead when the pieces do not line up.

  • Common mistake

    Moving the model to one device and leaving the data on another

    What it looks like

    The model is moved to CUDA with model.to(device), but a batch of data stays on the CPU when it reaches the loss calculation.

    Why it fails

    The source says the model and the data must be on the same device. A model on the GPU and data on the CPU are not working on the same hardware.

    Do this instead

    Pass the same device value into every loss call, as the script does, so the data is loaded onto the device the model uses.

  • Common mistake

    Hard-coding the device to CUDA

    What it looks like
    device = torch.device("cuda") on a laptop with no CUDA-supported GPU.
    Why it fails

    The script is written to run on either kind of machine. A hard-coded CUDA device removes the fallback that torch.cuda.is_available() provides.

    Do this instead

    Keep the conditional expression so the device is chosen at run time.

  • Common mistake

    Passing the training loader for both losses

    What it looks like

    Both calls use train_loader, so val_loss is really a second training loss.

    Why it fails

    The script changes only the loader between the two calls. Using the same loader for both removes the second dataset that the assessment depends on.

    Do this instead

    Call calc_loss_loader with train_loader for train_loss and with val_loader for val_loss.

  • Common mistake

    Computing one loss outside the no-gradient block, so the two losses are not measured under identical conditions

Training Loss Versus Validation Loss

The two losses are computed by the same function, but they are measurements on different data. The table below sets out what stays the same and what changes between the training pass and the validation pass, using only what the source describes.

AspectTraining set lossValidation set loss
Data usedThe training set, the data the model learns fromThe validation set, the data the model is checked against
Loader passed intrain_loaderval_loader
Function calledcalc_loss_loadercalc_loss_loader
Model and deviceSame model, same deviceSame model, same device
Gradient trackingOff inside the no_grad blockOff inside the no_grad block
Output nametrain_lossval_loss

What is the same and what is different in the two loss calculations

Because the conditions are held constant, any difference between the two numbers can be traced to the data rather than to a change in method. The source does not give numeric values for either loss, so no particular result should be assumed from the script alone.

Reading the Two Losses Together

The source's purpose for the two losses is to assess the quality of LLM-generated text during training. The training set loss shows how the model does on the text it learns from. The validation set loss shows how it does on text it is checked against. Neither number answers the purpose alone. The assessment comes from the pair.

The source states that the two losses are separate measurements that are read together, not interchangeable numbers. It also states that judging text quality from a single loss leaves out part of the evidence. Returning to the opening question: the training loss alone cannot show whether the model is learning something useful or only getting better at the text it has already seen, because it is measured on that same text. The validation loss supplies the second view.

  • Common mistake

    Treating train_loss and val_loss as interchangeable

    Why it fails

    The source calls them separate measurements that are read together. Each answers a different question about the model.

    Do this instead

    Read the two values side by side and ask what each one tells you that the other cannot.

  • Common mistake

    Judging text quality from one loss only

    Why it fails

    The source says a single loss leaves out part of the evidence needed to assess quality.

    Do this instead

    Use the training and validation losses together as the basis for the assessment.

  • Common mistake

    Assuming no_grad changes what is being measured

    Why it fails

    The source gives no-grad as an efficiency measure because the script is measuring loss rather than training. It turns off bookkeeping, not data.

    Do this instead

    Think of the no-gradient block as a way of measuring with less work, while the data and the loss definition stay the same.

Predicting the Device Choice

What do you think happens?

A workstation has a CUDA-supported GPU and the script runs unchanged. Which device does torch.device("cuda" if torch.cuda.is_available() else "cpu") select?

  • cuda

  • cpu

Reveal answer
Answer:

cuda

When torch.cuda.is_available() is true, the expression selects CUDA. The model and the data are then both placed on that device, and the same code runs without changes.

MEDIUM

A colleague moves the model to the GPU, but their loss calculation still receives batches that were loaded onto the CPU. Using the source's rule, explain what is wrong and what single change in the script would address the issue.

Hints
  • Look at what the source says must happen to the data after the model is moved.
  • The device value is passed explicitly into the loss function as well.

Key Takeaways

  • The device line selects CUDA when torch.cuda.is_available() is true and falls back to the CPU otherwise, so the same code runs on either kind of machine.
  • The model and the data must be on the same device, so the device value is passed into every loss call.
  • The with torch.no_grad(): block turns off gradient tracking for efficiency because the script is measuring loss rather than training.
  • calc_loss_loader is called once with the training loader and once with the validation loader, so train_loss and val_loss are computed under identical conditions and can be compared.
  • Computing training and validation set losses is a way to assess the quality of LLM-generated text during training, and the two values are read together rather than in isolation.

Related concepts

More concepts starting with T