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 (بتان، سريع جدًا لكن بجودة أقل). ولا يتطلب نموذج 7B مكمم بتنسيق Q4 سوى نحو 4GB من 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، ما يجعله بديلًا مباشرًا لواجهة OpenAI البرمجية. ويمكن لوحدة A100 GPU واحدة تعمل باستخدام 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 جديد)، قد لا تكون أوزان المحوّل متوافقة، وقد تحتاجون إلى إعادة التدريب. وعندما تتغير متطلبات مهمتكم، ستحتاجون إلى تحديث بيانات التدريب وإعادة التدريب. وعندما تكتشفون أنماط فشل جديدة في بيئة الإنتاج، أضيفوا أمثلة إلى مجموعة التدريب. خططوا لـإعادة التدريب الدورية كجزء من سير عمل عمليات تعلّم الآلة في بيئة الإنتاج.

مصفوفة قرار النشر

يعتمد اختيار استراتيجية نشر النموذج الذي ضبطتموه بدقة على حجم الاستخدام وقيود البنية التحتية لديكم. بالنسبة إلى الحجم المنخفض (أقل من 1000 طلب يوميًا)، تُعد واجهة برمجة تطبيقات الضبط الدقيق من OpenAI الخيار الأبسط. بالنسبة إلى الحجم المتوسط (من 1000 إلى 100,000 طلب يوميًا)، يمكنكم استخدام vLLM على مثيل GPU واحد. أما بالنسبة إلى التطبيقات عالية الحجم أو الحساسة لزمن الاستجابة، فاستخدموا vLLM مع عدة وحدات GPU، وفكروا في استخدام التوازي الموتر، وأضيفوا طبقة تخزين مؤقت أمامية. وبالنسبة إلى البيانات الحساسة للخصوصية، استضيفوا النموذج ذاتيًا على بنيتكم التحتية باستخدام GGUF/llama.cpp أو vLLM.

تحقق سريع

اختبروا مدى فهمكم لتقييم النماذج التي ضُبطت بدقة ونشرها في هذا الدرس.

مراجعة الدرس

تعلمتم في هذا الدرس أن التقييم الصارم قبل النشر، الذي يقارن النموذج المضبوط بدقة بالنموذج الأساسي على حالات اختبار محتجزة، أمر لا غنى عنه، وأن اختبار الانحدار يكشف النسيان الكارثي للقدرات العامة الناتج عن التخصص المفرط، وأن خيارات النشر تتراوح بين واجهة برمجة تطبيقات الضبط الدقيق المُدارة من OpenAI لتبسيط العملية، وvLLM لخدمة نماذج الإنتاج ذات معدل المعالجة العالي، وGGUF/llama.cpp للاستدلال المحلي القائم على وحدة المعالجة المركزية. تهانينا على إكمال مسار هندسة الذكاء الاصطناعي!

البدء مجانًا

تعلم 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 يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. متى يتفوق الضبط الدقيق على هندسة المطالبات
  2. إعداد مجموعة بيانات تدريب عالية الجودة
  3. الضبط الدقيق باستخدام LoRA وHugging Face PEFT
  4. تقييم النموذج المضبوط بدقة ونشره
← العودة إلى AI Engineering Academy