فاین‌تیون فارسی روی Qwen: درس‌های آزمایش اولیه

داده فارسی تمیز کم است، ارزیابی سخت است و نیم‌فاصله همه چیز را به هم می‌ریزد. تجربه ما از خط لوله داده تا گیت ارتقای مدل.

فاین‌تیون فارسی معمولا آن‌طور که در آموزش‌ها نوشته شده پیش نمی‌رود. دستور axolotl train را که بزنید، همه چیز اجرا می‌شود؛ مشکل جای دیگری است. سه چیز واقعا سخت‌اند: داده باکیفیت و بدون دردسر حقوقی کم است، خروجی فارسی را با معیار انگلیسی نمی‌شود سنجید، و نیم‌فاصله در هر مرحله از پیش‌پردازش می‌تواند بی‌سروصدا خراب شود.

این نوشته گزارش کارِ خط لوله داده و آزمایش‌های اولیه ماست روی خانواده Qwen. عمدا هیچ عدد نتیجه‌ای اینجا نمی‌آید: تا وقتی ارزیابی روی سخت‌افزار نهایی اجرا نشده، هر عددی که منتشر کنیم حدس است.

اول: شاید اصلا لازم نباشد

سخت‌ترین توصیه اول می‌آید. اگر مسئله شما «مدل فارسی خوب نمی‌فهمد» است، به احتمال زیاد فاین‌تیون جواب شما نیست.

فاین‌تیون رفتار را شکل می‌دهد، دانش را اضافه نمی‌کند. برای «مدل باید سیاست مرجوعی ما را بلد باشد» جوابْ بازیابی از سند است، نه آموزش. برای «مدل باید همیشه با این ساختار JSON جواب بدهد» جوابْ پرامپت خوب و خروجی ساختاریافته است. فاین‌تیون وقتی معنی می‌دهد که سبک، لحن و قالب پاسخ را در هزاران نمونه می‌خواهید، یا مدل پایه در زبان شما ضعیف است.

ما در دسته دوم بودیم و باز هم اول همه راه‌های ارزان‌تر را امتحان کردیم.

داده: مجوز قبل از کیفیت

اولین فیلتر ما کیفیت نبود، مجوز بود. یک مجموعه داده عالی که بند غیرتجاری دارد، برای محصول تجاری بی‌فایده است و بدتر، بدهی حقوقی است که تا آخر عمر مدل با شماست.

پس هر منبع را قبل از دانلود بررسی کردیم و رأی هر کدام در docs/data-catalog.md ثبت شد. چند مجموعه معروف فارسی از همین در بیرون ماندند؛ FarsInstruct، Matina و Maux-SFT-30k در مخلوط ما نیستند. این کار سبد داده را کوچک‌تر کرد و مجبورمان کرد بخش بیشتری از داده را خودمان بسازیم و پاک کنیم. معامله بدی نبود.

پاک‌سازی: ترتیب مراحل اهمیت دارد

متن فارسی وب سه بیماری همزمان دارد: کدگذاری خراب (mojibake)، حروف عربی به‌جای فارسی، و فاصله‌گذاری آشفته. ترتیبی که این‌ها را درمان می‌کنید، روی نتیجه اثر می‌گذارد.

خط لوله ما اول کدگذاری را تعمیر می‌کند، بعد اسکریپت فارسی را یکدست می‌کند:

import html
import unicodedata

import ftfy
import hazm

_HAZM_NORMALIZER = hazm.Normalizer()


def fix_encoding(s: str) -> str:
    s = ftfy.fix_text(s)
    s = html.unescape(s)
    return unicodedata.normalize("NFC", s)

ترتیب اینجا باربر است. hazm فرض می‌کند متنی که می‌گیرد از قبل درست decode شده؛ اگر اول normalize کنید و بعد mojibake را تعمیر کنید، بخشی از خرابی برای همیشه در داده می‌ماند.

انتخاب NFC هم عمدی است، نه NFKC. هر دو شکل‌های نمایشی عربی را جمع می‌کنند، ولی NFKC تمایزهایی را از بین می‌برد که در فارسی معنا دارند.

بعد از تعمیر، یکدست‌سازی اجرا می‌شود: نرمال‌سازی hazm، تبدیل کاف و یای عربی و تای گرد به شکل فارسی، حذف اعراب، و تبدیل ارقام عربی و فارسی به ASCII. آخری برای متن سایت غلط است، ولی برای داده آموزش لازم است. مدل نباید سه شکل مختلف عدد پنج را سه چیز جدا یاد بگیرد.

نیم‌فاصله، دشمن بی‌سروصدا

نیم‌فاصله (U+200C) در فارسی حرف نیست، ولی مرز کلمه است. «می‌رود» با نیم‌فاصله یک واژه است؛ با فاصله کامل، دو واژه بی‌ربط. توکنایزرهایی که روی متن عمدتا انگلیسی آموزش دیده‌اند، این نویسه را به شکل‌های ناسازگار می‌شکنند: گاهی همراه پیشوند، گاهی جدا.

سه چیز از این ماجرا یاد گرفتیم.

نیم‌فاصله را حذف نکنید. وسوسه‌انگیز است، چون همه چیز را ساده می‌کند و طول توکن را کم می‌کند. ولی مدل یاد می‌گیرد چیزی بنویسد که هیچ فارسی‌زبانی نمی‌نویسد.

یکدستش کنید. دنباله‌های تکراری، نیم‌فاصله بعد از فاصله و نیم‌فاصله اول و آخر کلمه را تمیز کنید تا یک مفهوم، یک بازنمایی داشته باشد.

آستانه‌های کیفیت را دوباره کالیبره کنید. فیلترهای آماده مثل Gopher و FineWeb برای انگلیسی تنظیم شده‌اند و فارسیِ چسبان با نیم‌فاصله، آمار متفاوتی دارد. آستانه‌ها را در یک فایل YAML جدا گذاشتیم تا بدون دست زدن به کد قابل تنظیم باشند:

DEFAULTS = {
    "min_doc_chars": 200,
    "mean_word_len_min": 1.2,
    "mean_word_len_max": 12.0,
    "frac_non_fa_alpha_max": 0.40,
    "frac_dup_lines_max": 0.50,
    "frac_short_lines_max": 1.0,
}

مثلا «میانگین طول واژه» با آستانه انگلیسی، متن سالم فارسی را دور می‌ریزد. هر آستانه‌ای که از یک مقاله انگلیسی کپی کنید، باید روی نمونه فارسی خودتان بازبینی شود.

بقیه مراحل استاندارد است و جای نوآوری ندارد: حذف تکراری‌های نزدیک با MinHash، شناسایی و پوشاندن داده شخصی، و حذف متن‌های قالبی سایت‌ها.

روش آموزش: کوچک شروع کنید

انتخاب ما QLoRA است روی مدل پایه کوانتیزه، با رتبه lora_r=32 و کف ۱۶. آموزش با Axolotl اجرا می‌شود و برای هر خانواده مدل یک فایل پیکربندی جدا داریم. ادغام آداپتور با mergekit انجام می‌شود.

مهم‌تر از این انتخاب‌ها، یک عادت است: قبل از هر اجرای بزرگ، یک اجرای دود روی کوچک‌ترین مدل خانواده. پیکربندی این مسیر برای Qwen3.5-0.8B و حدود صد نمونه فارسی آماده است. اجرای محلی فقط ساختار پیکربندی را می‌سنجد و آموزش واقعی باید روی Colab یا یک GPU دیگر اجرا شود.

هدف این اجرای کوتاه، سنجش کیفیت مدل نیست. باید پیش از اجرای اصلی، ناسازگاری قالب چت، فیلد خالی و مسیر ذخیره اشتباه را آشکار کند.

هر آرتیفکتی که بیرون می‌آید هم در MODELS.md ثبت می‌شود: مدل پایه، مجوز، هش SHA256، اندازه، فایل پیکربندی و مقصد ذخیره‌سازی. سه ماه بعد که کسی می‌پرسد «این وزن‌ها از کجا آمد»، جواب باید یک خط از یک جدول باشد، نه حافظه یک نفر.

تله‌ای که انتظارش را نداشتیم

آداپتوری که فقط با داده فارسی آموزش ببیند، توانایی فراخوانی ابزار مدل را تضعیف می‌کند. وزن‌های پایه یخ‌زده‌اند و انتظار می‌رود رفتار قبلی حفظ شود، ولی بازنمایی‌ها به سمت توزیع تازه می‌روند و مسیر تولید فراخوان ابزار آسیب می‌بیند. برای محصولی مثل دانا که هم فارسی می‌خواهد هم عاملِ ابزارزن، این یک اشکال جدی است؛ سندباکس اجرای کد دقیقا روی همین توانایی سوار است.

راه‌حل در دستور کار آموزش ثبت شده است. مخلوط داده باید پنج تا ده درصد ردپای واقعی استفاده از ابزار داشته باشد: نوبت‌های فارسی دور اسکیمای انگلیسی، هم تک‌نوبتی و هم چندنوبتی. کنارش یک گیت ادغام گذاشته‌ایم. افت نرخ موفقیت فراخوان ابزار نسبت به مدل پایه نباید از سه واحد درصد بیشتر شود.

نکته کلی‌اش این است: هر توانایی که در مخلوط داده نماینده ندارد، در معرض فراموشی است. اگر برایتان مهم است، باید در داده حضور داشته باشد.

ارزیابی: سخت‌ترین بخش

سنجش خروجی فارسی، کار سختی است. معیارهای خودکار انگلیسی‌محور اغلب نمی‌فهمند جمله فارسی طبیعی است یا ترجمه ماشینی بدقواره، و loss پایین‌تر هیچ چیزی درباره «فارسیِ روان» نمی‌گوید.

راه ما ترکیبی است. ParsBench معیار خودکار و تکرارپذیر است و برای ارتقای خودکار به کار می‌رود. کنارش، یک مدل داور روی حدود سیصد موضوع فارسی خروجی دو مدل را مقایسه می‌کند. داور کامل نیست، ولی وقتی مقایسه دوتایی می‌کند و موضوع‌ها ثابت‌اند، از هر معیار خودکاری به قضاوت انسانی نزدیک‌تر است.

گیت رفتن به تولید سه شرط دارد و هر سه باید همزمان برقرار باشند. ترجیح داور بالای پنجاه‌وپنج درصد. افت انگلیسی کمتر از سه واحد درصد. و هیچ تسکی بیش از دو واحد درصد بدتر نشود.

شرط دوم و سوم عمدی‌اند. بهتر کردن فارسی به قیمت خراب کردن انگلیسی و کدنویسی، ساده است و ما آن معامله را نمی‌خواهیم. یک مدل که فارسی‌اش کمی بهتر شده ولی در تولید کد افت کرده، برای کاربران ما پسرفت است.

اگر امروز شروع می‌کردیم

چهار چیز را زودتر انجام می‌دادیم. بررسی مجوز پیش از دانلود. کالیبره کردن آستانه‌های کیفیت روی نمونه فارسی، از روز اول. اجرای دود به‌عنوان عادت، نه استثنا. و ساختن ارزیابی قبل از اولین آموزش، نه بعدش.

آخری از همه مهم‌تر است. بدون معیار، فاین‌تیون تبدیل می‌شود به نگاه کردن به چند خروجی و گفتن «انگار بهتر شده». وقتی مدل روی دستگاه نهایی ارزیابی شد، اعداد را همین‌جا منتشر می‌کنیم.

اگر می‌خواهید مدل‌های فارسی دانا را امتحان کنید، از شروع سریع نخستین درخواست را بفرستید.

دانا را امتحان کنید
همین الان ثبت‌نام کنید، یک کلید API بسازید و اولین درخواست را بفرستید. شروع رایگان است.