Textbooks Are All You Need: How Data Quality Trumps Model Scale
Textbooks Are All You Need: How Data Quality Trumps Model Scale
In the current era of Large Language Models (LLMs), the prevailing narrative has been "bigger is better." The industry has largely followed scaling laws that suggest increasing parameter counts and token volume is the primary path to intelligence.
However, the release of phi-1 flipped this script. By proving that a 1.3B parameter model could outperform models orders of magnitude larger, the researchers introduced a provocative thesis: Data quality is a more powerful lever than model scale.
In this post, we dive deep into the architecture, the "textbook" data philosophy, and a PyTorch implementation of the core intuition behind phi-1.
The Core Intuition: Quality > Quantity
The central hypothesis of phi-1 is that "textbook-quality" data—content that is clear, self-contained, and instructive—can dramatically accelerate a model's learning curve.
Most LLMs are trained on massive, noisy scrapes of the internet (Common Crawl). While this provides breadth, it also introduces immense noise, contradictions, and low-signal content. Phi-1 replaces this "noise" with a curated mix of filtered high-value samples and synthetically generated textbooks.
The "Phi" Strategy in Three Pillars:
- Filtering the Noise: Instead of taking all available code, they used GPT-4 to annotate a small subset for "educational value" and trained a classifier to filter the rest.
- Synthetic Pedagogy: They used GPT-3.5 to generate synthetic Python textbooks, providing the model with structured, step-by-step explanations of concepts.
- Reasoning via Exercises: The model wasn't just fed text; it was fine-tuned on synthetic "CodeExercises" (problem $\rightarrow$ solution) to unlock logical reasoning.
The Technical Blueprint
The architecture of phi-1 is a conventional Transformer, but its power comes from the pipeline that feeds it.
High-Level Workflow
The following diagram illustrates the journey from raw, noisy web data to a high-performing compact model.
The Algorithmic Steps
- Data Curation (Filtering): A random forest classifier, trained on GPT-4 annotations, filtered the "The Stack" and StackOverflow datasets down to ~6B high-quality tokens.
- Synthetic Generation: GPT-3.5 generated a synthetic textbook dataset (<1B tokens) focused on Python.
- Pretraining (phi-1-base): The model was pretrained on the combined "CodeTextbook" dataset for ~8 passes.
- Finetuning (phi-1): A final polish using ~180M tokens of synthetic Python exercises to sharpen reasoning.
Implementation: Simulating the "Textbook" Effect
To understand why this works, we can implement a scaled-down version of the phi-1 architecture. The key to this demo is the TextbookDataset, which provides high-signal, structured patterns rather than random noise.
import torch
import torch.nn as nn
from torch.nn import functional as F
from torch.utils.data import DataLoader, Dataset
import numpy as np
class PhiConfig:
def __init__(self):
self.vocab_size = 100
self.block_size = 32
self.n_embd = 128
self.n_head = 4
self.n_layer = 4
self.dropout = 0.1
self.learning_rate = 1e-3
self.batch_size = 16
self.epochs = 10
class CausalSelfAttention(nn.Module):
def __init__(self, config):
super().__init__()
self.n_head = config.n_head
self.n_embd = config.n_embd
self.c_attn = nn.Linear(config.n_embd, 3 * config.n_embd)
self.c_proj = nn.Linear(config.n_embd, config.n_embd)
self.dropout = nn.Dropout(config.dropout)
def forward(self, x):
B, T, C = x.size()
q, k, v = self.c_attn(x).split(self.n_embd, dim=2)
k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
att = (q @ k.transpose(-2, -1)) * (1.0 / np.sqrt(k.size(-1)))
mask = torch.tril(torch.ones(T, T, device=x.device)).view(1, 1, T, T)
att = att.masked_fill(mask == 0, float('-inf'))
att = F.softmax(att, dim=-1)
y = (att @ v).transpose(1, 2).contiguous().view(B, T, C)
return self.dropout(self.c_proj(y))
class Block(nn.Module):
def __init__(self, config):
super().__init__()
self.ln_1 = nn.LayerNorm(config.n_embd)
self.attn = CausalSelfAttention(config)
self.ln_2 = nn.LayerNorm(config.n_embd)
self.mlp = nn.Sequential(
nn.Linear(config.n_embd, 4 * config.n_embd),
nn.GELU(),
nn.Linear(4 * config.n_embd, config.n_embd),
nn.Dropout(config.dropout),
)
def forward(self, x):
x = x + self.attn(self.ln_1(x))
x = x + self.mlp(self.ln_2(x))
return x
class PhiModel(nn.Module):
def __init__(self, config):
super().__init__()
self.transformer = nn.ModuleDict({
'wte': nn.Embedding(config.vocab_size, config.n_embd),
'wpe': nn.Embedding(config.block_size, config.n_embd),
'h': nn.Sequential(*(Block(config) for _ in range(config.n_layer))),
'ln_f': nn.LayerNorm(config.n_embd),
})
self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
def forward(self, idx):
device = idx.device
b, t = idx.size()
pos = torch.arange(0, t, dtype=torch.long, device=device).unsqueeze(0)
x = self.transformer.wte(idx) + self.transformer.wpe(pos)
x = self.transformer.ln_f(self.transformer.h(x))
return self.lm_head(x)
class TextbookDataset(Dataset):
"""Simulates high-signal, structured 'textbook' data."""
def __init__(self, num_samples=1000, seq_len=32):
self.num_samples = num_samples
self.seq_len = seq_len
self.patterns = [[i for i in range(10)], [i for i in range(10, 0, -1)], [i % 5 for i in range(10)]]
def __len__(self): return self.num_samples
def __getitem__(self, idx):
pattern = self.patterns[idx % len(self.patterns)]
seq = (pattern * (self.seq_len // len(pattern) + 1))[:self.seq_len]
x = torch.tensor(seq, dtype=torch.long)
return x, torch.roll(x, -1)
# Training Execution
config = PhiConfig()
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
dataset = TextbookDataset(num_samples=2000)
dataloader = DataLoader(dataset, batch_size=config.batch_size, shuffle=True)
model = PhiModel(config).to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=config.learning_rate)
criterion = nn.CrossEntropyLoss()
model.train()
for epoch in range(config.epochs):
for x, y in dataloader:
x, y = x.to(device), y.to(device)
optimizer.zero_grad()
loss = criterion(model(x).view(-1, config.vocab_size), y.view(-1))
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1}/{config.epochs} complete.")
Key Takeaways for Engineers
The success of phi-1 provides three critical lessons for anyone building AI systems:
- The "Data Flywheel" is Real: Spending 80% of your time on data curation and 20% on architecture often yields better results than the inverse.
- Synthetic Data is a First-Class Citizen: When high-quality human data is scarce, using a larger model (like GPT-4) to generate "textbooks" for a smaller model is a viable and powerful distillation strategy.
- Small Models are Capable: We often overestimate the need for billions of parameters. If the signal-to-noise ratio of the training data is high enough, compact models can achieve state-of-the-art reasoning.
Final Thought: In the race to AGI, we've spent years focusing on the size of the "brain." Phi-1 reminds us that the quality of the "education" is what actually determines intelligence.