After each fine-tuning iteration, the question arises: did it get better? Without a structured metrics system, it's easy to fall into a trap—the model may overfit, lose general knowledge, or degrade on edge cases. We've seen projects where teams did 15+ iterations "by eye" until they built an evaluation pipeline. Our approach—a hierarchy of metrics from fast automatic ones to expensive human evaluation—lets you stop at the right moment and guarantee quality.
Why comprehensive evaluation of a fine-tuned model matters?
One metric doesn't tell the whole story. BLEU may be high, but the model may hallucinate facts. Perplexity may drop, but response usefulness may decline. Only a combination of automatic metrics, LLM-as-judge, and human evaluation shows the true quality of a fine-tuned neural network. We've been using this approach in all projects for over 5 years—accumulating a portfolio of 50+ fine-tuning projects.
Hierarchy of evaluation metrics
Level 1: Automatic metrics
Fast, cheap, work without human. Provide rough assessment but are indispensable for rapid iterations.
Level 2: LLM-as-judge
A strong model (GPT-4o, Claude 3.5) evaluates the responses of the tested model. Correlates well with human judgment under the right prompt.
Level 3: Human evaluation
Gold standard. Expensive, but necessary for final validation and calibrating lower levels. Typically 200–500 examples suffice.
Comparison of metric types:
| Metric Type | Speed | Cost | Correlation with Human |
|---|---|---|---|
| Automatic (BLEU, ROUGE, perplexity) | Instant | Low | 50-60% |
| LLM-as-judge | 1-2 days per 1000 examples | Medium | 80-85% |
| Human evaluation | Week per 200 examples | High | 95-100% |
How LLM-as-judge complements automatic metrics?
Automatic metrics (BLEU, ROUGE) only measure surface overlap with a reference. LLM-as-judge evaluates semantics: factual accuracy, completeness, logic. In one case, base Llama 3.1 8B showed BLEU-4=0.18, and after fine-tuning—0.39, but the LLM judge revealed the model started inventing legal articles. Without the judge, this would have been missed. LLM-as-judge correlates with human evaluation at 85%, which is 1.5 times higher than automatic metrics. Also, LLM-as-judge is crucial for evaluating text generation where automatic metrics are powerless. Using an evaluation pipeline reduces iteration time by 3 times compared to ad-hoc testing.
Practical implementation of LLM-as-judge
Example implementation of LLM-as-judge
from openai import OpenAI
JUDGE_PROMPT = """You are a strict expert evaluating the quality of AI assistant responses.
Question: {question}
Assistant's answer: {answer}
Reference answer: {reference}
Evaluate the answer on the following criteria (each 1–5):
1. Factual accuracy
2. Completeness of coverage
3. Structuredness
4. Style appropriateness
Return JSON: {{"accuracy": X, "completeness": X, "structure": X, "style": X, "overall": X, "reasoning": "..."}}"""
def llm_judge(question: str, answer: str, reference: str, client: OpenAI) -> dict:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": JUDGE_PROMPT.format(question=question, answer=answer, reference=reference)
}],
response_format={"type": "json_object"},
temperature=0.1
)
return json.loads(response.choices[0].message.content)
Automatic metrics: implementation in code
Below is an example of computing BLEU and ROUGE for an evaluation pipeline. The code can be used as a template.
from nltk.translate.bleu_score import corpus_bleu, SmoothingFunction
from rouge_score import rouge_scorer
references = [[ref.split()] for ref in reference_list]
hypotheses = [hyp.split() for hyp in hypothesis_list]
bleu_4 = corpus_bleu(references, hypotheses,
weights=(0.25, 0.25, 0.25, 0.25),
smoothing_function=SmoothingFunction().method1)
scorer = rouge_scorer.RougeScorer(['rouge1','rouge2','rougeL'], use_stemmer=True)
rouge_scores = [scorer.score(ref, hyp) for ref, hyp in zip(reference_list, hypothesis_list)]
Perplexity—model confidence metric
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
def compute_perplexity(model, tokenizer, texts: list[str]) -> float:
total_loss = 0
total_tokens = 0
model.eval()
with torch.no_grad():
for text in texts:
encodings = tokenizer(text, return_tensors="pt").to(model.device)
outputs = model(**encodings, labels=encodings["input_ids"])
total_loss += outputs.loss.item() * encodings["input_ids"].shape[1]
total_tokens += encodings["input_ids"].shape[1]
avg_loss = total_loss / total_tokens
return torch.exp(torch.tensor(avg_loss)).item()
Classification and extraction—evaluate via F1 and accuracy
from sklearn.metrics import classification_report, f1_score
def evaluate_classification(model_outputs: list, ground_truth: list) -> dict:
predictions = []
for output in model_outputs:
try:
pred = json.loads(output)["category"]
except:
pred = "parse_error"
predictions.append(pred)
report = classification_report(ground_truth, predictions, output_dict=True)
return {
"macro_f1": report["macro avg"]["f1-score"],
"weighted_f1": report["weighted avg"]["f1-score"],
"accuracy": report["accuracy"],
"per_class": {k: v for k, v in report.items() if isinstance(v, dict) and k not in ["macro avg", "weighted avg"]}
}
Practical example: comprehensive evaluation of a fine-tuned model
Base model: Llama 3.1 8B Instruct. Fine-tuning: QLoRA r=16, 2000 examples of legal documents.
| Metric | Base model | Fine-tuned | Change |
|---|---|---|---|
| ROUGE-L | 0.41 | 0.67 | +63% |
| BLEU-4 | 0.18 | 0.39 | +117% |
| Perplexity (domain) | 24.3 | 11.8 | -51% |
| Perplexity (MMLU) | 8.2 | 9.1 | +11% (forgetting) |
| LLM-judge overall | 3.1 | 4.3 | +39% |
| F1 (NER category) | 0.61 | 0.89 | +46% |
Perplexity on MMLU increased by 11%—moderate catastrophic forgetting, but for a narrow legal use case this is acceptable. Solution: add 10% general data in the next round. This fine-tuning benchmark helps quantify forgetting.
What our model evaluation work includes
- Development of a custom evaluation pipeline for your task (including scripts, configs, and documentation)
- Setup of automatic metrics (BLEU, ROUGE, perplexity, F1, accuracy)
- Integration of LLM-as-judge with judge model selection and prompt calibration
- Conducting human evaluation (engaging experts, collecting annotations)
- Building a metrics dashboard and monitoring (MLflow, Weights & Biases)
- Delivery of full documentation, access credentials, and a training session
- Ongoing support and maintenance for 3 months post-deployment
- Final report with recommendations for model improvement
- Training your team to work with the pipeline
Investment: from $3,000 for a basic pipeline to $10,000 for a comprehensive system. Savings on unnecessary iterations can reach 40% of project budget.
Start with an evaluation pipeline—contact us for a model audit. Our method ensures reliable LLM quality assessment.
Timeline and evaluation process
- Task and metrics analysis—1–2 days
- Pipeline development and integration—3–5 days
- Automatic evaluation—a few hours
- LLM-as-judge (up to 1000 examples)—1–2 days
- Human evaluation—1 week
- Report and recommendations—1–2 days
Total for one fine-tuning iteration—1–2 weeks. For a comprehensive project with multiple rounds—from 3 weeks.
Post-deployment monitoring
import mlflow
def log_inference_quality(prompt, response, user_feedback):
with mlflow.start_run(run_name="production-monitoring"):
mlflow.log_metrics({
"response_length": len(response.split()),
"refusal_detected": int("I cannot" in response.lower()),
"user_rating": user_feedback.get("rating", -1),
})
Monitoring in production is mandatory—we guarantee the model doesn't degrade over time. Certified engineers from our team accompany the project throughout its lifecycle. Our team has 5+ years of experience in fine-tuning and has completed over 50 projects.
If you want to implement systematic evaluation, contact us. We'll help build a pipeline and save on unnecessary iterations. Request a consultation: we'll evaluate your project in 2 days.







