A/B-тестування дофіно-tuned моделей
A/B-тестування LLM — процес порівняння двох версій моделі (baseline vs candidate) на реальному трафіку або репрезентативній вибірці запитів. Без A/B-тесту неможливо достовірно підтвердити, що дофіно-tuned модель краще за попередню в умовах продакшну — лабораторні метрики (ROUGE, F1) не завжди корелюють з реальною користю.
Структура A/B-тесту для LLM
Типові порівняння:
- Базова модель (GPT-4o / Llama base) vs fine-tuned версія
- Fine-tuned v1 vs Fine-tuned v2 (ітерація датасету)
- Модель A (Llama 3.1 8B) vs Модель B (Mistral 7B), обидві fine-tuned
- Промпт v1 vs Промпт v2 на одній моделі
Метрики A/B-тесту:
- Переважання користувачів (explicit: лайки/дизлайки, implicit: повторне використання)
- Task completion rate (користувач отримав необхідну відповідь з першої спроби)
- Time-to-value (скільки повідомлень потрібно, щоб розв'язати задачу)
- Escalation rate (частка звернень, переданих людині)
- Latency (P50, P95, P99)
Реалізація маршрутизації трафіку
import hashlib
import random
from typing import Literal
class ABRouter:
"""Детермінована маршрутизація A/B за user_id"""
def __init__(self, experiment_name: str, split: float = 0.5):
self.experiment_name = experiment_name
self.split = split # Частка трафіку для варіанту B
def assign(self, user_id: str) -> Literal["control", "treatment"]:
"""Один користувач завжди потрапляє в одну групу"""
hash_input = f"{self.experiment_name}:{user_id}"
hash_value = int(hashlib.md5(hash_input.encode()).hexdigest(), 16)
normalized = (hash_value % 10000) / 10000
return "treatment" if normalized < self.split else "control"
router = ABRouter("fine-tuned-v2-test", split=0.2) # 20% трафіку на нову модель
# В обробнику запиту
def handle_request(user_id: str, prompt: str) -> str:
variant = router.assign(user_id)
if variant == "control":
response = baseline_model.generate(prompt)
model_version = "baseline"
else:
response = finetuned_model.generate(prompt)
model_version = "v2-finetuned"
log_event(user_id, variant, prompt, response, model_version)
return response
Статистична значимість
Недостатньо просто порівняти середні метрики — потрібно перевірити статистичну значимість різниці.
from scipy import stats
import numpy as np
def ab_significance_test(
control_outcomes: list[float],
treatment_outcomes: list[float],
alpha: float = 0.05
) -> dict:
"""
Двосторонній t-тест для перевірки значимості різниці в метриках
control_outcomes: метрики групи A (наприклад, task_completion = [1,0,1,1,...])
"""
t_stat, p_value = stats.ttest_ind(control_outcomes, treatment_outcomes)
control_mean = np.mean(control_outcomes)
treatment_mean = np.mean(treatment_outcomes)
relative_lift = (treatment_mean - control_mean) / control_mean * 100
return {
"control_mean": control_mean,
"treatment_mean": treatment_mean,
"relative_lift_pct": relative_lift,
"p_value": p_value,
"significant": p_value < alpha,
"sample_sizes": {"control": len(control_outcomes), "treatment": len(treatment_outcomes)}
}
# Розрахунок необхідного розміру вибірки
def required_sample_size(
baseline_rate: float, # Поточна метрика (наприклад, 0.75)
min_detectable_effect: float, # Мінімальне значиме покращення (наприклад, 0.05)
alpha: float = 0.05,
power: float = 0.80
) -> int:
from statsmodels.stats.power import TTestIndPower
analysis = TTestIndPower()
effect_size = min_detectable_effect / (baseline_rate * (1 - baseline_rate)) ** 0.5
n = analysis.solve_power(effect_size=effect_size, alpha=alpha, power=power)
return int(np.ceil(n))
# Приклад: baseline task_completion = 75%, хочемо зафіксувати покращення на 5%
n = required_sample_size(0.75, 0.05)
print(f"Потрібно {n} запитів на кожну групу") # ~500–1000
Практичний case study: A/B-тест саппорт-боту
Контекст: customer support бот на основі Llama 3.1 8B fine-tuned v1. Підготовлена версія v2 з поліпшеним датасетом (+800 прикладів, виправлені failure cases v1).
Експеримент:
- Контроль (80% трафіку): v1
- Тест (20% трафіку): v2
- Тривалість: 14 днів
- Розмір вибірки: 6200 діалогів на контрольну групу, 1550 на тестову
Первинна метрика: task_completion_rate (користувач вирішив питання без escalation). Вторинні: CSAT, escalation_rate, avg_turns_to_resolution.
Результати:
| Метрика | v1 (control) | v2 (treatment) | p-value | Значимо? |
|---|---|---|---|---|
| Task completion | 71.3% | 78.9% | 0.0012 | Так |
| CSAT | 3.8 | 4.1 | 0.034 | Так |
| Escalation rate | 28.7% | 21.1% | 0.0008 | Так |
| Avg turns | 3.2 | 2.9 | 0.18 | Ні |
| Latency P95 | 2.1с | 2.3с | — | +10% |
v2 статистично значимо краще за трьома з чотирьох метрик. Зростання латентності P95 (+10%) прийнятне. Прийнято рішення про повний rollout.
Інструменти для A/B-тестування LLM
LangSmith (LangChain): інтегроване відслідковування експериментів, comparison view. Phoenix (Arize): OpenTelemetry-based observability для LLM. MLflow: універсальне відслідковування експериментів. Weights & Biases: таблиці, гістограми, LLM eval pipeline.
Часові рамки
- Настройка A/B інфраструктури: 3–7 днів
- Експеримент (досягнення необхідного n): 1–4 тижні
- Аналіз результатів та прийняття рішення: 2–3 дні
- Всього: 2–5 тижнів







