Learning Objectives

  • Understand pretraining vs fine-tuning
  • Implement efficient training loops
  • Apply learning rate schedules (warmup + decay)
  • Implement checkpointing and evaluation

7.1 Pretraining: Next-Token Prediction

The pretraining task: given tokens [t₁, t₂, …, t_n], predict [t₂, t₃, …, t_{n+1}]. This self-supervised objective can be applied to any text corpus without labels.

Haskell — sliding-window training examples
-- Sliding window over tokenised text
createTrainingExamples :: Int -> [Int] -> [([Int], Int)]
createTrainingExamples contextLength tokens =
  [ (take contextLength (drop i tokens), tokens !! (i + contextLength))
  | i <- [0 .. length tokens - contextLength - 1] ]

-- Batch structure
data Batch = Batch
  { inputIds     :: Matrix Int
  , targetIds    :: Vector Int
  , attentionMask :: Matrix Bool
  }

createBatches :: Int -> Int -> [([Int], Int)] -> [Batch]
createBatches batchSize contextLength examples =
  map createBatch (chunksOf batchSize examples)
  where
    createBatch batch =
      Batch { inputIds  = map fst batch
            , targetIds = map snd batch
            , attentionMask = trueMatrix (length batch) contextLength }

7.2 Training Loop

Haskell — training state and step
data TrainingState = TrainingState
  { model     :: LanguageModel
  , optimizer :: AdamOptimizer
  , step      :: Int
  , loss      :: Double
  }

-- One training step
trainStep :: TrainingState -> Batch -> TrainingState
trainStep state batch =
  let logits      = forward (model state) (inputIds batch)
      batchLoss   = crossEntropyLoss logits (targetIds batch)
      gradients   = backprop (model state) logits (targetIds batch)
      updatedModel = updateParameters (optimizer state) (model state) gradients
  in state { model = updatedModel
           , step  = step state + 1
           , loss  = batchLoss }

-- Full training run
train :: LanguageModel -> Int -> [Batch] -> LanguageModel
train model numEpochs batches =
  let initialState = TrainingState
        { model = model, optimizer = adamOptimizer, step = 0, loss = 0 }
      finalState   = foldl (\s _ -> trainEpoch s batches) initialState [1..numEpochs]
  in model finalState

trainEpoch :: TrainingState -> [Batch] -> TrainingState
trainEpoch = foldl trainStep

7.3 Learning Rate Scheduling

Learning rates that are too high lead to unstable training; too low leads to slow convergence. The original Transformer paper uses a warmup-then-decay schedule:

lr = d_model^(-0.5) · min(step^(-0.5), step · warmup_steps^(-1.5)) Typical values: warmup_steps = 4000 peak LR ≈ 6e-4 (for d_model = 512)
Haskell — LR schedule
learningRateSchedule
  :: Double   -- d_model
  -> Int      -- warmup_steps
  -> Int      -- current_step
  -> Double
learningRateSchedule d_model warmupSteps currentStep =
  let step    = fromIntegral currentStep
      warmup  = fromIntegral warmupSteps
      factor  = d_model ** (-0.5)
      schedule = min (step ** (-0.5)) (step * warmup ** (-1.5))
  in factor * schedule

7.4 Checkpointing

Haskell — save and load checkpoints
saveCheckpoint :: FilePath -> LanguageModel -> Int -> IO ()
saveCheckpoint path model step = do
  encodeFile (path ++ "/model_" ++ show step ++ ".model") model
  writeFile  (path ++ "/metadata.txt") (show step)

loadCheckpoint :: FilePath -> IO (LanguageModel, Int)
loadCheckpoint path = do
  step  <- read <$> readFile (path ++ "/metadata.txt")
  model <- decodeFile (path ++ "/model_" ++ show step ++ ".model")
  return (model, step)

-- Evaluation
evaluate :: LanguageModel -> [Batch] -> Double
evaluate model batches =
  let losses = map (computeBatchLoss model) batches
  in average losses

Reading & Viewing

Essential reading

Attention Is All You Need — Training details section (2017) Vaswani et al. Section 5 covers the exact LR schedule and training settings. arxiv.org/abs/1706.03762
Language Models are Unsupervised Multitask Learners (GPT-2, 2019) Radford, Wu, Child, Luan, Amodei, Sutskever Details on large-scale pretraining. PDF link

Reference implementation

nanoGPT Andrej Karpathy Minimal ~400-line PyTorch GPT. Excellent reference when implementing your Haskell version. github.com/karpathy/nanoGPT