Background
Sections
IntroductionModule 01 β€” Tensors 🧊01 Β· Creating Tensors 🧊02 Β· Indexing & Reshaping πŸ”ͺ03 Β· Tensor Math βž—04 Β· Device Placement πŸ–₯️⚑Module 02 β€” Autograd βš™οΈ01 Β· The Computational Graph πŸ•ΈοΈ02 Β· Backward Pass & Gradients ⬅️03 Β· Turning Autograd Off πŸ›‘04 Β· Gradient Gotchas πŸͺ€Module 03 β€” Neural Networks01 Β· The `nn.Module` Basics 🧱02 Β· Common Layers 🧩03 Β· Building a Network πŸ—οΈ04 Β· Inspecting Models πŸ”Module 04 β€” Data Handling πŸ—‚οΈ01 Β· Dataset Basics πŸ“‡02 Β· The DataLoader 🚚03 Β· Transforms 🎨04 Β· Splits & Built-in Datasets βœ‚οΈModule 05 β€” The Training Loop πŸ”01 Β· Loss Functions 🎯02 Β· Optimizers βš™οΈ03 Β· The Training Loop πŸ”04 Β· Evaluation & Metrics πŸ“ŠModule 06 β€” Saving & Loading πŸ’Ύ01 Β· `state_dict` Basics πŸ’Ύ02 Β· Checkpoints & Resuming ⏸️03 Β· Loading for Inference πŸš€04 Β· Best Model & Early Stopping πŸ…Module 07 β€” Computer Vision πŸ‘οΈ01 Β· Convolutions πŸ”²02 Β· CNN Architecture πŸ›οΈ03 Β· Transfer Learning πŸ”04 Β· Image Classification Project πŸ§ͺModule 08 β€” NLP & Transformers πŸ’¬01 Β· Text Data & Tokenization πŸ”€02 Β· Embeddings 🧭03 Β· Recurrent Layers & LSTMs πŸ”„04 Β· Intro to Transformers ⚑Module 09 β€” Ecosystem: PyTorch Lightning ⚑01 Β· Why Lightning? πŸ€”02 Β· The LightningModule 🧩03 Β· The Trainer πŸŽ›οΈ04 Β· DataModules & Callbacks 🧰Module 10 β€” Deployment & Optimization πŸš€01 Β· Exporting Models πŸ“¦02 Β· `torch.compile` ⚑03 Β· Inference Optimization πŸͺΆ04 Β· Serving & Next Steps πŸŽ“

02 · The LightningModule 🧩

5 min read

This is where your model lives in Lightning. The trick to learning it: every method maps directly onto a piece of the loop you already write by hand.


1. The 'Why'

The LightningModule is the heart of Lightning, and the fastest way to understand it is to realize it's not a new concept β€” it's your existing training code, cut along its natural seams and dropped into named methods. When you write a raw loop, you already mentally distinguish "the part that computes a training step's loss" from "the part that sets up the optimizer" from "the part that runs a validation step." Lightning simply asks you to make those divisions explicit by putting each into its own method. Then, instead of you calling them in the right order inside hand-written loops, the Trainer (sub-module 03) calls them for you β€” at the right time, on the right device, with zero_grad/backward/step handled automatically.

The payoff is that your model code becomes a clean, declarative description of what your model does with none of the loop mechanics tangled in. A LightningModule subclass holds your nn.Module layers (in __init__, exactly as before), defines a training_step that returns the loss for one batch, optionally a validation_step for metrics, and a configure_optimizers that returns your optimizer. Notice what's missing: no for epoch, no .to(device), no zero_grad, no backward, no eval(). Lightning supplies all of that. This sub-module shows the one-to-one mapping from the raw loop you mastered in Module 05 to these methods β€” once you see the correspondence, the LightningModule stops looking like framework magic and starts looking like tidy PyTorch.


2. Core Concepts

It is an nn.Module. LightningModule subclasses nn.Module, so everything from Module 03 applies β€” you define layers in __init__ and can still call the model directly. It just adds structured hooks.

training_step(self, batch, batch_idx). The body of one training iteration: unpack the batch, run the forward pass, compute the loss, and return the loss. Lightning does zero_grad, backward, and step around it. The batch is already on the correct device.

validation_step(self, batch, batch_idx). Like training_step but for validation β€” compute and log metrics. Lightning wraps it in eval() + no_grad() automatically. (test_step mirrors it for the test set.)

configure_optimizers(self). Create and return your optimizer(s) (and optionally LR schedulers). Replaces the manual optimizer setup from Module 05.

self.log(...). Report a metric by name; Lightning aggregates it across the epoch and sends it to your logger and progress bar β€” no manual metric accumulation.


3. Code in Action

A complete LightningModule

import torch
import torch.nn as nn
import torch.nn.functional as F
import lightning as L

class LitClassifier(L.LightningModule):
    def __init__(self, in_dim=784, hidden=128, num_classes=10, lr=1e-3):
        super().__init__()
        self.save_hyperparameters()               # stores args for checkpointing/logging
        self.net = nn.Sequential(                 # same nn.Module you'd build in Module 03
            nn.Flatten(),
            nn.Linear(in_dim, hidden), nn.ReLU(),
            nn.Linear(hidden, num_classes),
        )
        self.lr = lr

    def forward(self, x):
        return self.net(x)                        # standard forward pass

    def training_step(self, batch, batch_idx):
        x, y = batch                              # batch is ALREADY on the device
        logits = self(x)
        loss = F.cross_entropy(logits, y)         # compute the loss...
        self.log("train_loss", loss)              # ...and log it
        return loss                               # RETURN it β€” Lightning does backward/step

    def validation_step(self, batch, batch_idx):
        x, y = batch
        logits = self(x)
        loss = F.cross_entropy(logits, y)
        acc = (logits.argmax(1) == y).float().mean()
        self.log_dict({"val_loss": loss, "val_acc": acc})   # eval()/no_grad() are automatic

    def configure_optimizers(self):
        return torch.optim.Adam(self.parameters(), lr=self.lr)

The mapping from raw PyTorch (side by side)

# RAW loop body                     ->   LightningModule method
# optimizer.zero_grad()             ->   (automatic)
# logits = model(x.to(device))      ->   training_step: logits = self(x)
# loss = criterion(logits, y)       ->   training_step: loss = F.cross_entropy(...)
# loss.backward()                   ->   (automatic β€” just `return loss`)
# optimizer.step()                  ->   (automatic)
# optimizer = Adam(model.params())  ->   configure_optimizers
# model.eval()/no_grad() for val    ->   (automatic around validation_step)

Using it is still normal PyTorch

model = LitClassifier()
# It's an nn.Module, so this works exactly as before:
dummy = torch.randn(4, 1, 28, 28)
print(model(dummy).shape)          # torch.Size([4, 10]) -> plain forward pass

4. Common Pitfalls

Pitfall 1 β€” Forgetting to return loss from training_step. Lightning runs the backward pass on whatever you return. If you compute the loss but don't return it (or return None), no gradients flow and the model silently never learns. Always return the loss (or a dict containing a "loss" key).

Pitfall 2 β€” Manually calling .to(device), zero_grad, or backward. Inside a LightningModule these are Lightning's job. Doing them yourself double-moves data or double-steps the optimizer, causing errors or corrupted training. Write the step as if the batch is already on-device (it is) and let Lightning handle the mechanics.

Pitfall 3 β€” Putting metric math in the wrong place or not logging. Accumulating metrics in plain Python variables across steps (the Module 05 habit) fights Lightning's system. Use self.log(...)/self.log_dict(...) so Lightning handles epoch-level aggregation and reduction across devices correctly.


5. Further Reading & Watch List


⬅️ Prev: 01 Β· Why Lightning? Β· ➑️ Next: 03 Β· The Trainer