آموزش تنظیم num_ctx در Ollama برای افزایش طول کانتکست
مقدار پیشفرض num_ctx در Ollama اغلب باعث قطع شدن پرامپتهای طولانی میشود. با تنظیم دستی این پارامتر در Modelfile یا API، محدودیت حافظه KV cache را مدیریت کنید.
عملکرد num_ctx و دلیل قطع شدن پرامپتهای طولانی
طول کانتکست در Ollama تعداد توکنهایی است که مدل بارگذاریشده میتواند بهطور همزمان در حافظه نگه دارد و num_ctx گزینهای است که این مقدار را تعیین میکند. Ollama بهصورت پیشفرض مقداری را انتخاب میکند که بسیار کمتر از حداکثر ظرفیت اعلامشده توسط مدل است؛ بنابراین پرامپتهای طولانی پیش از آنکه توسط مدل خوانده شوند، قطع میشوند. هیچ بخشی از پاسخ سیستم، شما را از وقوع این اتفاق مطلع نمیکند.
مدل Llama 3.1 8B در کتابخانه مدلهای Ollama با پنجره کانتکست 128k لیست شده است. یک سرور معمولی این مقدار را در اختیار شما قرار نمیدهد. مستندات خود Ollama در صفحات مختلف مقادیر پیشفرض متفاوتی را ذکر کردهاند: بخش FAQ عدد 4096 توکن را بیان میکند، مرجع Modelfile میگوید num_ctx بهطور پیشفرض روی 2048 تنظیم شده است و صفحه طول کانتکست میگوید مقدار پیشفرض بر اساس VRAM (حافظه ویدیویی) موجود انتخاب میشود: 4k برای کمتر از 24 GiB، 32k برای بازه 24 تا 48 GiB و 256k برای مقادیر بالاتر از آن. هر یک از این موارد در نسخههای خاصی از نرمافزار صادق بودهاند. این تناقض، درس مفیدی به همراه دارد: بهجای اعتماد به هر صفحهای، از جمله همین صفحه، مقدار واقعی را از سرور در حال اجرای خود بخوانید.
قطع شدن متن (Truncation) بهصورت بیسروصدا رخ میدهد، زیرا مدل همچنان پاسخ میدهد و پاسخ نیز از نظر متنی منسجم به نظر میرسد. این پاسخ از بخش انتهایی ورودی شما تولید شده است. خلاصهای که نیمه اول یک سند را از دست داده باشد، ممکن است به اشتباه نشاندهنده ضعف مدل تلقی شود، در حالی که معمولاً دلیل آن کوچک بودن پنجره کانتکست است.
بررسی طول کانتکست Ollama که واقعاً روی سرور شما اعمال شده است
بررسی که روی هر build جواب میدهد prompt_eval_count است؛ یعنی تعداد توکنهای prompt که سرور گزارش میدهد پردازش کرده است. اگر بیش از ظرفیت کانتکست، داده ارسال کنید، این عدد روی همان حد مجاز متوقف میشود.
sudo apt update && sudo apt install -y jq
LONG=$(python3 -c "print('the quick brown fox jumps over the lazy dog. ' * 2000)")
jq -n --arg p "$LONG" '{model:"llama3.1:8b", prompt:$p, stream:false, options:{num_ctx:4096}}' |
curl -s http://localhost:11434/api/generate -d @- |
jq '{prompt_eval_count, prompt_eval_duration}'آن prompt حدود 18,000 کلمه است، یعنی بسیار بیشتر از 4096 توکن. مقدار prompt_eval_count به جای نزدیک شدن به تعداد واقعی توکنها، عددی نزدیک به 4096 برمیگرداند، زیرا سرور باقی دادهها را حذف کرده است. دوباره آن را با "num_ctx":16384 اجرا کنید تا ببینید عدد افزایش مییابد. اگر build شما به جای کوتاه کردن (truncate) پیام خطا میدهد، نتیجه همان است، فقط با هشداری صریحتر.
ollama psستون CONTEXT در buildهایی که آن را چاپ میکنند، طول کانتکستی را نشان میدهد که مدل بارگذاریشده در حال حاضر با آن اجرا میشود. ستون PROCESSOR در کنار آن، محل قرارگیری مدل را نمایش میدهد. مقدار 100% CPU در یک VPS بدون GPU طبیعی است. تقسیمبندی به صورت 30%/70% CPU/GPU در یک سیستم دارای GPU به این معنی است که وزنهای مدل به همراه cache دیگر در VRAM جا نمیشوند و دلیل معمول آن، افزایش num_ctx است.
journalctl -u ollama --no-pager | grep -i n_ctx | tail -n 5موتور inference اندازه کانتکست خود را در خطی شامل n_ctx چاپ میکند. عبارت دقیق ممکن است در releaseهای مختلف تغییر کند، بنابراین اگر این خط را ندیدید، آن را به حساب تغییر نام بگذارید، نه به عنوان مدرکی برای یک مشکل خاص.
چهار مکان برای تنظیم num_ctx
در درخواست. مقدار "options": {"num_ctx": 16384} را به /api/generate یا /api/chat ارسال کنید. این تنظیم بر تمام تنظیمات دیگر اولویت دارد و فقط برای همان فراخوانی اعمال میشود. اگر این مقدار با مقداری که مدل بارگذاریشده با آن در حال اجراست تفاوت داشته باشد، سرور ابتدا مدل را مجدداً بارگذاری میکند که میتوانید آن را در load_duration در پاسخ مشاهده کنید: این مقدار از نزدیک صفر به چند ثانیه جهش میکند.
در نشست تعاملی. داخل ollama run، عبارت /set parameter num_ctx 16384 را تایپ کنید. این تنظیم برای همان نشست باقی میماند.
در Modelfile. این کار مقدار را در یک مدل نامگذاریشده تثبیت میکند، بنابراین هر کلاینتی بدون نیاز به تغییر در سمت خود، آن را دریافت میکند.
FROM llama3.1:8b
PARAMETER num_ctx 16384ollama create llama3.1-16k -f ./Modelfile
ollama run llama3.1-16kدر سرور. پارامتر OLLAMA_CONTEXT_LENGTH مقدار پیشفرض را برای هر درخواستی که num_ctx اختصاصی خود را ندارد، تعیین میکند. در systemd، به جای ویرایش فایل unit، یک فایل drop-in اضافه کنید.
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_CONTEXT_LENGTH=16384"sudo systemctl daemon-reload
sudo systemctl restart ollama
ollama psاولویتبندی زمانی اهمیت پیدا میکند که در حال عیبیابی کلاینت شخص دیگری هستید. درخواستی که حاوی num_ctx باشد، بر پیشفرض سرور غلبه میکند؛ بنابراین یک رابط کاربری چت یا عاملی (agent) که مقدار کوچکی را به صورت خودکار ارسال میکند، تغییرات شما در systemd را بیاثر میسازد. هنگامی که یک عامل کدنویسی را به سرور Ollama خود متصل میکنید، پیش از مقصر دانستن سرور، بررسی کنید که کلاینت چه مقداری ارسال میکند.
چرا نمیتوانید num_ctx را صرفاً روی حداکثر مقدار مدل تنظیم کنید
مکانیزم Attention باعث میشود هر توکن، تمام توکنهای پیش از خود را بررسی کند. کلیدها (keys) و مقادیر (values) محاسبهشده برای توکنهای قبلی ذخیره میشوند تا برای هر توکن جدید دوباره محاسبه نشوند؛ این فضای ذخیرهسازی، KV cache (کش کلید/مقدار) نام دارد. این فضا در زمان بارگذاری مدل برای کل num_ctx تخصیص مییابد، نه بهصورت تدریجی با رشد گفتگو. بنابراین، یک context بزرگ حتی برای یک پرامپت تکخطی نیز همان مقدار حافظه را اشغال میکند.
آموزش هزینه استنتاج در DigitalOcean محاسبات ریاضی آن را در یک خط بیان میکند:
kv_bytes_per_token = 2 * layers * kv_heads * head_dim * bytes_per_valueعدد 2 در فرمول، کلیدها و مقادیر را بهصورت جداگانه میشمارد. سایر اعداد را از مشخصات مدل خود استخراج کنید.
curl -s http://localhost:11434/api/show -d '{"model":"llama3.1:8b"}' |
jq '.model_info | {ctx: ."llama.context_length", layers: ."llama.block_count", heads: ."llama.attention.head_count", kv_heads: ."llama.attention.head_count_kv", embed: ."llama.embedding_length"}'مدل Llama 3.1 8B دارای 32 لایه و 8 هد کلید/مقدار است. ابعاد هد از تقسیم embed بر heads بهدست میآید، یعنی در اینجا 4096 / 32 = 128؛ برخی مدلها این مقدار را مستقیماً بهعنوان llama.attention.key_length منتشر میکنند. کش پیشفرض مقادیر را با فرمت f16 ذخیره میکند، بنابراین bytes_per_value برابر با 2 است و حاصلضرب 2 32 8 128 2 برابر با 131,072 بایت میشود. این یعنی 128 KiB کش برای هر توکن از context. این مقدار را در طول context ضرب کنید تا هزینه از حالت انتزاعی خارج شود.
The data behind this chart
[
{
"label": "4k",
"kv_cache_gib": 0.5,
"total_ram_gib": 5.1
},
{
"label": "8k",
"kv_cache_gib": 1,
"total_ram_gib": 5.6
},
{
"label": "16k",
"kv_cache_gib": 2,
"total_ram_gib": 6.6
},
{
"label": "32k",
"kv_cache_gib": 4,
"total_ram_gib": 8.6
},
{
"label": "64k",
"kv_cache_gib": 8,
"total_ram_gib": 12.6
},
{
"label": "128k",
"kv_cache_gib": 16,
"total_ram_gib": 20.6
}
]آن 6 ردیف، حاصل محاسبات ریاضی فرمول بالاست، نه اندازهگیریهای تجربی. ستون مجموع، حجم دانلود 4.9 گیگابایتی که کتابخانه Ollama برای llama3.1:8b در اوت 2026 لیست کرده (یعنی 4.6 GiB) را اضافه میکند و بافرهای محاسباتی و خودِ پردازش سرور را در نظر نمیگیرد. این اعداد را بهعنوان حداقلِ کفِ حافظه در نظر بگیرید.
نکته اصلی در مقیاسبندی است. در 8k، هزینه کش برابر با 1 GiB است که در مقایسه با وزنهای مدل ناچیز است. در حداکثر ظرفیت 128k مدل، این هزینه به 16 GiB میرسد که بیش از سه برابر وزنهای مدل است و مجموع آن به نزدیک 20.6 GiB میرسد. بنابراین، یک VPS با 4 گیگابایت رم نمیتواند این مدل را با هیچ context مفیدی بارگذاری کند. یک VPS با 8 گیگابایت رم برای 8k مناسب است. یک VPS با 16 گیگابایت رم به 32k میرسد و همچنان برای سایر پردازشهای سرور فضا باقی میماند. هر یک از این آستانهها با افزایش وزنهای مدل جابهجا میشوند؛ بنابراین اگر در حال بررسی یک مدل بزرگتر در مقایسه با این 8B هستید، همان محاسباتی که برای تگ Qwen 27B روی یک VPS فقط با CPU انجام شد، نشان میدهد که بین 8 تا 64 گیگابایت رم، چه مقدار فضای اندکی برای context باقی میماند.
وقتی KV cache در حافظه جا نمیشود چه اتفاقی میافتد
در یک VPS که فقط از CPU استفاده میکند، حجم پردازش بهسادگی افزایش مییابد. در حین بارگذاری مدل و اجرای یک درخواست طولانی، آن را زیر نظر بگیرید.
free -m
ps -eo rss,comm --sort=-rss | head -n 5مقدار RSS (حافظه مقیم) بر حسب کیلوبایت نمایش داده میشود. اگر میزان swap استفادهشده در free -m شروع به افزایش کرد، مقدار context را کاهش دهید. اگر KV cache در swap قرار بگیرد، تولید هر توکن ممکن است چندین ثانیه طول بکشد، زیرا برای هر توکن جدید، کل cache باید خوانده شود.
اگر حافظه سرور بهطور کامل تمام شود، هسته سیستمعامل (kernel) بزرگترین پردازش را انتخاب کرده و آن را متوقف (kill) میکند.
sudo dmesg | grep -i "killed process"خطی که عبارت Out of memory: Killed process 1234 (ollama) را نشان میدهد به این معنی است که context درخواستی شما در حافظه جا نشده است. Ollama معمولاً پیش از رسیدن به این مرحله درخواست را رد میکند و عملیات با پیامی که میزان حافظه مورد نیاز را در مقایسه با حافظه آزاد نشان میدهد، با خطا مواجه میشود.
در سرورهای مجهز به GPU، این خطا بیسروصداتر رخ میدهد. لایهها به حافظه RAM سیستم منتقل میشوند، ollama ps تقسیم بار بین CPU و GPU را نشان میدهد و نرخ خروجی (throughput) بهشدت افت میکند. میزان این افت به سختافزار شما بستگی دارد؛ بنابراین بهجای اعتماد به اعداد و ارقام ماشینهای دیگر، تعداد توکن در ثانیه را روی سیستم خودتان اندازهگیری کنید و این کار را برای هر تنظیمات context تکرار کنید.
زمان پیشپردازش (Prefill) سریعتر از طول پرامپت افزایش مییابد
پیشپردازش (Prefill) به عملیاتی گفته میشود که روی ورودی شما پیش از تولید اولین توکن خروجی انجام میگیرد. هر توکن در پرامپت به تمام توکنهای پیش از خود توجه (attend) میکند، بنابراین حجم کل عملیات با توان دوم طول ورودی افزایش مییابد. دو برابر کردن طول پرامپت، زمان انتظار برای دریافت اولین توکن را بیش از دو برابر میکند.
پاسخ ارائهشده حاوی اندازهگیریهای مربوطه است، بنابراین نیازی نیست به حدس و گمان تکیه کنید.
jq -n --arg p "$LONG" '{model:"llama3.1:8b", prompt:$p, stream:false, options:{num_ctx:16384}}' |
curl -s http://localhost:11434/api/generate -d @- |
jq '{tokens: .prompt_eval_count, prefill_seconds: (.prompt_eval_duration/1000000000)}'این کار را یک بار با یک پرامپت کوتاه و بار دیگر با یک پرامپت طولانی اجرا کنید، سپس در هر مورد تعداد توکنها را بر ثانیه تقسیم کنید. در یک VPS که فقط از CPU استفاده میکند، پیشپردازش معمولاً کندترین بخش یک درخواست با کانتکست طولانی است و عددی که برای توکن بر ثانیه از یک پرامپت کوتاه به دست میآید، نمیتواند این زمان را پیشبینی کند.
همزمانی (Concurrency) جایی است که این موضوع بیشترین آسیب را میزند. هر درخواستی که در حال پردازش است به حافظه کش اختصاصی خود نیاز دارد، بنابراین حافظه موجود در نمودار بالا به ازای هر درخواست است، نه به ازای هر سرور؛ و یک درخواست طولانی میتواند منابع سیستم را اشغال کند در حالی که درخواستهای کوتاه در صف انتظار میمانند. مقدار OLLAMA_NUM_PARALLEL را با دقت تنظیم کنید و پیش از افزایش همزمان هر دو عدد، مقاله تعداد کاربران همزمانی که یک LLM خودمیزبان میتواند سرویسدهی کند را مطالعه کنید.
بازیابی حافظه با استفاده از کش کوچکتر
bytes_per_value در فرمول، تنظیمی است که شما کنترل میکنید. بخش FAQ در Ollama مستندات مربوط به OLLAMA_KV_CACHE_TYPE را ارائه میدهد که در آن f16 به عنوان مقدار پیشفرض 2 بایت، به همراه q8_0 با 1 بایت و q4_0 برای مقادیر کمتر از آن ذکر شده است. تغییر به q8_0 حجم کش را نصف میکند، بنابراین ردیف 32k به جای 4 گیگابایت، تنها 2 گیگابایت فضا اشغال میکند. همان بخش FAQ به OLLAMA_FLASH_ATTENTION=1 نیز اشاره دارد که برخی از بیلدها برای اعمال کش کوانتیزه (quantised) به آن نیاز دارند.
[Service]
Environment="OLLAMA_FLASH_ATTENTION=1"
Environment="OLLAMA_KV_CACHE_TYPE=q8_0"به جای فرض کردن، تأیید کنید: سرویس را ریاستارت کنید، مدل را با همان num_ctx قبلی بارگذاری نمایید و میزان RSS را مقایسه کنید. پشتیبانی از این قابلیت به مدل و backend بستگی دارد، بنابراین اگر تغییری مشاهده نکردید، یعنی ترکیب فعلی شما از این قابلیت پشتیبانی نمیکند. مستندات این گزینهها را بدون تضمین کیفیت خروجی فهرست کردهاند، پس پیش از تکیه بر آنها، q4_0 را با پرامپتهای خود تست کنید. اگر این تنظیمات دلیل حضور شما در اینجا هستند، Ollama و llama.cpp این موارد را به شکل متفاوتی ارائه میدهند.
دستورالعملی برای انتخاب num_ctx
- حداکثر کانتکست مدل، تعداد لایهها و تعداد headهای key/value آن را از
/api/showبخوانید. - مقدار بایت به ازای هر توکن را با فرمول محاسبه کرده و در کانتکست مورد نظر خود ضرب کنید.
- حجم وزنها (weight size) را اضافه کنید، آن را با رم آزاد مقایسه کرده و حداقل 1 GiB را برای سایر پردازشهای سیستم کنار بگذارید.
- مقدار را تنظیم کنید، مدل را بارگذاری نمایید و سپس با
ollama psوprompt_eval_countتأیید کنید که چه مقداری اعمال شده است. - بار کاری واقعی خود را اجرا کنید و در حین کار
free -mرا زیر نظر بگیرید؛ اگر swap شروع به فعالیت کرد، کانتکست را نصف کنید.
بیشتر کارها به کانتکست کمتری نسبت به آنچه کاربران اختصاص میدهند نیاز دارند. خلاصهسازی یک گزارش طولانی در 16k جا میشود. یک رابط بازیابی اطلاعات که پنج تکه از اسناد را جایگذاری میکند، بهندرت از 8k فراتر میرود. یک عامل برنامهنویسی که فایلهای کامل را میخواند، موردی است که واقعاً به 64k یا بیشتر نیاز دارد و در این حالت باید سختافزار را بر اساس کانتکست انتخاب کنید، نه برعکس. اگر سرور هنوز جدید است، از یک نصب Ollama روی VPS شروع کنید و پس از اینکه مدلها بهدرستی بارگذاری شدند، کانتکست را تنظیم کنید.
FAQ
طول متن پیشفرض در Ollama چقدر است؟
این موضوع به نسخه و سختافزار بستگی دارد، بنابراین بهجای فرض کردن، آن را بررسی کنید. بخش FAQ در مستندات Ollama به 4096 توکن اشاره دارد، مرجع Modelfile مقدار پیشفرض num_ctx را 2048 ذکر کرده است، و صفحه مربوط به طول متن (context length) بیان میکند که مقدار پیشفرض بر اساس VRAM موجود انتخاب میشود: 4k برای کمتر از 24 GiB، 32k برای 24 تا 48 GiB، و 256k برای مقادیر بالاتر. یک VPS که فقط از CPU استفاده میکند، در محدوده کوچک قرار میگیرد. دستور ollama ps در نسخههایی که این ستون را دارند، طول متن اعمالشده را چاپ میکند و prompt_eval_count در پاسخ API، این مقدار را در تمام نسخهها تأیید میکند.
چرا Ollama ابتدای پرامپت طولانی من را نادیده میگیرد؟
زیرا طول پرامپت از پنجره متن (context window) بیشتر بوده است، بنابراین سرور پیش از آنکه مدل آن را ببیند، بخشی از آن را حذف کرده و هیچ خطایی نیز بازنگردانده است. همان پرامپت را دوباره با یک num_ctx بزرگتر ارسال کنید و رشد prompt_eval_count را در پاسخ مشاهده کنید. اگر این عدد تغییر نکرد، چیزی بین شما و سرور در حال تنظیم num_ctx است که این اتفاق در رابطهای کاربری چت و فریمورکهای عامل (agent frameworks) رایج است.
یک num_ctx بزرگتر به چه مقدار RAM اضافی نیاز دارد؟
طول متن را در هزینه کش به ازای هر توکن ضرب کنید که برابر با 2 * layers * kv_heads * head_dim * bytes_per_value است. برای مدل Llama 3.1 8B با دقت f16، این مقدار 128 KiB به ازای هر توکن است؛ بنابراین 32k توکن هزینهای معادل 4 GiB و 128k کامل هزینهای معادل 16 GiB علاوه بر وزنهای مدل دارد. کش هنگام بارگذاری مدل تخصیص مییابد، بنابراین یک num_ctx بزرگ، حتی زمانی که پرامپتهای شما کوتاه هستند، آن مقدار حافظه را اشغال میکند.
آیا پنجره متن بزرگتر باعث کندی Ollama میشود؟
بله، از دو طریق. زمان پیشپردازش (prefill) با مجذور طول پرامپت افزایش مییابد، بنابراین یک ورودی طولانی، اولین توکن را بیش از آنچه طولش نشان میدهد، به تأخیر میاندازد. کش بزرگتر همچنین برای حافظه رقابت میکند: در سیستمهای دارای GPU، لایهها را به RAM سیستم میراند و در سیستمهای دارای CPU، دستگاه را به سمت استفاده از swap سوق میدهد. یک num_ctx بزرگ که هرگز آن را پر نمیکنید، همچنان هزینه حافظه دارد، هرچند زمان پیشپردازش را افزایش نمیدهد.
آیا میتوانم num_ctx را بهطور دائمی برای یک مدل تنظیم کنم؟
بله. یک Modelfile حاوی FROM llama3.1:8b و PARAMETER num_ctx 16384 بنویسید و سپس ollama create llama3.1-16k -f ./Modelfile را اجرا کنید. هر کلاینتی که درخواست llama3.1-16k کند، بدون ارسال هیچ گزینهای، آن مقدار متن را دریافت میکند. درخواستی که num_ctx خاص خود را داشته باشد همچنان اولویت دارد، بنابراین این کار یک مقدار پیشفرض تعیین میکند، نه یک سقف محدودکننده.