Part of Language AI Handbook
Examines Microsoft's Phi model family and how textbook-quality training data enables small models to match larger competitors.
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Phi Models
What if smaller models could match larger ones? The Phi series from Microsoft Research challenged the prevailing "scale is all you need" paradigm by showing that carefully curated training data matters as much as, or more than, model size. A 1.3 billion parameter model matching GPT-3.5 on certain benchmarks seemed implausible until Phi proved otherwise.
The Phi models represent a philosophical shift in language model development. Rather than training on all available web text and hoping scale will overcome noise, the Phi team focused obsessively on data quality. They synthesized "textbook-quality" training data using larger models, creating datasets designed to teach reasoning and coding alongside world knowledge. This approach treats model training as curriculum design rather than data accumulation.
This chapter explores the Phi model family from Phi-1 through Phi-3. You'll learn about the textbook-quality data hypothesis, understand the architectural choices that complement high-quality training, and see how small language models can achieve surprising capabilities when trained thoughtfully.
The Small Model Challenge
Before Phi, the path to better language models seemed clear: make them bigger. GPT-2 (1.5B parameters) improved on GPT (117M). GPT-3 (175B) improved on GPT-2. Each generation scaled up by roughly 10x, and performance followed. This pattern suggested that capability and size were fundamentally linked.
Scaling laws describe the predictable relationship between model size, training data, and compute on one hand, and model performance on the other. Research by Kaplan et al. (2020) and Hoffmann et al. (2022) showed that loss decreases as a power law:
where:
- : the model's loss (lower is better)
- : the number of model parameters
- : the number of training tokens
- : empirically determined exponents (typically around 0.05-0.1)
This relationship suggests that larger models are reliably better given sufficient data and compute.

The Phi models consistently achieve lower loss than standard scaling laws predict. This shows that data quality can substitute for model size.
But scaling has costs. Training GPT-4 reportedly cost over $100 million. Running inference on 175B parameter models requires specialized hardware. The environmental footprint of training and deploying such models is substantial. If smaller models could achieve similar capabilities, the practical benefits would be enormous.
The Phi project asked a contrarian question: what if training data quality, rather than sheer quantity, is the primary bottleneck? Web-scraped datasets contain enormous amounts of low-quality content: SEO spam, machine-generated text, forum noise, and redundant information. What if we replaced this with carefully crafted educational content?
# Compare model sizes in the Phi family vs contemporaries
phi_models = {
"Phi-1 (2023)": {"params": 1.3, "training_tokens": 7, "focus": "Code"},
"Phi-1.5 (2023)": {
"params": 1.3,
"training_tokens": 30,
"focus": "Reasoning",
},
"Phi-2 (2023)": {"params": 2.7, "training_tokens": 250, "focus": "General"},
"Phi-3-mini (2024)": {
"params": 3.8,
"training_tokens": 3300,
"focus": "General",
},
}
comparison_models = {
"GPT-3.5": {"params": 175, "training_tokens": 300, "focus": "General"},
"LLaMA-2-7B": {"params": 7, "training_tokens": 2000, "focus": "General"},
"Mistral-7B": {"params": 7, "training_tokens": 8000, "focus": "General"},
"LLaMA-2-70B": {"params": 70, "training_tokens": 2000, "focus": "General"},
}Phi Model Family ================================================================= Model Params (B) Tokens (B) Focus ----------------------------------------------------------------- Phi-1 (2023) 1.3 7 Code Phi-1.5 (2023) 1.3 30 Reasoning Phi-2 (2023) 2.7 250 General Phi-3-mini (2024) 3.8 3300 General Comparison Models ----------------------------------------------------------------- GPT-3.5 175.0 300 General LLaMA-2-7B 7.0 2000 General Mistral-7B 7.0 8000 General LLaMA-2-70B 70.0 2000 General
The table reveals the dramatic size difference between Phi models and their competitors. Phi-1 uses only 7B training tokens compared to LLaMA-2's 2,000B, yet achieves competitive performance. This 280x difference in training data volume shows how strongly data quality affects the result.
The parameter counts tell a striking story. Phi-1 with 1.3B parameters competed with models 10-100x larger on coding tasks. Phi-2 at 2.7B matched LLaMA-2-7B and approached LLaMA-2-70B on reasoning benchmarks. How is this possible?
Phi-1: Textbooks Are All You Need
The first Phi model focused on code generation. The team hypothesized that the quality of training data for coding tasks could be dramatically improved by generating synthetic "textbook" content rather than relying on scraped code from GitHub.
The Textbook-Quality Data Hypothesis
Real-world code has problems as training data:
- Noise: Comments may be wrong, outdated, or missing entirely
- Inconsistency: Different projects use different styles and patterns
- Complexity: Production code often handles edge cases that obscure core concepts
- Lack of explanation: Code exists without pedagogical context
A programming textbook, by contrast, introduces concepts systematically, provides clear explanations, uses consistent style, and builds complexity gradually. The Phi team used GPT-3.5 to generate synthetic textbook content covering Python programming concepts.
# Example of the difference between web-scraped vs textbook-style code
# Web-scraped style (from real repositories)
webscrape_example = """
def proc(lst, k=None):
# TODO: refactor
res = []
for i, x in enumerate(lst):
if k and i >= k:
break
if x is not None and str(x).strip():
res.append(x.lower() if hasattr(x, "lower") else x)
return res or None # legacy compat
"""
# Textbook style (synthetic, educational)
textbook_example = '''
def filter_and_normalize(items, limit=None):
"""
Filter out empty items and normalize strings to lowercase.
This function demonstrates two common list operations:
1. Filtering: removing None and empty string values
2. Normalization: converting strings to lowercase
Args:
items: A list of items to process
limit: Optional maximum number of items to return
Returns:
A list of filtered and normalized items
Example:
>>> filter_and_normalize(["Hello", None, "WORLD", ""])
["hello", "world"]
"""
result = []
for item in items:
# Skip None values
if item is None:
continue
# Skip empty strings (after stripping whitespace)
if isinstance(item, str) and not item.strip():
continue
# Normalize strings to lowercase
if isinstance(item, str):
result.append(item.lower())
else:
result.append(item)
# Stop if we have reached the limit
if limit is not None and len(result) >= limit:
break
return result
'''Web-Scraped Code vs Textbook-Quality Code
============================================================
--- Web-Scraped Style ---
def proc(lst, k=None):
# TODO: refactor
res = []
for i, x in enumerate(lst):
if k and i >= k:
break
if x is not None and str(x).strip():
res.append(x.lower() if hasattr(x, "lower") else x)
return res or None # legacy compat
--- Textbook Style ---
def filter_and_normalize(items, limit=None):
"""
Filter out empty items and normalize strings to lowercase.
This function demonstrates two common list operations:
1. Filtering: removing None and empty string values
2. Normalization: converting strings to lowercase
Args:
items: A list of items to process
limit: Optional maximum number of items to return
Returns:
A list of filtered and normalized items
Example:
filter_and_normalize(["Hello", None, "WORLD", ""])
["hello", "world"]
"""
result = []
for item in items:
# Skip None values
if item is None:
continue
# Skip empty strings (after stripping whitespace)
if isinstance(item, str) and not item.strip():
continue
# Normalize strings to lowercase
if isinstance(item, str):
result.append(item.lower())
else:
result.append(item)
# Stop if we have reached the limit
if limit is not None and len(result) >= limit:
break
return resultThe textbook-style code is longer, but every line serves an educational purpose. Variable names are descriptive. Comments explain why the code works instead of merely describing what it does. The docstring includes a concrete example. This is the kind of content that teaches programming concepts effectively.
Phi-1 Training Data Composition
Phi-1's training data combined three sources:
- Filtered Code: About 6B tokens of web code filtered for quality
- Synthetic Textbooks: 1B tokens of GPT-3.5 generated educational content
- Synthetic Exercises: Code exercises with solutions, also generated
The filtering process was aggressive. From a large corpus of Python code, only about 20% passed quality filters based on educational value metrics. The synthetic data was generated with careful prompting to produce content resembling high-quality textbooks and courses.
# Phi-1 training data breakdown
phi1_data = {
"Filtered Web Code": 6.0,
"Synthetic Textbooks": 1.0,
"Synthetic Exercises": 0.2,
}
total_tokens = sum(phi1_data.values())
Phi-1 Results
Phi-1's performance surprised the research community. On HumanEval, a benchmark for Python code generation, Phi-1 achieved 50.6% pass@1, comparable to models 10x its size.
# HumanEval benchmark comparison
humaneval_results = {
"Phi-1 (1.3B)": 50.6,
"StarCoder (15B)": 33.6,
"CodeGen-16B": 29.3,
"GPT-3.5": 48.1,
"code-davinci-002": 47.0,
}
This result validated the core hypothesis: training data quality can substitute for model scale. A small model trained on carefully curated educational content matched or exceeded much larger models trained on raw web data.
Phi-1.5: Extending to Reasoning
Building on Phi-1's success, the team created Phi-1.5 to explore whether the textbook-quality approach generalized beyond coding. Phi-1.5 maintained the 1.3B parameter size but expanded the synthetic data to cover common-sense reasoning and general knowledge.
Synthetic Data for Reasoning
Generating high-quality reasoning data is more challenging than code. Code has clear correctness criteria: it either runs or it doesn't. Reasoning depends on context and nuance, with some judgments remaining subjective. The Phi-1.5 team developed prompting strategies to generate textbook-like content for topics including:
- Science explanations
- Mathematical reasoning
- Common-sense scenarios
- Logical deduction
# Example prompt structure for generating reasoning data
reasoning_prompt_template = """
Write a short educational passage that explains the following concept
in a clear, textbook-like style. Include a concrete example and
explain the reasoning step by step.
Concept: {concept}
The passage should:
1. Introduce the concept clearly
2. Provide a relatable example
3. Walk through the reasoning explicitly
4. Summarize the key insight
"""
example_concepts = [
"Why ice floats on water",
"How compound interest works",
"Why the sky appears blue",
"The difference between correlation and causation",
]Example Concepts for Synthetic Reasoning Data ================================================== 1. Why ice floats on water 2. How compound interest works 3. Why the sky appears blue 4. The difference between correlation and causation
The resulting dataset contained approximately 20B tokens of synthetic textbook content covering diverse reasoning domains, plus 10B tokens of filtered web data selected for educational quality.
Phi-1.5 Architecture
Phi-1.5 used a standard transformer decoder architecture with some notable choices:
- 24 layers: Relatively deep for a 1.3B model
- 2048 hidden dimension: Standard width
- 32 attention heads: With and 32 heads, each head operates on dimensions
- Rotary positional embeddings (RoPE): Same as LLaMA for better length generalization
- Flash Attention: For efficient training
phi15_config = {
"Parameters": "1.3B",
"Layers": 24,
"Hidden Size": 2048,
"Attention Heads": 32,
"Head Dimension": 64,
"Vocabulary Size": 51200,
"Context Length": 2048,
"Positional Encoding": "RoPE",
"Activation": "GELU",
}Phi-1.5 Architecture Configuration ============================================= Parameters 1.3B Layers 24 Hidden Size 2048 Attention Heads 32 Head Dimension 64 Vocabulary Size 51200 Context Length 2048 Positional Encoding RoPE Activation GELU
The configuration shows a relatively deep network (24 layers) for a 1.3B parameter model, with standard choices for hidden size and attention heads. The use of RoPE positional encodings matches LLaMA, letting better length generalization than absolute positional embeddings.
The architecture itself was not novel. The innovation was entirely in the training data. This strengthens the textbook-quality hypothesis: the same architecture performs dramatically differently depending on what it's trained on.
Phi-2: Scaling Quality
Phi-2 doubled the parameter count to 2.7B and scaled up the synthetic data pipeline. The model demonstrated that the textbook-quality approach continues to provide benefits at larger scales, maintaining competitive performance against models 10-25x its size.
Training Data Strategy
Phi-2's training combined multiple data sources:
- Synthetic textbooks: Expanded coverage of STEM topics and coding, together with reasoning
- Web data: Heavily filtered for educational content using NLP classifiers
- Code: High-quality repositories with documentation
The total training corpus was approximately 250B tokens, still modest compared to LLaMA-2's 2T tokens or GPT-3's 300B tokens, but dramatically higher quality according to Microsoft's metrics.
# Training efficiency comparison (comparable-size models: ≤7B params + Mistral)
training_efficiency = {
"Phi-2": {"params": 2.7, "tokens": 250, "tokens_per_param": 92.6},
"LLaMA-2-7B": {"params": 7, "tokens": 2000, "tokens_per_param": 285.7},
"LLaMA-2-13B": {"params": 13, "tokens": 2000, "tokens_per_param": 153.8},
"Mistral-7B": {"params": 7, "tokens": 8000, "tokens_per_param": 1142.9},
}
Benchmark Performance
Phi-2 achieved remarkable results across diverse benchmarks, often matching or exceeding models with 7-25x more parameters.
# Multi-benchmark comparison
benchmarks = {
"MMLU": {
"Phi-2 (2.7B)": 56.3,
"Mistral-7B": 60.1,
"LLaMA-2-7B": 45.3,
"LLaMA-2-13B": 54.8,
"LLaMA-2-70B": 68.9,
},
"GSM8K (Math)": {
"Phi-2 (2.7B)": 57.2,
"Mistral-7B": 37.8,
"LLaMA-2-7B": 14.6,
"LLaMA-2-13B": 28.7,
"LLaMA-2-70B": 56.8,
},
"HumanEval (Code)": {
"Phi-2 (2.7B)": 59.0,
"Mistral-7B": 30.5,
"LLaMA-2-7B": 12.8,
"LLaMA-2-13B": 18.3,
"LLaMA-2-70B": 29.9,
},
"MBPP (Code)": {
"Phi-2 (2.7B)": 60.6,
"Mistral-7B": 47.5,
"LLaMA-2-7B": 20.8,
"LLaMA-2-13B": 30.8,
"LLaMA-2-70B": 49.8,
},
}
The results reveal an interesting pattern. Phi-2 excels particularly on GSM8K (mathematical reasoning) and code generation, precisely the domains where textbook-quality data provides the clearest advantage. On MMLU (general knowledge), larger models maintain an edge, suggesting that some types of knowledge benefit more from scale than from data quality.

Phi-3: Quality Meets Scale
Phi-3, released in 2024, represents the maturation of the textbook-quality approach. With 3.8B parameters in the mini variant, it was trained on an unprecedented 3.3 trillion tokens of heavily filtered and synthetic data. Phi-3 closed the gap with much larger models across nearly all benchmarks.
Data Pipeline Innovations
The Phi-3 training pipeline incorporated several innovations:
- Multi-stage filtering: Multiple rounds of quality filtering using trained classifiers
- Diverse synthetic generation: Using multiple large models (not just GPT-4) to generate diverse content
- Domain coverage expansion: Systematic coverage of academic subjects, professional domains, and world knowledge
- Instruction tuning: Post-training alignment using synthetic instruction-response pairs
# Phi-3 training stages
phi3_stages = {
"Stage 1: Web Filtering": {
"input": "Raw web data",
"process": "Quality classifiers, deduplication",
"output": "~1T high-quality tokens",
},
"Stage 2: Synthetic Generation": {
"input": "Topic outlines, skill maps",
"process": "Multi-model generation, verification",
"output": "~500B synthetic tokens",
},
"Stage 3: Data Mixing": {
"input": "All filtered and synthetic data",
"process": "Curriculum-based mixing, repetition tuning",
"output": "3.3T training tokens",
},
"Stage 4: Instruction Tuning": {
"input": "Pre-trained model",
"process": "SFT + DPO alignment",
"output": "Phi-3-instruct variants",
},
}Phi-3 Training Pipeline ================================================================= Stage 1: Web Filtering ---------------------------------------- Input: Raw web data Process: Quality classifiers, deduplication Output: ~1T high-quality tokens Stage 2: Synthetic Generation ---------------------------------------- Input: Topic outlines, skill maps Process: Multi-model generation, verification Output: ~500B synthetic tokens Stage 3: Data Mixing ---------------------------------------- Input: All filtered and synthetic data Process: Curriculum-based mixing, repetition tuning Output: 3.3T training tokens Stage 4: Instruction Tuning ---------------------------------------- Input: Pre-trained model Process: SFT + DPO alignment Output: Phi-3-instruct variants
Phi-3 Model Variants
Phi-3 comes in multiple sizes to suit different deployment scenarios:
phi3_variants = {
"Phi-3-mini": {
"params": 3.8,
"context": 128000,
"target": "Edge devices, mobile",
},
"Phi-3-small": {
"params": 7,
"context": 128000,
"target": "Balanced performance",
},
"Phi-3-medium": {
"params": 14,
"context": 128000,
"target": "High performance",
},
}Phi-3 Model Variants ======================================================= Variant Parameters Context Target Use ------------------------------------------------------- Phi-3-mini 3.8 B 128,000 Edge devices, mobile Phi-3-small 7.0 B 128,000 Balanced performance Phi-3-medium 14.0 B 128,000 High performance

The 128K context length is notable, achieved through RoPE scaling techniques. Standard RoPE is trained for a fixed context length, but the frequencies can be adjusted to extrapolate to longer sequences. Phi-3 uses a technique where the base frequency is modified:
where is a scaling factor that stretches the rotational frequencies, letting the model to handle longer sequences than it was originally trained on. This allows Phi-3 models to process long documents, complete codebases, or extended conversations while maintaining their small footprint.
Phi-3 Performance
Phi-3-mini matches or exceeds much larger models on key benchmarks:
phi3_comparison = {
"MMLU": {
"Phi-3-mini (3.8B)": 68.8,
"Mixtral-8x7B (47B)": 70.6,
"GPT-3.5-turbo": 70.0,
"LLaMA-3-8B": 66.6,
},
"GSM8K": {
"Phi-3-mini (3.8B)": 82.5,
"Mixtral-8x7B (47B)": 74.4,
"GPT-3.5-turbo": 57.1,
"LLaMA-3-8B": 79.6,
},
"HumanEval": {
"Phi-3-mini (3.8B)": 58.5,
"Mixtral-8x7B (47B)": 40.2,
"GPT-3.5-turbo": 48.1,
"LLaMA-3-8B": 62.2,
},
}
Code Implementation: Phi-Style Attention
To truly understand how Phi models work, we need to examine their core computational mechanism: multi-head attention with rotary positional embeddings (RoPE). While the architecture is standard, implementing it from scratch reveals the elegant mathematics that enables transformers to process sequences.
Our implementation journey proceeds through three stages:
- Positional encoding: How RoPE embeds position information through rotation
- Attention computation: How QKV tensors interact to produce contextual representations
- Integration: How these components combine in a complete attention layer
Understanding Rotary Position Embeddings
Before diving into code, let's build intuition for why positional encoding matters. A transformer's attention mechanism computes similarity between all pairs of tokens, but this computation is inherently position-agnostic. The same query-key pair produces the same similarity score regardless of where the tokens appear in the sequence. Yet position clearly matters: "The cat sat on the mat" has different meaning than "mat the on sat cat The."
Traditional approaches add positional embeddings to token embeddings before attention. RoPE takes a different approach: it encodes position by rotating the query and key vectors in embedding space. The key insight is that rotation preserves vector magnitudes while changing the angle between vectors. When we compute the dot product (attention score) between a rotated query and key, the result depends on their relative positions, not just their content.
Think of it geometrically: if we rotate a query vector by angle and a key vector by angle , their dot product depends on the difference . By making these rotation angles position-dependent, we encode relative position directly into the attention computation.
import numpy as np
def apply_rotary_embeddings(x, cos, sin):
"""
Apply rotary positional embeddings to input tensor.
RoPE rotates pairs of dimensions in the embedding space by
position-dependent angles, letting the model to encode
relative positions through the attention dot product.
Args:
x: Input tensor of shape (batch, heads, seq_len, head_dim)
cos: Cosine of rotation angles (seq_len, head_dim)
sin: Sine of rotation angles (seq_len, head_dim)
Returns:
Rotated tensor of same shape as input
"""
# Split into pairs for rotation
x1 = x[..., ::2] # Even indices
x2 = x[..., 1::2] # Odd indices
# Reshape cos/sin for broadcasting
cos = cos[np.newaxis, np.newaxis, :, :]
sin = sin[np.newaxis, np.newaxis, :, :]
# Apply rotation
rotated_x1 = x1 * cos - x2 * sin
rotated_x2 = x1 * sin + x2 * cos
# Interleave back together
result = np.empty_like(x)
result[..., ::2] = rotated_x1
result[..., 1::2] = rotated_x2
return result
def compute_rope_frequencies(head_dim, max_seq_len, base=10000):
"""
Compute RoPE frequency components.
These frequencies determine how quickly each dimension
rotates as position increases, with lower-index dimensions
rotating more slowly to capture longer-range patterns.
"""
# Frequency for each dimension pair
dim_indices = np.arange(0, head_dim, 2)
freqs = 1.0 / (base ** (dim_indices / head_dim))
# Position indices
positions = np.arange(max_seq_len)
# Compute angles: (seq_len, head_dim/2)
angles = np.outer(positions, freqs)
# Expand to full head_dim by repeating
cos = np.cos(angles)
sin = np.sin(angles)
return cos, sinThe Mathematics of Rotation
RoPE applies a 2D rotation to each pair of consecutive dimensions in the embedding. A 2D rotation by angle transforms a vector to:
This is exactly what our apply_rotary_embeddings function computes: for each pair of dimensions, it applies the rotation using the precomputed cosine and sine values.

The rotation angle for position and dimension pair is computed as:
where:
- : the position index in the sequence (0, 1, 2, ...)
- : the dimension pair index (0, 1, 2, ..., )
- : the head dimension (e.g., 64)
- : a hyperparameter controlling the frequency scale (typically 10,000)
Lower-indexed dimensions rotate more slowly (smaller ), capturing longer-range positional relationships, while higher-indexed dimensions rotate faster, encoding fine-grained local position information.
This frequency hierarchy is important: it allows the model to represent both local patterns (through high-frequency dimensions) and long-range dependencies (through low-frequency dimensions) simultaneously. Let's see this in practice:
RoPE Frequency Computation ================================================== Head dimension: 64 Sequence length: 8 Cosine shape: (8, 32) Sine shape: (8, 32) First 4 positions, first 4 frequency components: Cosine values: [[ 1. 1. 1. 1. ] [ 0.54 0.732 0.846 0.912] [-0.416 0.071 0.431 0.665] [-0.99 -0.628 -0.116 0.301]] Sine values: [[0. 0. 0. 0. ] [0.841 0.682 0.533 0.409] [0.909 0.997 0.902 0.747] [0.141 0.778 0.993 0.954]]
The output shows how RoPE frequencies vary across positions and dimensions. Notice that the first column (lowest frequency) changes slowly across positions, while higher-frequency components (rightmost columns) oscillate more rapidly. At position 0, all sine values are 0 and cosine values are 1, meaning no rotation occurs. As position increases, the rotation angles grow, with higher-indexed dimensions accumulating rotation faster.

From Position Encoding to Attention
With position encoding understood, we can now build the complete attention mechanism. The core idea of attention is simple: each position in the sequence should be able to gather information from other positions, weighted by relevance. A query asks "what information do I need?", keys answer "here's what I have", and values provide the actual information to aggregate.
The mathematical formulation captures this intuition precisely. The core attention computation follows the standard scaled dot-product formula:
where:
- : the query matrix, where each row represents a position asking "what should I attend to?"
- : the key matrix, where each row represents a position answering "here's what I contain"
- : the value matrix, containing the actual information to be aggregated
- : the key/query dimension (typically 64 in Phi models)
- : the sequence length
- : scaling factor that prevents dot products from becoming too large
The softmax normalizes attention scores so each query position's weights sum to 1, creating a valid probability distribution over key positions.
Why divide by ? As the dimension grows, dot products tend to grow in magnitude (they're sums of terms). Large dot products push softmax into regions where gradients vanish, making training unstable. The scaling factor keeps the variance of dot products roughly constant regardless of .
Putting It All Together: The PhiAttention Class
Now we can combine RoPE with scaled dot-product attention in a complete implementation. The PhiAttention class orchestrates the full computation:
- Project input to QKV tensors using learned weight matrices
- Reshape to separate attention heads (each head attends independently)
- Apply RoPE to queries and keys (encoding position)
- Compute scaled dot-product attention
- Apply causal mask for autoregressive generation
- Project output back to model dimension
class PhiAttention:
"""
Multi-head attention as used in Phi models.
Combines standard scaled dot-product attention with:
- Rotary positional embeddings (RoPE)
- Pre-normalization
- Flash Attention compatibility (not implemented here)
"""
def __init__(self, hidden_size=2048, num_heads=32, head_dim=64, seed=42):
"""
Initialize attention weights.
Args:
hidden_size: Model hidden dimension
num_heads: Number of attention heads
head_dim: Dimension per attention head
seed: Random seed for initialization
"""
self.hidden_size = hidden_size
self.num_heads = num_heads
self.head_dim = head_dim
np.random.seed(seed)
# Phi uses small initialization scale
scale = 0.02
# Query, Key, Value projections
self.W_q = np.random.randn(hidden_size, num_heads * head_dim) * scale
self.W_k = np.random.randn(hidden_size, num_heads * head_dim) * scale
self.W_v = np.random.randn(hidden_size, num_heads * head_dim) * scale
# Output projection
self.W_o = np.random.randn(num_heads * head_dim, hidden_size) * scale
# Pre-compute RoPE frequencies for max sequence length
self.max_seq_len = 2048
self.cos, self.sin = compute_rope_frequencies(
head_dim // 2 * 2, # Ensure even
self.max_seq_len,
)
def forward(self, x, mask=None):
"""
Compute attention output.
Args:
x: Input tensor (batch, seq_len, hidden_size)
mask: Optional causal mask (seq_len, seq_len)
Returns:
output: Attention output (batch, seq_len, hidden_size)
weights: Attention weights (batch, heads, seq_len, seq_len)
"""
batch, seq_len, _ = x.shape
# Project to Q, K, V
Q = x @ self.W_q # (batch, seq_len, num_heads * head_dim)
K = x @ self.W_k
V = x @ self.W_v
# Reshape to (batch, num_heads, seq_len, head_dim)
Q = Q.reshape(batch, seq_len, self.num_heads, self.head_dim)
Q = Q.transpose(0, 2, 1, 3)
K = K.reshape(batch, seq_len, self.num_heads, self.head_dim)
K = K.transpose(0, 2, 1, 3)
V = V.reshape(batch, seq_len, self.num_heads, self.head_dim)
V = V.transpose(0, 2, 1, 3)
# Apply RoPE to Q and K
cos_slice = self.cos[:seq_len]
sin_slice = self.sin[:seq_len]
Q = apply_rotary_embeddings(Q, cos_slice, sin_slice)
K = apply_rotary_embeddings(K, cos_slice, sin_slice)
# Compute attention scores using scaled dot-product attention
# The scaling factor sqrt(d_k) prevents dot products from growing
# too large as the head dimension increases
scale = np.sqrt(self.head_dim)
scores = (Q @ K.transpose(0, 1, 3, 2)) / scale
# Apply causal mask if provided (set masked positions to -inf)
if mask is not None:
scores = np.where(mask, scores, -1e9)
# Softmax converts scores to attention weights that sum to 1
exp_scores = np.exp(scores - scores.max(axis=-1, keepdims=True))
weights = exp_scores / exp_scores.sum(axis=-1, keepdims=True)
# Weighted sum of values produces the attention output
output = weights @ V # (batch, heads, seq_len, head_dim)
# Reshape back
output = output.transpose(
0, 2, 1, 3
) # (batch, seq_len, heads, head_dim)
output = output.reshape(batch, seq_len, self.num_heads * self.head_dim)
# Output projection
output = output @ self.W_o
return output, weightsThe implementation follows the mathematical formulation closely. Notice how the forward method mirrors our six-step process: projection and reshaping, RoPE application, attention computation, masking, then output projection.
Key implementation details:
- Small initialization scale (0.02): Phi uses smaller weight initialization than some models, which can help training stability
- Pre-computed RoPE frequencies: We compute cosine and sine values once and slice them per sequence length, avoiding redundant computation
- Causal masking: The mask ensures position can only attend to positions , letting autoregressive generation
Testing the Implementation
Let's verify our attention layer works correctly by processing a small sequence and examining the outputs:
# Test the attention implementation
batch_size = 1
seq_len = 10
hidden_size = 256
num_heads = 4
head_dim = 64
# Create deterministic test input (seeded once in the shared setup cell)
x = np.random.randn(batch_size, seq_len, hidden_size)
# Create causal mask
causal_mask = np.tril(np.ones((seq_len, seq_len), dtype=bool))
# Initialize and run attention
attention = PhiAttention(
hidden_size=hidden_size, num_heads=num_heads, head_dim=head_dim, seed=17
)
output, weights = attention.forward(x, mask=causal_mask)Phi-Style Attention Test ================================================== Input shape: (1, 10, 256) Output shape: (1, 10, 256) Weights shape: (1, 4, 10, 10) Attention weights sum per query (should be 1.0): [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
The output confirms our implementation is working correctly. The input and output shapes match (preserving dimensions through the attention layer), and each row of attention weights sums to exactly 1.0, showing proper softmax normalization. The weights tensor has shape (1, 4, 6, 6), representing 4 attention heads each creating a 6x6 attention matrix for the 6-position sequence.
Visualizing Attention Patterns
The causal mask creates a distinctive lower-triangular pattern in the attention weights. This pattern is basic to autoregressive language modeling: when predicting position 5, the model can only see positions 0-5, not future positions 6 and beyond. Let's visualize this:


Understanding Phi's Efficiency
Why can Phi models achieve competitive performance with fewer parameters? Several factors contribute:
Data Efficiency
The textbook-quality approach maximizes learning per token. Instead of exposing the model to repetitive or low-quality content, every training example teaches something. This is analogous to the difference between learning from a well-designed curriculum versus random exposure to information.

High-quality data allows Phi models to achieve comparable performance to much larger models while using significantly fewer training tokens. The key insight is that learning per token can vary by an order of magnitude depending on data quality.
Capacity Utilization
Large models trained on noisy data may dedicate significant capacity to memorizing spurious patterns, formatting quirks, or redundant information. A smaller model trained on cleaner data can allocate more of its limited capacity to task-relevant patterns.
Reasoning Focus
Phi's training data emphasizes step-by-step reasoning, code with explanations, and mathematical derivations. This may help the model develop stronger reasoning circuits compared to models that see mostly surface-level text patterns.
Limitations and Considerations
Despite their impressive efficiency, Phi models have limitations worth understanding.
Knowledge breadth: While Phi excels at reasoning and code, models trained on more diverse web data may have broader world knowledge. A larger model might know more obscure facts simply because it has seen more text. Phi's focus on high-quality synthetic data means it may miss niche domains that weren't explicitly covered in the curriculum.
Long-tail capabilities: The textbook-quality approach works well for structured domains like programming and mathematics where educational content is well-defined. For more open-ended creative tasks or understanding of cultural nuance, the synthetic data generation process may not capture the full richness needed.
Synthetic data limitations: While synthetic data can be high-quality, it ultimately reflects the capabilities and biases of the models that generated it. If the teacher models (like GPT-3.5 or GPT-4) have systematic blind spots, these may propagate to Phi. There's also a risk of "mode collapse" where synthetic data becomes repetitive or stylistically narrow.
Benchmark saturation: As Phi models achieve strong benchmark performance, questions arise about whether benchmarks truly measure the capabilities we care about. A model optimized for textbook-style reasoning might excel at clean benchmark problems while struggling with messy real-world inputs.
# Areas where larger models may still have advantages
tradeoffs = {
"Phi Advantages": [
"Mathematical reasoning",
"Code generation",
"Step-by-step explanations",
"Inference efficiency",
"Edge deployment",
],
"Large Model Advantages": [
"Broad world knowledge",
"Rare/niche topics",
"Creative writing",
"Cultural nuance",
"Few-shot learning on new tasks",
],
}Phi vs Large Models: Tradeoffs ======================================================= Phi Advantages: • Mathematical reasoning • Code generation • Step-by-step explanations • Inference efficiency • Edge deployment Large Model Advantages: • Broad world knowledge • Rare/niche topics • Creative writing • Cultural nuance • Few-shot learning on new tasks
Deployment and Practical Use
Phi models are designed for efficient deployment. Their small size enables scenarios that would be impractical with larger models.
Edge deployment: Phi-3-mini can run on smartphones and laptops without cloud connectivity. This enables privacy-preserving applications where user data never leaves the device.
Quantization: The small parameter count makes quantization more effective. Quantization reduces memory by representing weights with fewer bits. For a model with parameters, the memory requirement scales as:
where:
- : the number of model parameters
- : the number of bits per parameter (e.g., 16 for FP16, 4 for INT4)
A 4-bit quantized Phi-3-mini (3.8B parameters) requires approximately of memory while maintaining most of its capabilities.
Batched inference: The smaller memory footprint allows processing more requests simultaneously on a single GPU, reducing per-query costs.
Fine-tuning: With fewer parameters, fine-tuning Phi models requires less compute and memory, making customization accessible to more users.
# Deployment scenarios
deployment_specs = {
"Phi-3-mini (FP16)": {
"memory_gb": 7.6,
"smartphone": False,
"laptop_gpu": True,
"server_gpu": True,
},
"Phi-3-mini (INT4)": {
"memory_gb": 2.0,
"smartphone": True,
"laptop_gpu": True,
"server_gpu": True,
},
"LLaMA-2-7B (FP16)": {
"memory_gb": 14,
"smartphone": False,
"laptop_gpu": False,
"server_gpu": True,
},
"LLaMA-2-70B (FP16)": {
"memory_gb": 140,
"smartphone": False,
"laptop_gpu": False,
"server_gpu": True,
},
}Deployment Feasibility by Platform ================================================================= Model Memory Phone Laptop Server ----------------------------------------------------------------- Phi-3-mini (FP16) 7.6 GB ✗ ✓ ✓ Phi-3-mini (INT4) 2.0 GB ✓ ✓ ✓ LLaMA-2-7B (FP16) 14.0 GB ✗ ✗ ✓ LLaMA-2-70B (FP16) 140.0 GB ✗ ✗ ✓

Summary
The Phi model family demonstrates that data quality can substitute for model scale. By training on carefully curated "textbook-quality" data, Phi models achieve remarkable efficiency.
The key innovations and insights from this chapter:
-
Textbook-quality hypothesis: Carefully curated educational content enables more efficient learning than raw web data. Quality trumps quantity when data is engineered for pedagogical value.
-
Synthetic data generation: Using larger models to generate structured, educational training data creates a new paradigm for dataset construction. The resulting data can teach concepts more effectively than naturally-occurring text.
-
Evolution across versions: Phi-1 proved the concept for code, Phi-1.5 extended it to reasoning, Phi-2 scaled the approach, and Phi-3 achieved near-parity with models 10-25x larger.
-
Architecture simplicity: Phi uses standard transformer architecture with RoPE and Flash Attention. The innovation is entirely in the training data, validating that architecture alone doesn't determine capability.
-
Deployment efficiency: Small parameter counts enable edge deployment, efficient quantization, and lower inference costs. Phi-3-mini can run on devices where larger models are impractical.
-
Tradeoffs: Phi models excel at structured reasoning tasks but may have narrower knowledge coverage than larger models trained on diverse web data. The synthetic data approach has inherent limitations around diversity and coverage.
The Phi series challenges us to think differently about model development. Rather than assuming that scale is the only path to capability, the textbook-quality approach suggests that thoughtful data curation may be equally important. As the field matures, we may see convergence between these strategies: large-scale training on carefully filtered and augmented data that combines the benefits of both approaches.
Key Parameters
When working with or fine-tuning Phi models, the following parameters have the most significant impact:
Model Selection
- Model variant: Choose Phi-3-mini (3.8B) for edge deployment and mobile, Phi-3-small (7B) for balanced performance, or Phi-3-medium (14B) for maximum capability. The mini variant offers the best efficiency-to-capability ratio for most use cases.
Inference Configuration
- Context length: Phi-3 supports up to 128K tokens through RoPE scaling. Use shorter contexts (4K-8K) for faster inference; extend to 128K only when processing long documents.
- Quantization: INT4 quantization reduces memory by approximately 4x with minimal quality degradation. Recommended for edge deployment; use FP16 or BF16 for maximum quality on server hardware.
- Batch size: Smaller model size allows larger batch sizes on the same hardware, improving throughput for high-volume applications.
Fine-tuning Configuration
- LoRA rank: Ranks of 16-64 work well for domain adaptation. Lower ranks (8-16) suffice for style transfer; higher ranks (32-64) for learning new capabilities.
- Learning rate: Use 1e-5 to 5e-5 for full fine-tuning, 1e-4 to 3e-4 for LoRA. Phi's smaller size makes it more sensitive to learning rate than larger models.
- Training epochs: 1-3 epochs typically sufficient given the model's strong base capabilities. Monitor for overfitting on small datasets.
The next chapter examines other efficient model architectures and techniques for reducing the computational requirements of large language models while maintaining their capabilities.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about Phi models and the textbook-quality data hypothesis.
Phi Models Quiz
Reference
Citation details
Cite or share this article.
Continue with the full handbook
This chapter is part of Language AI Handbook. Use the handbook page to browse the complete table of contents and continue reading in sequence.
Explore Language AI HandbookStay up to date
Get articles, book updates, and news delivered to your inbox.
No spam, unsubscribe anytime.
Join the community
Sign in to remove popups, track your reading progress, and join the discussion.

Comments
No comments yet. Be the first to share your thoughts!