Skip to content

Model Distillation and Compression

What You'll Learn

Objective Time Difficulty
Understand knowledge distillation for LLMs 40 min Advanced
Generate high-quality distillation data
Train a smaller model to mimic a larger one
Evaluate cost vs quality tradeoffs

What is Knowledge Distillation?

Knowledge distillation transfers the capabilities of a large teacher model into a smaller student model. The student learns to mimic the teacher's behavior on your specific tasks.

Teacher Model (e.g., GPT-4.1, Claude Opus)
  - Expensive: $10-15/1M output tokens
  - Slow: 1-3 seconds per response
  - High quality on everything
        |
        | Generate training examples
        v
Student Model (e.g., Llama-3.1-8B, GPT-4.1-mini fine-tuned)
  - Cheap: $0.50-1.60/1M output tokens  
  - Fast: 0.1-0.5 seconds per response
  - High quality on YOUR specific tasks

When to Distill

Scenario Distillation Value
High-volume API calls (>100K/day) Very high — saves 80-90% on costs
Latency-sensitive applications High — smaller models respond faster
Need to self-host (privacy) High — can run on single GPU
Diverse, unpredictable tasks Low — student won't generalize as well
Low volume (<1K requests/day) Low — just use the teacher directly

Step 1: Generate Distillation Data

The key is to generate a diverse, high-quality dataset using the teacher model:

from openai import OpenAI
import json
import random

client = OpenAI()

def generate_distillation_data(
    prompts, 
    teacher_model="gpt-4.1",
    system_prompt="",
    samples_per_prompt=1
):
    """Generate training data using a teacher model."""
    training_data = []

    for prompt in prompts:
        for _ in range(samples_per_prompt):
            messages = []
            if system_prompt:
                messages.append({"role": "system", "content": system_prompt})
            messages.append({"role": "user", "content": prompt})

            response = client.chat.completions.create(
                model=teacher_model,
                messages=messages,
                temperature=0.7,  # Some variety in responses
            )

            teacher_response = response.choices[0].message.content

            training_data.append({
                "messages": [
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": prompt},
                    {"role": "assistant", "content": teacher_response}
                ]
            })

    return training_data

# Example: distill customer service capabilities
prompts = [
    "What is your return policy?",
    "My order hasn't arrived yet",
    "I want to cancel my subscription",
    "How do I change my shipping address?",
    "The product I received is damaged",
    # ... hundreds more prompts covering your use cases
]

system_prompt = "You are a helpful customer service agent. Be concise, empathetic, and solution-oriented."

data = generate_distillation_data(prompts, system_prompt=system_prompt)

# Save to JSONL
with open("distillation_data.jsonl", "w") as f:
    for example in data:
        f.write(json.dumps(example) + "
")

print(f"Generated {len(data)} distillation examples")

Generating Diverse Prompts

def generate_diverse_prompts(task_description, num_prompts=500):
    """Use the teacher to generate diverse training prompts."""
    response = client.chat.completions.create(
        model="gpt-4.1",
        messages=[{
            "role": "user", 
            "content": f"""Generate {num_prompts} diverse user prompts for this task:
{task_description}

Requirements:
- Cover edge cases and unusual scenarios
- Vary in length, complexity, and tone
- Include some ambiguous or challenging requests
- Mix simple and complex queries

Return as a JSON array of strings."""
        }],
        temperature=0.9,
    )

    return json.loads(response.choices[0].message.content)

Step 2: Quality Filtering

Not all teacher responses are good. Filter before training:

def filter_quality(data, teacher_model="gpt-4.1"):
    """Use the teacher to quality-check its own responses."""
    filtered = []

    for example in data:
        user_msg = next(m for m in example["messages"] if m["role"] == "user")
        asst_msg = next(m for m in example["messages"] if m["role"] == "assistant")

        check = client.chat.completions.create(
            model=teacher_model,
            messages=[{
                "role": "user",
                "content": f"""Rate this response quality (1-5):

Question: {user_msg["content"]}
Response: {asst_msg["content"]}

Return JSON: {{"score": <1-5>, "reason": "..."}}"""
            }],
            temperature=0,
            response_format={"type": "json_object"}
        )

        result = json.loads(check.choices[0].message.content)
        if result["score"] >= 4:
            filtered.append(example)

    print(f"Kept {len(filtered)}/{len(data)} examples (filtered {len(data)-len(filtered)} low-quality)")
    return filtered

Step 3: Train the Student

Use the distillation data to fine-tune a smaller model (exactly like Lessons 3-4):

# Option A: Fine-tune GPT-4.1-mini via OpenAI API
job = client.fine_tuning.jobs.create(
    training_file=upload_file("distillation_data.jsonl"),
    model="gpt-4.1-mini",
    hyperparameters={"n_epochs": 3},
    suffix="distilled-cs-agent"
)

# Option B: Fine-tune an open-source model locally
# (See Lesson 4 for the full Hugging Face workflow)

Step 4: Evaluate the Student

def evaluate_distillation(test_prompts, system_prompt, student_model, teacher_model="gpt-4.1"):
    """Compare student vs teacher on held-out test prompts."""
    results = {"student_wins": 0, "teacher_wins": 0, "ties": 0}

    for prompt in test_prompts:
        messages = [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": prompt}
        ]

        teacher_resp = client.chat.completions.create(
            model=teacher_model, messages=messages, temperature=0.3
        ).choices[0].message.content

        student_resp = client.chat.completions.create(
            model=student_model, messages=messages, temperature=0.3
        ).choices[0].message.content

        # Use teacher to judge (with position randomization to avoid bias)
        if random.random() > 0.5:
            a, b, a_label, b_label = teacher_resp, student_resp, "teacher", "student"
        else:
            a, b, a_label, b_label = student_resp, teacher_resp, "student", "teacher"

        judge = client.chat.completions.create(
            model="gpt-4.1",
            messages=[{"role": "user", "content": f"""Compare these two responses:

Prompt: {prompt}

Response A: {a}
Response B: {b}

Which is better? Return JSON: {{"winner": "A" or "B" or "tie"}}"""}],
            temperature=0,
            response_format={"type": "json_object"}
        )

        winner = json.loads(judge.choices[0].message.content)["winner"]
        if winner == "A":
            results[f"{a_label}_wins"] += 1
        elif winner == "B":
            results[f"{b_label}_wins"] += 1
        else:
            results["ties"] += 1

    total = len(test_prompts)
    print(f"Student wins: {results['student_wins']}/{total}")
    print(f"Teacher wins: {results['teacher_wins']}/{total}")
    print(f"Ties: {results['ties']}/{total}")
    return results

Cost Analysis

def calculate_roi(
    daily_requests,
    avg_input_tokens=200,
    avg_output_tokens=300,
    teacher_input_price=2.50,   # per 1M tokens
    teacher_output_price=10.00,
    student_input_price=0.40,
    student_output_price=1.60,
    distillation_cost=200,      # one-time cost to generate data + train
):
    """Calculate ROI of distillation."""
    daily_input = daily_requests * avg_input_tokens / 1_000_000
    daily_output = daily_requests * avg_output_tokens / 1_000_000

    teacher_daily = (daily_input * teacher_input_price) + (daily_output * teacher_output_price)
    student_daily = (daily_input * student_input_price) + (daily_output * student_output_price)

    savings_per_day = teacher_daily - student_daily
    breakeven_days = distillation_cost / savings_per_day if savings_per_day > 0 else float('inf')

    print(f"Daily cost (teacher): ${teacher_daily:.2f}")
    print(f"Daily cost (student): ${student_daily:.2f}")
    print(f"Daily savings: ${savings_per_day:.2f}")
    print(f"Breakeven: {breakeven_days:.0f} days")
    print(f"Annual savings: ${savings_per_day * 365:,.0f}")

# Example: 50K requests/day
calculate_roi(daily_requests=50_000)

Resources

  • OpenAI Model Distillation Guide: platform.openai.com/docs/guides/distillation
  • Paper: "Distilling the Knowledge in a Neural Network" (Hinton et al., 2015)
  • Blog: How to Distill GPT-4 into a Smaller Model: Practical guide with code examples

Key Takeaways

  • Knowledge distillation trains a small, cheap model to mimic a large, expensive one on your specific tasks
  • Generate 500-5,000 diverse examples using the teacher model as training data
  • Quality-filter the teacher's outputs before training
  • A well-distilled student can match 80-95% of teacher quality at 10-20% of the cost
  • The ROI is strongest for high-volume, narrow-domain applications

Next Lesson

Lesson 10: Fine-Tuning Best Practices and Case Studies — Real-world lessons from production fine-tuning projects, common pitfalls, and a decision framework.