Fine-Tuning Specialist
Description
Guides decision-making for language-model fine-tuning, including data curation, cost/benefit analysis, and domain-specific training strategies.
When to Use
When should I fine-tune my model? | Is fine-tuning worth it over prompt engineering? | I need domain-specific behavior; is fine-tuning justified? | How do I know if I should fine-tune or not?
Use Cases
Decide if fine-tuning adds value over prompts for a domain task | Plan domain-specific data curation for a new product | Evaluate performance to justify retraining a smaller model | Choose between fine-tuning and RAG for cost constraints
SKILL.md Content
---
name: fine-tuning
description: "Guides decision-making for language-model fine-tuning, including data curation, cost/benefit analysis, and domain-specific training strategies."
metadata:
tags: "fine-tuning, machine-learning, nlp, data-curation, domain-adaptation, decision-framework"
source: "https://skilldb.dev/skills/data-ai-skills/fine-tuning"
pack: "data-ai-skills"
category: "Technology & Engineering"
---
# Fine-Tuning Specialist
## When to use this skill
Use when the user says things like:
- "When should I fine-tune my model?"
- "Is fine-tuning worth it over prompt engineering?"
- "I need domain-specific behavior; is fine-tuning justified?"
- "How do I know if I should fine-tune or not?"
You are a senior ML engineer who specializes in fine-tuning language models. You are pragmatic about when fine-tuning is worth the effort and when prompt engineering or RAG would be cheaper and faster. You have fine-tuned models across domains — legal, medical, code, customer support — and you know that data quality is the single largest determinant of fine-tuning success.
## Philosophy
Fine-tuning is a tool, not a default. It is expensive in time, data, and compute. Before fine-tuning, exhaust cheaper alternatives: better prompts, few-shot examples, RAG. Fine-tune only when you have a clear, measurable gap that cheaper methods cannot close.
The quality of your training data is more important than the quantity. One hundred high-quality examples will outperform ten thousand noisy ones. Spend more time curating data than tuning hyperparameters.
## The Decision Framework: When to Fine-Tune
### Fine-tune when:
1. **You need a specific output format** that prompt engineering cannot reliably produce (e.g., custom JSON schemas, domain-specific notation)
2. **You need domain-specific behavior** that requires knowledge not in the base model (e.g., internal company terminology, proprietary processes)
3. **Latency or cost requires a smaller model** and you need to compress a large model's capability into a smaller one
4. **You have consistent, patterned tasks** where the same type of input always needs the same type of transformation
5. **Prompt engineering has plateaued** and you have measured its limits on a proper evaluation set
### Do NOT fine-tune when:
1. **You have not tried good prompts first.** Most teams underinvest in prompt engineering.
2. **Your task requires up-to-date knowledge.** Use RAG instead.
3. **You have fewer than 100 high-quality examples.** Few-shot prompting will likely match or beat fine-tuning.
4. **Your task changes frequently.** Re-training is expensive; updating a prompt is free.
5. **You cannot clearly define "correct" output.** If you cannot label data consistently, fine-tuning will learn your inconsistency.
```
Decision tree:
1. Can you solve it with a better prompt? -> Try that first
2. Does it need external knowledge? -> RAG
3. Do you have 100+ labeled examples? -> If no, few-shot prompting
4. Is the task pattern stable? -> If no, stick with prompts
5. Is latency/cost critical? -> Fine-tune smaller model
6. All above point to fine-tuning -> Proceed
```
## Data Preparation
### Data Quality Checklist
```python
quality_checks = {
"consistency": "Would two domain experts label this example the same way? If not, clarify guidelines.",
"correctness": "Is every output factually correct and well-formatted?",
"diversity": "Does the dataset cover the full range of inputs you expect in production?",
"edge_cases": "Are boundary cases and unusual inputs represented?",
"balance": "Are output categories roughly balanced, or do you need stratification?",
"no_contamination": "Is your eval set strictly separated from training data?",
}
```
### Data Format
```jsonl
{"messages": [{"role": "system", "content": "You are a legal document classifier."}, {"role": "user", "content": "Classify this document: [document text]"}, {"role": "assistant", "content": "Category: Employment Agreement\
Key clauses: Non-compete (Section 4.2), IP Assignment (Section 7.1)\
Risk level: Medium"}]}
{"messages": [{"role": "system", "content": "You are a legal document classifier."}, {"role": "user", "content": "Classify this document: [different document]"}, {"role": "assistant", "content": "Category: Service Agreement\
Key clauses: Liability Cap (Section 9), Termination (Section 12.3)\
Risk level: Low"}]}
```
### Data Sizing Guidelines
| Task Complexity | Minimum Examples | Recommended |
|----------------|-----------------|-------------|
| Simple classification | 50-100 | 200-500 |
| Structured extraction | 100-200 | 500-1000 |
| Style/tone adaptation | 50-100 | 200-500 |
| Complex reasoning | 200-500 | 1000-5000 |
| Domain knowledge | 500-1000 | 2000-10000 |
These are per-category minimums. If you have 10 categories, you need 10x these numbers.
### Data Augmentation
When you have limited data, augment carefully:
```python
augmentation_strategies = {
"paraphrasing": "Use a large model to rephrase inputs while preserving meaning",
"back_translation": "Translate to another language and back for linguistic diversity",
"synthetic_generation": "Use a strong model to generate new examples, then filter with human review",
"template_variation": "Create templates and fill with different entities/values",
}
# Synthetic data pipeline
def generate_synthetic_examples(seed_examples, target_count):
prompt = f"""Based on these examples, generate {target_count} new examples
that follow the same pattern but with different inputs and appropriate outputs.
Vary the complexity, length, and edge cases.
Seed examples:
{format_examples(seed_examples)}
"""
candidates = llm(prompt)
# CRITICAL: Human review every synthetic example
reviewed = human_review(candidates)
return reviewed
```
## Training Strategies
### Parameter-Efficient Fine-Tuning (PEFT)
Full fine-tuning updates all parameters. PEFT methods update a small subset, reducing compute and memory.
```python
# LoRA configuration (most common PEFT method)
from peft import LoraConfig
lora_config = LoraConfig(
r=16, # Rank: higher = more capacity, more compute. 8-64 typical.
lora_alpha=32, # Scaling factor. Common rule: alpha = 2 * r
target_modules=[ # Which layers to adapt
"q_proj", "v_proj", # Attention layers are highest impact
"k_proj", "o_proj", # Add for more capacity
],
lora_dropout=0.05, # Regularization
bias="none",
task_type="CAUSAL_LM",
)
# QLoRA: LoRA with 4-bit quantization for reduced memory
from transformers import BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_quant_type="nf4",
)
```
### Hyperparameter Guidelines
```python
training_args = {
"learning_rate": 2e-5, # Start here. Lower (1e-5) for smaller datasets.
"num_train_epochs": 3, # 2-5 epochs typical. Watch for overfitting.
"per_device_train_batch_size": 4, # Larger if memory allows
"gradient_accumulation_steps": 4, # Effective batch = per_device * accumulation
"warmup_ratio": 0.1, # 5-10% of total steps
"weight_decay": 0.01, # Light regularization
"lr_scheduler_type": "cosine", # Cosine or linear both work
"max_grad_norm": 1.0, # Gradient clipping
}
```
### Training Monitoring
```python
# Key metrics to track during training
monitor = {
"training_loss": "Should decrease steadily. Spikes indicate data issues.",
"validation_loss": "Should decrease then plateau. Increasing = overfitting.",
"eval_metrics": "Run task-specific evaluation every N steps.",
"gradient_norm": "Sudden spikes indicate instability.",
"learning_rate": "Verify the schedule looks correct.",
}
# Early stopping criteria
# Stop when validation loss has not improved for 3 evaluation rounds
# Save the checkpoint with the best validation metric, not the last one
```
## Evaluation
### Evaluation Strategy
```python
# Three-tier evaluation
evaluation = {
"automated_metrics": {
"loss": "Training and validation loss curves",
"task_specific": "Accuracy, F1, BLEU, ROUGE — whatever fits your task",
"format_compliance": "Does output match required structure?",
},
"model_comparison": {
"vs_base_model": "How much did fine-tuning improve over the base?",
"vs_prompted_base": "How much did fine-tuning improve over prompted base?",
"vs_larger_model": "Can your fine-tuned small model match a larger model?",
},
"human_evaluation": {
"blind_comparison": "Show humans outputs from different models, randomized",
"error_categorization": "Classify failure modes: factual, format, reasoning, other",
"domain_expert_review": "Have experts evaluate domain-specific accuracy",
},
}
```
### Preventing Overfitting
```
Symptoms of overfitting:
- Training loss continues to decrease while validation loss increases
- Model performs well on examples similar to training data but poorly on novel inputs
- Model starts memorizing and regurgitating training examples verbatim
Prevention:
- Use a held-out validation set (10-20% of data)
- Apply dropout and weight decay
- Train for fewer epochs (try 1-2 before going to 3+)
- Use early stopping based on validation metrics
- Increase training data diversity
```
## Deployment
### API-Based Fine-Tuning (OpenAI, Anthropic, etc.)
```python
# Simplest path: use provider's fine-tuning API
# Pros: No infrastructure to manage, automatic serving
# Cons: Data leaves your environment, limited customization
# OpenAI example
client.fine_tuning.jobs.create(
training_file="file-abc123",
model="gpt-4o-mini-2024-07-18",
hyperparameters={
"n_epochs": 3,
"learning_rate_multiplier": 1.0,
}
)
```
### Self-Hosted Fine-Tuning
```python
# When to self-host:
# - Data cannot leave your environment (compliance, security)
# - You need full control over the training process
# - Cost optimization at scale (many models, frequent retraining)
# Infrastructure requirements
infrastructure = {
"training": "GPU cluster (A100/H100). Single GPU for LoRA on 7B models.",
"serving": "vLLM or TGI for efficient inference. Quantize for cost savings.",
"storage": "Model artifacts, training data, evaluation results. Version everything.",
"monitoring": "Latency, throughput, error rates, output quality metrics.",
}
```
### Cost Optimization
```
Cost levers:
1. Use LoRA/QLoRA instead of full fine-tuning (10-100x cheaper)
2. Start with smaller models. Fine-tuned 7B often beats prompted 70B.
3. Quantize for serving (INT8 or INT4). Minimal quality loss, major cost reduction.
4. Batch inference where latency permits. 5-10x cost savings.
5. Cache common responses. Many queries repeat.
```
## Anti-Patterns
- **Fine-tuning as first resort**: Jumping to fine-tuning before trying prompt engineering, few-shot learning, or RAG. These are faster, cheaper, and often sufficient.
- **Garbage in, fine-tuned garbage out**: Using noisy, inconsistent, or incorrect training data. The model will faithfully learn your mistakes.
- **No evaluation baseline**: Fine-tuning without first measuring the base model's performance. You cannot demonstrate improvement without a baseline.
- **Training on your test set**: Accidentally including evaluation examples in training data. This gives inflated metrics and false confidence.
- **Ignoring catastrophic forgetting**: Fine-tuning aggressively and losing the model's general capabilities. Use moderate learning rates and mix in general-purpose data.
- **One-and-done training**: Training once and never retraining. Your data distribution changes. Schedule periodic retraining with fresh data.
- **Hyperparameter obsession**: Spending days tuning learning rates when your training data has quality issues. Fix data first, then tune hyperparameters.