AI Engineering Academy · Урок

Оценка и развёртывание дообученной модели

Проведите количественную оценку, сравнив базовую и дообученную модели на отложенных тестовых случаях, преобразуйте модель в GGUF для локального вывода и предоставьте её через llama.cpp или vLLM.

Урок 4 из 413 шагов

«Оценка и развёртывание дообученной модели» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Когда дообучение эффективнее составления запросов
  2. Подготовка высококачественного обучающего набора данных
  3. Дообучение LoRA с Hugging Face PEFT
  4. Оценка и развёртывание дообученной модели
← Назад к AI Engineering Academy