L4 · 09 · Capstone: Train & Serve a Tiny GPT

The Level 4 capstone closes the loop on everything this level covered: implement a tiny GPT (page 02), train it (03), quantise and serve it (04), benchmark it (04/05), and document the whole thing like a real system (06–07). You end with a model you built and a design doc you'd actually show a team.

Level 48–14 hCapstone

The deliverable

The arc

implement(page 02) train(03) quantise+ serve (04) benchmark(04/05) design doc + model card(06/07)

Step 1: implement and train

Use the attention you wrote in lab 2, or adapt nanoGPT. Keep it small enough to train on CPU/a laptop in minutes–hours: d_model ~128, 4 layers, 4 heads, character-level tokeniser. Pick a corpus you know well (a book, your docs, code).

import torch, torch.nn as nn, torch.nn.functional as F

class Block(nn.Module):
    def __init__(self, d, h):
        super().__init__()
        self.attn = nn.MultiheadAttention(d, h, batch_first=True)
        self.ln1, self.ln2 = nn.LayerNorm(d), nn.LayerNorm(d)
        self.ff = nn.Sequential(nn.Linear(d, 4*d), nn.GELU(), nn.Linear(4*d, d))
    def forward(self, x, mask):
        a, _ = self.attn(x, x, x, attn_mask=mask, need_weights=False)
        x = self.ln1(x + a)
        return self.ln2(x + self.ff(x))

class TinyGPT(nn.Module):
    def __init__(self, V, d=128, h=4, L=4, T=128):
        super().__init__()
        self.tok, self.pos = nn.Embedding(V, d), nn.Embedding(T, d)
        self.blocks = nn.ModuleList(Block(d, h) for _ in range(L))
        self.head, self.T = nn.Linear(d, V), T
        mask = torch.triu(torch.ones(T, T), 1).bool()   # causal
        self.register_buffer("mask", mask)
    def forward(self, idx):
        t = idx.size(1)
        x = self.tok(idx) + self.pos(torch.arange(t))
        for b in self.blocks: x = b(x, self.mask[:t, :t])
        return self.head(x)

Train it (page 02's loop): get batch → logits → cross-entropy vs. shifted targets → backward → step. Watch the loss drop; generate samples along the way to see it learn.

Step 2: quantise and serve

Quantisation for your own tiny model is dramatic but simple — cast weights to int8/fp16 and measure the size/speed change. For serving, wrap generation behind a small endpoint:

import json
import time


class TinyModel:
    """Stand-in for a real GPT.generate — demonstrates the serving pattern."""
    def generate(self, prompt_tokens, max_new):
        t0 = time.perf_counter()
        out = [7, 3, 3, 7][:max_new]          # pretend to decode
        time.sleep(0.005 * max_new)          # pretend decode latency
        return out, (time.perf_counter() - t0)


def handle_request(body: dict, model) -> dict:
    """OpenAI-ish request -> response with usage + latency."""
    prompt = body.get("prompt_tokens", [])
    max_new = min(body.get("max_tokens", 16), 128)
    t0 = time.perf_counter()
    tokens, decode_s = model.generate(prompt, max_new)
    return {
        "choices": [{"tokens": tokens}],
        "usage": {"prompt_tokens": len(prompt), "completion_tokens": len(tokens)},
        "latency_ms": round((time.perf_counter() - t0) * 1000, 1),
        "finish_reason": "length" if len(tokens) == max_new else "stop",
    }


if __name__ == "__main__":
    m = TinyModel()
    r = handle_request({"prompt_tokens": [1, 2, 3], "max_tokens": 3}, m)
    print(json.dumps(r))
    assert r["usage"]["completion_tokens"] == 3

Point: the serving layer tracks usage and latency per call — the same fields you logged in L3·07. A real version uses your trained model and a real HTTP server (or vLLM/llama.cpp once converted).

Step 3: benchmark

Step 4: the design document

One to two pages, model-card + design-doc style:

Acceptance criteria

What "done" proves You can take a model from code to a documented, served, benchmarked system. That's the Level 4 claim to "expert": not that you can quote the transformer paper, but that you can build and reason about the machinery end to end.
Then Level 5 Level 5 is where you choose a speciality and go to the frontier: reading research, interpretability, on-device AI, multimodal systems, and leading AI adoption. Level 4's internals are what make Level 5's papers readable.