AI Engineering Academy · Lekcja

Ocena i wdrażanie dostrojonego modelu

Wykonaj ilościowe ewaluacje porównujące model bazowy i dostrojony na odłożonych przypadkach testowych, przekonwertuj model do GGUF na potrzeby lokalnego wnioskowania i udostępnij go za pomocą llama.cpp lub vLLM.

Lekcja 4 z 413 kroki

Ocena i wdrażanie dostrojonego modelu to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Dlaczego ocenę należy przeprowadzić przed wdrożeniem?

Dostrojony model, który dobrze działa na danych treningowych, może działać gorzej niż model bazowy w rzeczywistym zastosowaniu produkcyjnym. Jedynym sposobem, aby się o tym przekonać, jest rygorystyczna ocena. Dostrajanie może prowadzić do katastrofalnego zapominania (utraty możliwości posiadanych przez model bazowy), nadmiernej specjalizacji (dobrego działania w zadaniu docelowym, ale gorszego w zadaniach pokrewnych) lub subtelnych regresji w zakresie zachowań związanych z bezpieczeństwem. Nigdy nie należy wdrażać dostrojonego modelu bez porównania go z modelem bazowym na reprezentatywnym zbiorze testowym.

Budowanie odłożonego zbioru testowego

Zbiór testowy musi być całkowicie oddzielony od danych treningowych i walidacyjnych — powinien zawierać przykłady, których model nie widział na żadnym etapie treningu. Zbiór testowy powinien odzwierciedlać pełny rozkład danych wejściowych z produkcji: typowe przypadki, przypadki brzegowe i dane wejściowe o charakterze adwersarialnym. W zadaniach związanych z wykonywaniem instrukcji należy uwzględnić przykłady wymagające zastosowania wszystkich aspektów instrukcji, a nie tylko tych najczęściej spotykanych. Zbiór testowy zawierający zwykle 100–500 przykładów wystarcza do wiarygodnej oceny.

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')

Ilościowe metryki oceny właściwe dla zadania

Należy wybrać metryki oceny dopasowane do zadania. W przypadku ekstrakcji JSON należy mierzyć współczynnik zgodności ze schematem oraz dokładność na poziomie pól. W przypadku klasyfikacji należy mierzyć dokładność, precyzję i czułość dla każdej klasy. W przypadku generowania tekstu należy użyć oceny LLM-as-judge do pomiaru jakości. W przypadku zgodności z formatem należy mierzyć dokładny współczynnik zgodności z formatem. Każdą metrykę należy obliczyć zarówno dla modelu bazowego, jak i dostrojonego, aby zmierzyć różnicę poprawy.

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

Ocena LLM-as-Judge

W przypadku zadań związanych z generowaniem otwartym należy użyć LLM-as-judge do oceniania wyników dostrojonego modelu względem oczekiwanych odpowiedzi. Należy poprosić GPT-4o o działanie w roli ewaluatora, przekazać dane wejściowe, oczekiwany wynik i wynik modelu, a następnie polecić ocenę poprawności, kompletności i zgodności z formatem w skali od 1 do 5. Średnia ocen dla całego zbioru testowego daje ogólną ocenę jakości. Należy porównać wynik dostrojonego modelu z wynikiem modelu bazowego na tym samym zbiorze testowym.

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)

Przeprowadzanie oceny porównawczej

Należy przeprowadzić bezpośrednią ocenę porównującą model bazowy, dostrojony model (oraz opcjonalnie silny punkt odniesienia oparty na odpowiednim promptowaniu) we wszystkich przypadkach testowych. Należy wygenerować wyniki każdego modelu dla każdego przypadku testowego, a następnie ocenić wszystkie wyniki za pomocą wybranych metryk. Należy przygotować tabelę porównawczą zawierającą wyniki metryk, odchylenia standardowe oraz przykłady sytuacji, w których dostrojony model wypada lepiej lub gorzej od punktu odniesienia.

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

Testy regresji pod kątem katastrofalnego zapominania

Dostrajanie może pogorszyć ogólne możliwości modelu — zjawisko to nazywa się katastrofalnym zapominaniem. Należy uruchomić zestaw testów regresji zarówno dla modelu bazowego, jak i dostrojonego, obejmujący zadania istotne poza zadaniem docelowym: odpowiadanie na pytania ogólne, rozumowanie, generowanie kodu i wykonywanie instrukcji. Jeśli dostrojony model uzyska w tych zadaniach znacznie niższe wyniki, ranga LoRA może być zbyt wysoka albo trening trwał zbyt wiele epok.

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

Konwertowanie do GGUF na potrzeby lokalnego wnioskowania

W przypadku lokalnego wdrożenia bez kosztownej infrastruktury GPU należy przekonwertować scalony model do formatu GGUF i uruchamiać wnioskowanie za pomocą llama.cpp. GGUF obsługuje różne poziomy kwantyzacji: Q4_K_M (4-bitowa, dobry kompromis między jakością a szybkością), Q8_0 (8-bitowa, jakość niemal pełna) oraz Q2_K (2-bitowa, bardzo szybka, ale zapewniająca niższą jakość). Model 7B po kwantyzacji Q4 wymaga zaledwie około 4 GB pamięci RAM i może działać na CPU z szybkością 1–5 tokenów na sekundę.

# 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'])

Udostępnianie modelu produkcyjnego za pomocą vLLM

W przypadku produkcyjnego udostępniania dostrojonego modelu na dużą skalę vLLM jest obecnie standardem. vLLM używa mechanizmu PagedAttention do wydajnego grupowania wielu żądań, znacznie zwiększając przepustowość GPU. Obsługuje punkty końcowe API zgodne z OpenAI, dzięki czemu może bezpośrednio zastąpić API OpenAI. Pojedynczy procesor GPU A100 uruchamiający vLLM z dostrojonym modelem 7B może obsłużyć setki żądań na minutę.

# 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)

Testy A/B dostrojonego modelu

Przed całkowitym przełączeniem środowiska produkcyjnego na dostrojony model należy przeprowadzić test A/B: skierować część ruchu produkcyjnego (na początek 5–10%) do dostrojonego modelu, podczas gdy większość nadal korzysta z modelu bazowego lub dotychczasowego podejścia opartego na promptach. Należy monitorować wyniki jakości, opóźnienia i metryki satysfakcji użytkowników dla obu grup. Udział ruchu obsługiwanego przez dostrojony model należy zwiększać dopiero wtedy, gdy test A/B potwierdzi poprawę po zebraniu statystycznie istotnej liczby żądań.

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

Utrzymywanie dostrojonych modeli w czasie

Dostrojone modele wymagają bieżącego utrzymania. Gdy model bazowy zostanie zaktualizowany (nowa wersja GPT-4o, nowe wydanie Mistral), wagi adaptera mogą stać się niekompatybilne i może być konieczne ponowne trenowanie. Gdy zmienią się wymagania dotyczące zadania, należy zaktualizować dane treningowe i ponownie przeprowadzić trening. Po wykryciu nowych rodzajów błędów w środowisku produkcyjnym należy dodać odpowiednie przykłady do zbioru treningowego. Należy zaplanować okresowe ponowne trenowanie jako część przepływu pracy związanego z produkcyjnymi operacjami ML.

Macierz decyzji dotyczących wdrożenia

Wybór strategii wdrażania dostrojonego modelu zależy od skali oraz ograniczeń infrastruktury. W przypadku małego wolumenu (mniej niż 1000 żądań dziennie) najprostsze jest API OpenAI do dostrajania. Przy średnim wolumenie (1000–100 000 dziennie) warto rozważyć vLLM na pojedynczej instancji GPU. W przypadku dużego wolumenu lub aplikacji krytycznych pod względem opóźnień należy użyć vLLM z wieloma procesorami GPU, rozważyć równoległość tensorową i dodać warstwę buforowania z przodu. W przypadku danych wrażliwych pod względem prywatności należy hostować model samodzielnie we własnej infrastrukturze, korzystając z GGUF/llama.cpp lub vLLM.

Szybki sprawdzian

Sprawdź swoją wiedzę na temat oceniania i wdrażania dostrojonych modeli z tego rozdziału.

Podsumowanie rozdziału

W tym rozdziale nauczyłeś się, że rygorystyczna ocena przed wdrożeniem, porównująca dostrojony model z modelem bazowym na odłożonych przypadkach testowych, jest niezbędna, testy regresji wykrywają katastrofalne zapominanie ogólnych możliwości spowodowane nadmierną specjalizacją, a opcje wdrażania obejmują zarówno zarządzane API OpenAI do dostrajania zapewniające prostotę, jak i vLLM do obsługi produkcyjnej o wysokiej przepustowości oraz GGUF/llama.cpp do lokalnego wnioskowania z użyciem CPU. Gratulacje z okazji ukończenia ścieżki AI Engineering!

Bezpłatny start

Ucz się Python dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
30
Lekcje
120

Często zadawane pytania

Czy lekcja „Ocena i wdrażanie dostrojonego modelu” jest bezpłatna?

Tak — pełny tekst „Ocena i wdrażanie dostrojonego modelu” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Ocena i wdrażanie dostrojonego modelu”?

Wykonaj ilościowe ewaluacje porównujące model bazowy i dostrojony na odłożonych przypadkach testowych, przekonwertuj model do GGUF na potrzeby lokalnego wnioskowania i udostępnij go za pomocą llama.c… Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?

Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Ocena i wdrażanie dostrojonego modelu”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?

Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Kiedy fine-tuning przewyższa promptowanie
  2. Przygotowanie wysokiej jakości zbioru treningowego
  3. Fine-tuning LoRA z Hugging Face PEFT
  4. Ocena i wdrażanie dostrojonego modelu
← Powrót do AI Engineering Academy