RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Eruo Fredoline
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /Fine-Tuning with LoRA and QLoRA
  6. /Ch. 14
Fine-Tuning with LoRA and QLoRA

14. Training Loop

Chapter 14 of 24 · 20 min
KEY INSIGHT

The training loop is the execution engine that translates hyperparameter choices into learned weights. A reliable implementation handles data loading, gradient computation, optimizer updates, and logging with clean separation of concerns. The core sequence: load a batch → compute loss → backpropagate → update weights → repeat. Each iteration must preserve numerical stability and avoid gradient accumulation errors that produce silent failures. ```python from torch.utils.data import DataLoader from transformers import get_linear_schedule_with_warmup from torch.optim import AdamW import torch.nn.functional as F def train_epoch(model, dataloader, optimizer, scheduler, device, gradient_accumulation_steps=4): model.train() total_loss = 0 optimizer.zero_grad() for step, batch in enumerate(dataloader): input_ids = batch["input_ids"].to(device) attention_mask = batch["attention_mask"].to(device) labels = batch["labels"].to(device) outputs = model( input_ids=input_ids, attention_mask=attention_mask, labels=labels ) # Scale loss for gradient accumulation loss = outputs.loss / gradient_accumulation_steps loss.backward() if (step + 1) % gradient_accumulation_steps == 0: torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() scheduler.step() optimizer.zero_grad() total_loss += outputs.loss.detach().cpu().item() return total_loss / len(dataloader) ``` Gradient clipping prevents exploding gradients—a common failure mode with LoRA adapters on certain data distributions. The threshold of 1.0 works for most transformer architectures, but verify with your specific use case. **Optimizer selection**: AdamW with weight decay handles the regularization correctly. For LoRA, bias and layer norm parameters typically get zero weight decay. QLoRA introduces the gradient scaling factor that must be accounted for in loss calculations. ```python # Correct optimizer setup for LoRA optimizer = AdamW( [{"params": model.lora_parameters, "weight_decay": 0.01}, {"params": model.other_parameters, "weight_decay": 0.1}], lr=2e-4 ) ```

EXERCISE

: Build a Complete Training Loop

Extend the training loop to include checkpoint saving and early stopping:

def train_with_validation(
    model, train_loader, val_loader, optimizer, scheduler,
    device, epochs=3, checkpoint_dir="./checkpoints"
):
    best_val_loss = float("inf")
    patience = 2
    patience_counter = 0
    
    for epoch in range(epochs):
        train_loss = train_epoch(model, train_loader, optimizer, scheduler, device)
        val_loss = evaluate(model, val_loader, device)
        
        print(f"Epoch {epoch}: train_loss={train_loss:.4f}, val_loss={val_loss:.4f}")
        
        if val_loss < best_val_loss:
            best_val_loss = val_loss
            patience_counter = 0
            save_checkpoint(model, optimizer, epoch, checkpoint_dir)
        else:
            patience_counter += 1
            if patience_counter >= patience:
                print("Early stopping triggered")
                break
    
    return best_val_loss

Implement the evaluate() and save_checkpoint() functions to complete this pattern.

← Chapter 13
Gradient Checkpointing
Chapter 15 →
Monitoring Training