Implementing the Modified Training Function
Linear warmup gradually increases the learning rate from initial_lr to peak_lr during the first warmup_steps batches of training.
Why a Model Needs a Training Function
A language model does not become useful on its own. It has to be trained, and the work of training is organized around a function. A training function is a function you write to train a model: it takes a model and training data and returns the model in a trained state.
The phrase implementing a training function points to the fact that training is not automatic. Someone has to decide how the training process is organized and write it as a function. Once it exists as a function, the same procedure can be called again whenever the model needs to be trained, which keeps the process consistent and easy to repeat.
This article follows one idea from the chapter on training and evaluating large language models: how a training function is structured, and in particular how its learning rate is computed. The central question to keep in mind is this: what goes into a training function, and what state does the model hold when the function finishes? The source for this topic is brief, so the focus here is on the core mechanism and what it means for structuring your code.
Inputs, Outputs, and the Model's State
The easiest way to build intuition for a training function is to follow the model's state from start to finish. Before training, the model is in its initial condition. During training, the function repeatedly processes data and updates the model. After training, the model is in a new condition that reflects the data it was trained on. Each stage is a snapshot of the same model at a different point in time.
The diagram shows the shape of the idea. The training data and the model both go into the training function, and what comes back is the model after training. The model's internal state is the part that changes, which is why the trained model is the changed element. Practically, this means you should check after the call that the model's state actually changed, because a function that returns without updating the model has not trained anything.
Where Pretraining Fits
The source names two linked ideas: implementing a training function, and pretraining the LLM. Pretraining is the stage where a large language model is trained on its base data, and the training function is the code that carries out that stage. In other words, the function is the mechanism, and pretraining is the job it is used for.
The Learning Rate Changes on Every Step
A learning rate controls how large a change the optimizer makes to a model's weights on each update. In many simple training loops it stays at one value for the entire run. The training function in this article does something different: it recalculates the learning rate on every step of the loop, and it uses one rule for the early steps and another rule for the rest of training. The two rules are called the warmup phase and the cosine annealing phase.
The word linear describes the shape of the warmup change. The learning rate rises by the same amount on every step, so plotted against steps it forms a straight line. The increment is a fixed quantity computed once, before the training loop begins. The learning rate itself is recalculated inside the innermost loop, once per batch, because the step counter changes once per batch.
Computing the Warmup Increment
The increment added on each warmup step is the total rise divided by the number of warmup steps. The total rise is the distance from initial_lr up to peak_lr, and the number of warmup steps spreads that rise evenly across the warmup period.
- 1Start with the peak learning rate, which the function reads from the optimizer.
- 2Subtract
initial_lrto get the total rise that warmup must cover. - 3Divide by
warmup_stepsto get the fixed amount the learning rate rises on each warmup step. - 4This quotient is computed once, before the epoch loop starts, and reused on every warmup step.
Once the increment is known, the learning rate at any warmup step follows directly. The step counter global_step counts the steps taken so far, so the learning rate is the starting value plus global_step times the increment.
Computing the increment and the learning rate at step 2
Suppose initial_lr is 0.1, peak_lr is 0.5, and warmup_steps is 4. What is the warmup increment, and what is the learning rate when global_step is 2?
- 1Find the total rise
peak_lrminusinitial_lris 0.5 minus 0.1, which is 0.4. - 2Find the increment
Divide the total rise by
warmup_steps: 0.4 divided by 4 gives 0.1. - 3Apply the warmup formula
With
global_stepequal to 2, the learning rate isinitial_lrplus 2 times 0.1, which is 0.1 plus 0.2. - 4Check the branch
global_step2 is less thanwarmup_steps4, so the linear warmup branch applies.
The increment is 0.1, and the learning rate at global_step 2 is 0.3.
Watching Warmup Climb Batch by Batch
What do you think happens?
Using the same values (initial_lr 0.1, peak_lr 0.5, warmup_steps 4), what is the learning rate at global_step 2?
Reveal answer
0.3
The increment is 0.1, and 0.1 plus 2 times 0.1 equals 0.3. Each warmup step adds the same fixed amount, so the rise is a straight line.
The diagram shows the warmup rising in equal steps of 0.1. Each batch adds the same amount, so the learning rate climbs in a straight line. When global_step reaches 4, which equals warmup_steps, the warmup branch no longer applies and the learning rate is taken from the cosine schedule, which starts from the peak.
Switching to Cosine Annealing
The branch that handles warmup begins with the condition global_step < warmup_steps. When that condition is true, the code uses the linear formula. When it is false, the code uses the cosine formula. Once global_step reaches warmup_steps, the training function stops using the linear rule and starts using cosine annealing, a technique used to adjust the learning rate after the warmup phase.
The two phases are connected through the same step counter and the same peak_lr. Warmup climbs toward the peak, and cosine annealing starts from that peak and decreases toward the minimum. The variable progress measures how far training has moved through the post-warmup period. The source defines it as the number of steps completed after warmup divided by the number of steps remaining after warmup, which is the total number of training steps minus warmup_steps. Because progress is measured from the end of warmup, the cosine schedule begins from the peak learning rate and moves toward min_lr as training continues.
Locating an Unexpected Learning Rate
When a learning rate looks wrong during training, the fastest path to the cause is to ask three questions in order. First, which branch applied at that step: was global_step below warmup_steps? Second, what were the values of initial_lr, peak_lr, and warmup_steps that fed into the increment? Third, for a cosine step, what were total_training_steps and warmup_steps that defined progress? Each question points to one line of the training function, so the divergence can be located rather than guessed.
lr_incrementis computed once, before the epoch loop starts- the condition decides which phase applies at this step
- the warmup line is where a warmup learning rate value originates
- the cosine branch is where progress determines the value
The snippet is an illustration of the structure described in the source, not a complete training function. The lines that matter for debugging are the increment line, the branch condition, and the warmup assignment. If a warmup value is wrong, check whether the increment was computed from the values you expected. If the value is wrong only after warmup ends, check the cosine branch and the progress inputs.
Mistakes When Structuring a Training Function
| Structural choice | Incorrect version | Correct version |
|---|---|---|
| Where the increment is computed | Recalculated inside the batch loop on every step | Computed once, before the epoch loop starts |
| Where the learning rate is computed | Computed once and held fixed for the whole run | Recalculated inside the innermost loop, once per batch |
| Where the peak comes from | Passed in as an argument the function does not expect | Read from the optimizer's first parameter group |
| Whether the model is checked | Function returns without confirming the model changed | Model state is confirmed to have changed after the call |
Structural choices that separate a sound training function from a fragile one
Common mistake Treating the learning rate as a single fixed value for the whole run
Why it failsThe training function in this article recalculates the learning rate on every step and switches rules, so a fixed value would skip warmup and cosine annealing entirely.
Do this insteadCompute the learning rate inside the per-batch loop, using the phase that applies at that step.
Common mistake Recomputing the warmup increment inside the batch loop
Why it failsThe increment depends only on
peak_lr,initial_lr, andwarmup_steps, which do not change during the run, so recomputing it wastes work and invites inconsistent inputs.Do this insteadCompute lr_increment once, before the epoch loop starts.
Common mistake Passing the peak learning rate as an argument
Why it failsThe source says the function does not receive a peak learning rate as an argument and reads it from the optimizer instead.
Do this insteadRead the peak from the optimizer's first parameter group, and understand that this assumes the optimizer's starting learning rate is the peak.
Common mistake Assuming the function trains the model just because it was called
Why it failsA function can run and still leave the model unchanged, so calling it does not guarantee training happened.
Do this insteadConfirm that the model's state actually changed after the call.
Common mistake Confusing the step counter global_step with the epoch number when checking which phase applies
Practice: Trace a Learning Rate Back to Its Line
Using initial_lr 0.1, peak_lr 0.5, and warmup_steps 4, a training run logs a learning rate of 0.25 at some step during warmup. Using the three debugging questions, determine which branch applied, what the increment should be, and which line would produce the value you need to check. Then state what the learning rate should be at global_step 3.
Hints
- Start by asking whether
global_stepwas belowwarmup_stepsat the logged step. - The increment is
peak_lrminusinitial_lr, divided bywarmup_steps. - At
global_step3, the warmup formula givesinitial_lrplus 3 times the increment.
Key Takeaways
- A training function takes a model and training data and returns the model in a trained state, and its usefulness depends on the model's state actually changing after the call.
- Linear warmup raises the learning rate by a fixed increment, (
peak_lrminusinitial_lr) divided bywarmup_steps, computed once before the epoch loop and applied throughinitial_lrplusglobal_steptimes the increment. - The training loop switches from warmup to cosine annealing when
global_stepis no longer less thanwarmup_steps, and the cosine schedule starts from the peak and lowers towardmin_lrusing progress. - When a learning rate looks wrong, identify the branch that applied, trace the increment inputs, and check the cosine progress inputs to locate the responsible line.
- Pretraining a large language model is the job, and the training function is the mechanism used to carry it out.