با انتشار مداوم مدلهای زبانی جدید، ادعاهای تبلیغاتی دربارهی توانایی آنها بیش از پیش گمراهکننده شده است. مقایسهی مدلها بدون آزمونهای تکرارپذیر و سنجش آنها صرفاً بر پایهی برداشتهای ذهنی، آسیبپذیری بزرگی برای پروژههای واقعی به همراه دارد؛ زیرا هر تغییری در نسخهی مدل یا پرامپتها میتواند بدون ثبت رسمی، موجب افت کیفیت در محیط عملیاتی شود.
راهحل این چالش، ساخت یک بستر ارزیابی اختصاصی (Evaluation Harness) است؛ سازوکاری کوچک و مستقل که وظیفهها را در قالب فایلهای استاندارد نگه میدارد، پاسخها را با توابع سنجش دقیق میآزماید و خروجی تابلوی امتیازات (Leaderboard) را در قالب JSON برای مقایسهی مداوم ذخیره میکند. در این ساختار، منطق وابستگی به مدل از چارچوب آزمون جدا میشود تا بتوان هر ارائهدهندهای را در چند ثانیه تعویض و مقایسه کرد.
اگر برای ارسال درخواست به مدلها از APIهای خارجی استفاده میکنید، توجه داشته باشید که پرداخت مستقیم با کارتهای بانکی ایرانی امکانپذیر نیست و برخی سرویسها دسترسی از نشانیهای اینترنتی ایران را محدود کردهاند. در چنین شرایطی میتوانید از مدلهای محلی سبک یا واسطهای سازگار با API استفاده کنید تا بستر ارزیابی بدون تغییر ساختاری به کار خود ادامه دهد.
مدلی که روی وظیفهای که نمیتوانید تعریفش کنید خوب عمل میکند، صرفاً از سر تصادف خوب عمل کرده است. بستر آزمایشی (harness) یعنی تعریف وظیفه، معیار ارزیابی، اجراکننده و تابلوی امتیازات؛ همه در قالبی جمعوجور و تعویضپذیر.
اهداف یادگیری
- تعریف وظیفه (Task) در قالب یک فایل JSONL با فیلدهای
prompt،targets،metricو فیلد اختیاریextrasبهازای هر نمونه. - پیادهسازی پنج معیار ارزیابی: تطابق دقیق (Exact Match)، معیار rouge-l F1، بررسی با اجرای کد، چندگزینهای و وجود زیررشته.
- ساخت یک اجراکننده که نمونههای هر وظیفه را دستهبندی (batch) کرده و به یک تطبیقدهنده (Adapter) مدل با قابلیت تعویض میفرستد.
- تولید خروجی JSON تابلوی امتیازات، شامل امتیازهای تفکیکی هر وظیفه، تاخیر (Latency) و میانگین کلی با قابلیت بازتولید.
صورتمسئله
هر هفته مدل زبانی جدیدی از راه میرسد. ادعای بازاریابی همهشان این است که عملکرد درخشانی دارند. پرسش صادقانه این است: عملکرد درخشان در چه کاری؟ پاسخ صادقانه همان تابلوی امتیازاتی است که خودتان نوشتهاید؛ چون تابلوی ارائهدهندهی مدل بر اساس معیارهایی تنظیم شده که خودشان مدل را برایش بهینهسازی کردهاند.
بدون داشتن بستر آزمایشی در مخزن کدتان، دو مدل را بر اساس حسوحال مقایسه میکنید. با داشتن این بستر، آنها را بر اساس امتیاز روی مجموعهوظیفهای مشخص با معیاری ثابت و روی خروجی JSON قابل مقایسه (diff) میسنجید. بستر آزمایشی، قراردادی میان اجرای دیروز و اجرای امروز شماست؛ بدون آن، افتهای عملکردی بدون متوجه شدن شما سر از محیط عملیاتی (پروداکشن) درمیآورند.
دام اصلی، بیشبرازش (overfitting) بستر آزمایشی روی یک مدل خاص است. راهحل این است که همهی تصمیمهای مختص یک مدل را از بستر آزمایشی بیرون نگه دارید: بستر آنقدر جمعوجور باشد که در 15 دقیقه خوانده شود؛ وظیفهها آنقدر سبک باشند که بشود آنها را در خود مخزن کد گنجاند؛ معیارها از پایه پیادهسازی شوند تا همکارتان بتواند بررسیشان کند؛ و تطبیقدهنده تنها جایی باشد که کد وابسته به مدل در آن قرار میگیرد. تطبیقدهنده را عوض کنید، تابلوی امتیازات جابهجا میشود؛ وظیفهها را عوض کنید، تابلوی امتیازات جابهجا میشود؛ هیچ چیز دیگری نباید تکان بخورد.
مفهوم
مشخصات وظیفه
هر نمونه یک سطر در فایل JSONL است:
{"id": "arith-00", "prompt": "compute: 2 + 2", "targets": ["4"], "metric": "exact_match"}
برای معیارهایی که به دادههای کمکی برای نمرهدهی نیاز دارند، extras این اطلاعات جانبی را حمل میکند:
{
"id": "code-00",
"prompt": "python: write a function f that doubles its input",
"targets": ["ok"],
"metric": "code_exec",
"extras": {"io_pairs": [[1, 2], [3, 6]]}
}
هر وظیفه یک فایل .jsonl در مسیر outputs/tasks/ است. نام فایل همان نام وظیفه است و همهی نمونههای داخل فایل از یک معیار مشترک استفاده میکنند.
پنج وظیفهی نمونه
| وظیفه | معیار | هدف آزمون |
|---|---|---|
| arithmetic | exact_match | درستی در سطح توکن برای پاسخی قطعی |
| summary | rouge_l | امتیاز F1 طولانیترین زیردنبالهی مشترک در برابر خلاصهی مرجعِ تکسطری |
| code-exec | code_exec | آزمون با اجرای کد: تابع پیشبینیشده باید مجموعهای از جفتهای ورودی-خروجی را برآورده کند |
| multiple-choice | multiple_choice | حرف اول پاسخ پیشبینیشده باید با یکی از حروف مجاز تطابق داشته باشد |
| generation | substring_contains | متن آزاد تولیدشده باید حداقل یکی از زیررشتههای هدف را دربرداشته باشد |
قرارداد معیار ارزیابی
هر معیار تابعی با امضای (prediction, targets, extras) -> float in [0.0, 1.0] است. بستر آزمایشی میانگین امتیاز تکتک نمونهها را میگیرد تا امتیاز وظیفه مشخص شود، سپس میانگین امتیاز وظیفهها را محاسبه میکند تا امتیاز کلی به دست آید. توابع معیار بسیار کوتاهاند:
exact_match: تبدیل به حروف کوچک، فشردهسازی فاصلهها و بررسی برابری.substring_contains: همان نرمالسازی و بررسی وجود زیررشته.multiple_choice: بزرگکردن حرف اول و بررسی تطابق.rouge_l: تقسیم طول LCS بر طول پیشبینی و طول مرجع، و محاسبهی F1 حاصل از دقت (Precision) و بازخوانی (Recall).code_exec: اجرای پیشبینی در یک فضای نام (namespace) محدود، فراخوانیf(x)روی تکتک جفتهای ورودی-خروجی، و شمارش موارد منطبق.
معیار code_exec پیشبینی را در فضای نامی با توابع توکار (builtins) محدودشده اجرا میکند. تستهای این درس تضمین میکنند که دستور import os با شکست روبهرو شود؛ چون os در فضای نام وجود ندارد و کد پیشبینیشده به هیچوجه نمیتواند به سیستم فایل دسترسی پیدا کند.
تطبیقدهندهی مدل
class ModelAdapter(Protocol):
def generate(self, prompts: Sequence[str]) -> List[str]: ...
@property
def name(self) -> str: ...
تطبیقدهنده مرز تفکیک (seam) است. در این درس، ما ToyAdapter را قرار دادهایم؛ تطبیقدهندهای بر پایهی الگویاب قطعی که پاسخ درست را برای همهی پرامپتهای پنج وظیفهی نمونه برمیگرداند. یک تطبیقدهندهی واقعی، مدل را فراخوانی کرده و خروجیاش را تحویل میدهد؛ برای بستر آزمایشی فرقی میان این دو نیست.
اجراکننده
تابع run_task پرامپتها را در دستههایی به اندازهی batch_size پردازش کرده و نتیجه را به تابع معیار میفرستد. run_leaderboard تمام وظیفهها را پیمایش میکند و از آنها میانگین میگیرد. write_leaderboard خروجی را با قید رشتهی ساختار (schema string) در قالب JSON ذخیره میکند تا تغییرات احتمالیِ فرمت در آینده، داشبوردها را بیسرصدا از کار نیندازد.
پیادهسازی
فایل code/main.py همان دستسازه (Artifact) قابل اجراست.
مرحلهی 1: مقداردهی اولیهی وظیفههای نمونه
تابع seed_fixture_tasks(target_dir) پنج فایل .jsonl را مینویسد. اولین اجرای main.py در صورت خالی بودن پوشه، این فایلها را میسازد.
مرحلهی 2: بارگذاری وظیفهها
تابع load_all_tasks(task_dir) تکتک فایلهای .jsonl را میخواند و دیکشنریای از نام وظیفه به لیستی از رکوردهای Example برمیگرداند. خطوط توضیحی که با # شروع میشوند و خطوط خالی نادیده گرفته میشوند تا توسعهدهندگان بتوانند فایلها را یادداشتگذاری کنند.
مرحلهی 3: پیادهسازی معیارها
هر معیار تابعی کوچک همراه با آزمون واحد (unit test) است. مجموعهی آزمونهای این درس شامل 13 حالت آزمایشی برای پوشش نرمالسازی، همپوشانی جزئی، اجرای کد و رد کدهای ناامن است.
مرحلهی 4: نوشتن اجراکننده
تابع run_task روی دستهها پیمایش کرده و یک TaskResult شامل امتیاز، تعداد پاسخهای درست، تعداد کل و تاخیر تولید میکند. run_leaderboard همهی وظیفهها را بررسی میکند و یک Leaderboard با میانگین کلی خروجی میدهد.
مرحلهی 5: تولید خروجی JSON
تابع write_leaderboard جدول را سریالیسازی میکند. پرچم --include-per-example رکوردهای تفکیکی هر نمونه را ذخیره میکند تا وقتی امتیازها تغییر کردند، بتوانید پیشبینیها را با اجرای قبلی مقایسه (diff) کنید.
اجرای برنامه:
python3 code/main.py
این اسکریپت در اولین اجرا فایلهای نمونه را میسازد، آنها را با تطبیقدهندهی آزمایشی (که همهی نمونهها را درست حل میکند) میسنجد و نتیجه را در outputs/leaderboard.json ذخیره میکند. امتیاز کلی با تطبیقدهندهی آزمایشی 1.0 است؛ آزمون تطبیقدهندهی ساختگی (stub) در test_main.py نشان میدهد وقتی تطبیقدهنده نتواند پاسخ دهد، همین بستر امتیاز 0.0 تولید میکند.
شیوهی استفاده
برای اتصال یک مدل واقعی، کافی است یک تطبیقدهنده بنویسید. ساختار آن به این شکل است:
class HttpAdapter:
name = "vendor.v1"
def __init__(self, endpoint, api_key):
self.endpoint = endpoint
self.api_key = api_key
def generate(self, prompts):
out = []
for prompt in prompts:
response = http_post(self.endpoint, prompt, self.api_key)
out.append(response["text"])
return out
در ابتدای تابع main()، کلاس ToyAdapter را با HttpAdapter تعویض کنید. بستر آزمایشی، وظیفهها، معیارها و تابلوی امتیازات دستنخورده باقی میمانند.
سه الگویی که هنگام استفاده از این بستر در پروژههای واقعی باید پیاده کنید:
- فایلهای وظیفه را تثبیت (Pin) کنید. فایل leaderboard.json باید هش محتوای وظیفهها را در خود داشته باشد یا فایلهای JSONL را کنار خود نگه دارد؛ وگرنه با تغییر فایل وظیفه امتیازها جابهجا میشوند و نمیفهمید تغییر از طرف مدل بوده یا دادهها.
- بهجای اکتفا به امتیاز، خودِ پیشبینیها را مقایسه کنید. پرچم
--include-per-exampleبه شما اجازه میدهد ببینید مدل دقیقاً در روزی که امتیازش افت کرد، چه خروجیای داده است. - اندازهی دستهها را محدود کنید. تطبیقدهندههای واقعی محدودیت نرخ درخواست (Rate limit) دارند. یک اندازهی دستهی کوچک باعث میشود بستر با سرویسدهندههای مختلف سازگار بماند.
تحویل پروژه
فایل outputs/skill-lm-eval-harness.md حاوی دستورالعمل کامل است: مشخصات وظیفه در قالب JSONL، پنج معیار، تطبیقدهندهی تعویضپذیر، اجراکنندهی دستهای و JSON تابلوی امتیازات همراه با رشتهی ساختار. فایلهای وظیفه در outputs/tasks/ همان فایلهای نمونه هستند؛ آنها را برای شروع کار به پروژههای واقعیتان منتقل کنید.
تمرینها
- وظیفهی ششمی اضافه کنید و برای آن یک معیار اختصاصی از پایه بنویسید (همپوشانی شبیه به BLEU، امتیازدهی بر اساس مرجع شبیه به BLEURT، یا هر معیاری با قرارداد مشخص).
- معیار
code_execرا طوری توسعه دهید که خروجی استاندارد (stdout) را دریافت کند و لیستی از stdoutهای مورد انتظار را بهعنوان هدف بپذیرد. - یک دستور مقایسهی تابلوی امتیازات اضافه کنید: با دریافت دو فایل
leaderboard.json، نمایش دهد که امتیاز کدام وظیفهها و به چه میزان جابهجا شده است. - تاخیر هر نمونه را محدود کنید. فراخوانی تطبیقدهنده را در یک مهلت زمانی (timeout) قرار دهید؛ ستون مجزایی برای
timeoutsدر تابلوی امتیازات نمایش دهید. - محتوای وظیفهها را با هش sha256 در تابلوی امتیازات تثبیت کنید تا هر بررسیکنندهای در آینده بتواند مطمئن شود ارزیابی دقیقاً روی همان وظیفهها انجام شده است.
اصطلاحات کلیدی
| اصطلاح | در گفتوگوها | در عمل |
|---|---|---|
| مشخصات وظیفه (Task spec) | «فرمت ارزیابی» | فایل JSONL با فیلدهای prompt ،targets ،metric و فیلد اختیاری extras برای هر نمونه |
| معیار (Metric) | «شیوهی نمرهدهی» | تابعی از (prediction, targets, extras) به یک عدد اعشاری در بازهی [0, 1] |
| تطبیقدهنده (Adapter) | «کلاینت مدل» | شیئی با متد generate(prompts) -> list[str]؛ تنها بخش کدی که به یک مدل خاص وابستگی دارد |
| تابلوی امتیازات (Leaderboard) | «جدول نتایج» | خروجی JSON شامل امتیاز هر وظیفه، تعداد کل، تاخیر و میانگین کلی |
| معیار اجرای کد (Code exec metric) | «اجرا کن و ببین» | اجرای پاسخ پیشبینیشده در یک فضای نام محدود و مقایسهی خروجی با جفتهای ورودی-خروجی |
برای مطالعهی بیشتر
- مخزن اصلی lm-evaluation-harness بهعنوان مرجع محیط عملیاتی (پروداکشن)؛ بسیار حجیمتر است اما ساختار یکسانی دارد.
- کتابخانهی lighteval از HuggingFace بهعنوان پیادهسازی دیگری از همین قرارداد.
- درس 46 از فاز 19 الگوهای انباشت گرادیان (Gradient Accumulation) را در پشتهی آموزشی که این بستر ارزیابی میکند، پوشش میدهد.
- درس 47 از فاز 19 ساختار چکپوینت (Checkpoint) مورد ارزیابی را شرح میدهد؛ هش چکپوینت را در تابلوی امتیازات ثبت کنید.
- درس 48 از فاز 19 پشتهی آموزش توزیعشده (Distributed Training) را بررسی میکند که مدلِ تحتآزمون را تولید کرده است.
