Оценка и развёртывание дообученной модели
Проведите количественную оценку, сравнив базовую и дообученную модели на отложенных тестовых случаях, преобразуйте модель в GGUF для локального вывода и предоставьте её через llama.cpp или vLLM.
«Оценка и развёртывание дообученной модели» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Engineering Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Engineering Academy содержит 4 уроков всего.
Почему оценку необходимо проводить до развёртывания
Модель с тонкой настройкой, хорошо работающая на обучающих данных, может работать хуже базовой модели в Вашем реальном сценарии в рабочей среде. Узнать это можно только с помощью строгой оценки. Тонкая настройка может привести к катастрофическому забыванию (утрате возможностей базовой модели), чрезмерной специализации (хорошей работе в Вашей задаче, но ухудшению в смежных задачах) или незаметному ухудшению поведения, связанного с безопасностью. Никогда не развёртывайте модель с тонкой настройкой, не сравнив её с базовой моделью на репрезентативном тестовом наборе.
Создание отложенного тестового набора
Ваш тестовый набор должен быть полностью отделён от обучающих и проверочных данных: он должен содержать примеры, которые модель не видела ни на одном этапе обучения. Тестовый набор должен представлять полное распределение входных данных в рабочей среде: распространённые случаи, граничные случаи и состязательные входные данные. Для задач следования инструкциям включите примеры, требующие выполнения всех аспектов инструкции, а не только наиболее распространённых. Для надёжной оценки обычно достаточно тестового набора из 100–500 примеров.
import json
from typing import TypedDict
class TestCase(TypedDict):
input: str # the user message
expected_output: str # the ideal response
category: str # e.g., 'format', 'accuracy', 'edge_case'
evaluation_method: str # 'exact_match', 'json_schema', 'llm_judge'
# Load test set (never used during training)
def load_test_set(path: str) -> list[TestCase]:
cases = []
with open(path) as f:
for line in f:
data = json.loads(line.strip())
cases.append({
'input': data['messages'][-2]['content'], # user message
'expected_output': data['messages'][-1]['content'], # assistant response
'category': data.get('metadata', {}).get('category', 'general'),
'evaluation_method': data.get('metadata', {}).get('eval_method', 'llm_judge')
})
return cases
test_set = load_test_set('test.jsonl')
print(f'Test set loaded: {len(test_set)} examples')Количественные метрики для оценки конкретной задачи
Выбирайте метрики оценки в соответствии с задачей. Для извлечения JSON измеряйте долю соответствия схеме и точность на уровне полей. Для классификации измеряйте точность, точность положительных предсказаний и полноту для каждого класса. Для генерации текста используйте оценивание качества с помощью LLM в роли судьи. Для соблюдения формата измеряйте точную долю соответствия формату. Рассчитывайте каждую метрику и для базовой модели, и для модели с тонкой настройкой, чтобы измерить разницу в улучшении.
import json
def evaluate_json_extraction(model_output: str, expected: str, schema: dict) -> dict:
metrics = {'valid_json': False, 'schema_compliant': False, 'field_accuracy': 0.0}
try:
parsed = json.loads(model_output.strip())
metrics['valid_json'] = True
# Check schema compliance
required_fields = schema.get('required', [])
all_present = all(field in parsed for field in required_fields)
correct_types = all(
isinstance(parsed.get(field), schema['properties'][field]['expected_type'])
for field in required_fields if field in parsed
)
metrics['schema_compliant'] = all_present and correct_types
# Field-level accuracy against expected output
expected_parsed = json.loads(expected)
correct_fields = sum(1 for k in expected_parsed if parsed.get(k) == expected_parsed[k])
metrics['field_accuracy'] = correct_fields / len(expected_parsed) if expected_parsed else 0.0
except json.JSONDecodeError:
pass # valid_json stays False
return metricsОценка с помощью LLM в роли судьи
Для задач генерации со свободной формой используйте LLM в роли судьи, чтобы оценивать результаты модели с тонкой настройкой относительно ожидаемых результатов. Предложите GPT-4o выступить в роли оценщика, передайте ему входные данные, ожидаемый результат и результат модели и попросите оценить правильность, полноту и соответствие формату по шкале от 1 до 5. Усредните оценки по тестовому набору, чтобы получить общую оценку качества. Сравните оценку модели с тонкой настройкой с оценкой базовой модели на том же тестовом наборе.
from openai import OpenAI
client = OpenAI()
def llm_judge_score(instruction: str, expected: str, actual: str) -> dict:
judge_prompt = f'''Evaluate the quality of an AI assistant response.
Instruction given to assistant:
{instruction}
Expected ideal response:
{expected}
Actual response from model being evaluated:
{actual}
Rate the actual response on these criteria (1=poor, 5=excellent):
1. Correctness: Is the information accurate?
2. Format compliance: Does it follow the expected output format?
3. Completeness: Does it address all parts of the instruction?
Return JSON: {{"correctness": N, "format": N, "completeness": N, "overall": N, "reason": "brief explanation"}}'''
response = client.chat.completions.create(
model='gpt-4o',
messages=[{'role': 'user', 'content': judge_prompt}],
response_format={'type': 'json_object'}
)
return json.loads(response.choices[0].message.content)Проведение сравнительной оценки
Проведите очное сравнение базовой модели, модели с тонкой настройкой и, при необходимости, сильного базового варианта с тщательно составленными запросами на всех тестовых примерах. Получите результаты каждой модели для каждого тестового примера, а затем оцените все результаты с помощью выбранных метрик. Составьте сравнительную таблицу с оценками метрик, стандартными отклонениями и примерами случаев, в которых модель с тонкой настройкой превосходит базовый вариант или уступает ему.
def run_full_evaluation(test_set: list, models: dict, system_prompt: str) -> dict:
results = {name: {'scores': [], 'errors': 0} for name in models}
for i, test_case in enumerate(test_set):
print(f'Evaluating test case {i+1}/{len(test_set)}')
for model_name, model_fn in models.items():
try:
output = model_fn(test_case['input'], system_prompt)
score = llm_judge_score(
test_case['input'],
test_case['expected_output'],
output
)
results[model_name]['scores'].append(score['overall'])
except Exception as e:
results[model_name]['errors'] += 1
results[model_name]['scores'].append(0)
# Summarize
summary = {}
for name, data in results.items():
scores = data['scores']
summary[name] = {
'mean_score': sum(scores) / len(scores),
'errors': data['errors']
}
print(f'{name}: mean={summary[name]["mean_score"]:.2f}, errors={data["errors"]}')
return summaryРегрессионное тестирование на катастрофическое забывание
Тонкая настройка может ухудшить общие возможности модели — это явление называется катастрофическим забыванием. Запустите набор регрессионных тестов для базовой модели и модели с тонкой настройкой, охватывающий важные для Вас задачи помимо целевой: ответы на общие вопросы, рассуждение, генерацию кода и следование инструкциям. Если модель с тонкой настройкой получает значительно более низкие оценки в этих задачах, возможно, ранг LoRA слишком высок или обучение продолжалось слишком много эпох.
REGRESSION_TEST_CASES = [
# General QA
{'input': 'What is the capital of France?', 'expected_substring': 'Paris'},
{'input': 'What is 17 * 23?', 'expected_substring': '391'},
# Instruction following
{'input': 'List 3 planets. Format as: 1. Planet Name', 'expected_pattern': r'^1\. '},
# Reasoning
{'input': 'If all A are B and all B are C, are all A also C?', 'expected_substring': 'yes'},
]
def run_regression_tests(model_fn, test_cases: list) -> float:
passed = 0
for test in test_cases:
output = model_fn(test['input'], '')
if 'expected_substring' in test:
if test['expected_substring'].lower() in output.lower():
passed += 1
elif 'expected_pattern' in test:
import re
if re.search(test['expected_pattern'], output):
passed += 1
rate = passed / len(test_cases)
print(f'Regression test pass rate: {rate:.1%} ({passed}/{len(test_cases)})')
return rateПреобразование в GGUF для локального вывода
Для локального развёртывания без дорогостоящей инфраструктуры с GPU преобразуйте объединённую модель в формат GGUF и запускайте вывод с помощью llama.cpp. GGUF поддерживает различные уровни квантования: Q4_K_M (4-битное, хороший баланс качества и скорости), Q8_0 (8-битное, почти полное качество) и Q2_K (2-битное, очень быстрое, но с более низким качеством). Квантованной 4-битной модели 7B требуется всего около 4 ГБ RAM, и она может работать на CPU со скоростью 1–5 токенов в секунду.
# Step 1: Convert merged HuggingFace model to GGUF
# git clone https://github.com/ggerganov/llama.cpp
# python llama.cpp/convert_hf_to_gguf.py ./merged-model --outtype f16 --outfile model-f16.gguf
# Step 2: Quantize to 4-bit
# ./llama.cpp/llama-quantize model-f16.gguf model-q4.gguf Q4_K_M
# Step 3: Run inference with llama.cpp Python bindings
# pip install llama-cpp-python
from llama_cpp import Llama
llm = Llama(
model_path='./model-q4.gguf',
n_ctx=4096, # context window
n_threads=8, # CPU threads
n_gpu_layers=0 # set > 0 to offload layers to GPU
)
output = llm.create_chat_completion(
messages=[{'role': 'user', 'content': 'What is the capital of France?'}],
temperature=0.1
)
print(output['choices'][0]['message']['content'])Обслуживание запросов с помощью vLLM в рабочей среде
Для обслуживания запросов к модели с тонкой настройкой в рабочей среде и в большом масштабе текущим стандартом является vLLM. vLLM использует PagedAttention для эффективного объединения нескольких запросов в пакеты, значительно увеличивая пропускную способность GPU. Он поддерживает конечные точки API, совместимые с OpenAI, поэтому может напрямую заменить API OpenAI. Один GPU A100 с vLLM и моделью 7B с тонкой настройкой способен обрабатывать сотни запросов в минуту.
# Start vLLM server (run from command line)
# pip install vllm
# python -m vllm.entrypoints.openai.api_server \
# --model ./merged-model \
# --host 0.0.0.0 \
# --port 8000 \
# --max-model-len 4096 \
# --tensor-parallel-size 1
# Use with OpenAI client (drop-in replacement)
from openai import OpenAI
client = OpenAI(
base_url='http://localhost:8000/v1',
api_key='not-needed' # vLLM doesn't require auth by default
)
response = client.chat.completions.create(
model='merged-model', # model name matches the path you passed to vLLM
messages=[{'role': 'user', 'content': 'Extract JSON from: "Alice, 30, NYC"'}]
)
print(response.choices[0].message.content)A/B-тестирование модели с тонкой настройкой
Прежде чем полностью перейти на модель с тонкой настройкой в рабочей среде, проведите A/B-тест: направьте часть рабочего трафика (начните с 5–10%) на модель с тонкой настройкой, пока большинство запросов по-прежнему обрабатывается базовой моделью или существующим вариантом запросов. Отслеживайте оценки качества, задержку и показатели удовлетворённости пользователей для обеих групп. Увеличивайте долю трафика модели с тонкой настройкой только после того, как A/B-тест подтвердит улучшение на статистически значимом числе запросов.
import random
class ModelRouter:
def __init__(self, fine_tuned_traffic_fraction=0.1):
self.ft_fraction = fine_tuned_traffic_fraction
self.metrics = {'base': {'count': 0, 'quality_sum': 0}, 'fine_tuned': {'count': 0, 'quality_sum': 0}}
def route(self, user_id: str, request: str) -> dict:
# Deterministic routing by user_id (same user always goes to same model)
use_fine_tuned = (hash(user_id) % 100) < (self.ft_fraction * 100)
model_group = 'fine_tuned' if use_fine_tuned else 'base'
response = call_model(request, use_fine_tuned=use_fine_tuned)
return {'response': response, 'model_group': model_group}
def record_quality(self, model_group: str, quality_score: float):
self.metrics[model_group]['count'] += 1
self.metrics[model_group]['quality_sum'] += quality_score
def ab_test_summary(self) -> dict:
summary = {}
for group, data in self.metrics.items():
avg = data['quality_sum'] / data['count'] if data['count'] > 0 else 0
summary[group] = {'avg_quality': avg, 'n': data['count']}
return summaryПоддержка моделей с тонкой настройкой с течением времени
Модели с тонкой настройкой требуют постоянного обслуживания. При обновлении базовой модели (выходе новой версии GPT-4o или новой версии Mistral) Ваши веса адаптера могут оказаться несовместимыми, и Вам, возможно, потребуется повторное обучение. При изменении требований к задаче необходимо обновить обучающие данные и повторить обучение. Обнаружив в рабочей среде новые варианты сбоев, добавляйте соответствующие примеры в обучающий набор. Запланируйте периодическое повторное обучение как часть рабочего процесса эксплуатации ML в рабочей среде.
Матрица решений по развёртыванию
Выбор стратегии развёртывания дообученной модели зависит от масштаба и ограничений вашей инфраструктуры. Для небольшого объёма (менее 1000 запросов в день) проще всего использовать API дообучения OpenAI. Для среднего объёма (1000–100 000 запросов в день) рассмотрите vLLM на одном экземпляре GPU. Для большого объёма или приложений, критичных к задержке, используйте vLLM с несколькими GPU, рассмотрите тензорный параллелизм и добавьте перед ним слой кэширования. Для чувствительных к конфиденциальности данных размещайте решение в собственной инфраструктуре с помощью GGUF/llama.cpp или vLLM.
Быстрая проверка
Проверьте, насколько хорошо вы усвоили материал об оценке и развёртывании дообученных моделей из этого урока.
Итоги урока
В этом уроке вы узнали, что строгая оценка перед развёртыванием с сопоставлением дообученной и базовой моделей на отложенных тестовых примерах обязательна, регрессионное тестирование выявляет катастрофическое забывание общих возможностей, вызванное чрезмерной специализацией, а варианты развёртывания варьируются от управляемого API дообучения OpenAI для простоты до vLLM для обслуживания в рабочей среде с высокой пропускной способностью и GGUF/llama.cpp для локального вывода на CPU. Поздравляем с завершением трека по инженерии ИИ!
Изучай Python с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 30
- Уроки
- 120
Часто задаваемые вопросы
Урок «Оценка и развёртывание дообученной модели» бесплатный?
Да — полный текст урока «Оценка и развёртывание дообученной модели» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Engineering Academy, подпишись на CoddyKit PRO. Курс AI Engineering Academy содержит 4 уроков всего.
Чему я научусь в уроке «Оценка и развёртывание дообученной модели»?
Проведите количественную оценку, сравнив базовую и дообученную модели на отложенных тестовых случаях, преобразуйте модель в GGUF для локального вывода и предоставьте её через llama.cpp или vLLM. Ты практикуешь AI Engineering Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Engineering Academy?
Предыдущий опыт не требуется. AI Engineering Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Оценка и развёртывание дообученной модели»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Engineering Academy?
Да. Каждый урок AI Engineering Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Когда дообучение эффективнее составления запросов
- Подготовка высококачественного обучающего набора данных
- Дообучение LoRA с Hugging Face PEFT
- Оценка и развёртывание дообученной модели