تفاوت KV cache و prompt cache در مدلهای زبانی
تفاوت کلیدی KV cache و prompt cache را درک کنید. KV cache حافظه RAM سرور شما برای هر درخواست است، در حالی که prompt caching قابلیتی برای کاهش هزینه و تاخیر در API است.
تفاوت KV cache و prompt cache: پاسخ کوتاه
KV cache و prompt cache ارائهدهنده، تنها در یک کلمه مشترک هستند و تقریباً هیچ شباهت دیگری ندارند. KV cache حافظهٔ کاری برای هر درخواست است. این حافظه در طول عمر یک درخواست در RAM یا VRAM سرور شما قرار میگیرد و با افزایش طول context و تعداد درخواستهای همزمان، رشد میکند. Prompt caching ارائهدهنده، یک قابلیت مربوط به هزینهها و تأخیر (latency) است. یک پیشوند ثابت از prompt شما روی سرورهای ارائهدهنده ذخیره میشود و هنگام ارسال مجدد، با تخفیف محاسبه میگردد.
یکی حافظهای است که به عنوان سختافزار خریداری میکنید. دیگری حافظهای است که شخص دیگری نگهداری میکند و بابت آن از شما اجاره میگیرد.
تفاوت عملی، اهمیت بیشتری نسبت به تعریف آن دارد. ممکن است ظرفیت KV cache شما تمام شود؛ در این صورت مدل بارگذاری نمیشود یا درخواست رد خواهد شد. ظرفیت prompt cache هرگز تمام نمیشود. تنها ممکن است در استفاده از آن موفق نباشید (cache hit رخ ندهد) که در این حالت، به سادگی هزینه کامل را پرداخت خواهید کرد.
محتوای KV cache و دلیل وجود آن
یک مدل transformer که در حال تولید توکن شماره 500 است، باید به تمام 499 توکن پیش از خود توجه (attend) کند. برای هر یک از آن توکنها، هر لایه به یک بردار کلید (key vector) و یک بردار مقدار (value vector) نیاز دارد. محاسبهٔ مجدد همهٔ آنها برای هر توکن جدید باعث میشود که زمان تولید با مربع طول توالی رشد کند؛ بنابراین runtime آنها را ذخیره میکند. این فضای ذخیرهسازی همان KV cache (کش کلید/مقدار) است.
این کش یک وضعیت مختص به هر درخواست (per-request state) است، زیرا دقیقاً از توالی توکنهای همان درخواست ساخته شده است. دو کاربر که promptهای متفاوتی میفرستند نمیتوانند از آن به صورت مشترک استفاده کنند، مگر اینکه runtime از قابلیت prefix caching استفاده کند که یک ویژگی مجزا است و در ادامه توضیح داده شده است.
سرویسدهی در دو مرحله انجام میشود. مرحلهٔ Prefill کل prompt شما را میخواند و کش را پر میکند و محدود به توان پردازشی (compute) است. مرحلهٔ Decode هر بار یک توکن تولید کرده و به کش اضافه میکند و محدود به پهنای باند حافظه (memory bandwidth) است. همین تفکیک باعث میشود که سرعت پردازش prompt و سرعت تولید توکن هنگام اندازهگیری توکن بر ثانیه روی سیستم خودتان متفاوت گزارش شود.
حافظه KV cache چقدر فضا اشغال میکند؟
به دنبال جدول فروشنده نباشید. حجم آن یک محاسبه ریاضی است که میتوانید برای هر مدلی دوباره انجام دهید:
bytes per token = 2 * layers * kv_heads * head_dim * bytes per elementعدد 2 نشاندهنده key و value است. تمام اعداد دیگر از config.json مدل میآیند که در صفحه Hugging Face مدل منتشر شده است.
مدل Llama 3.1 8B را در نظر بگیرید. فایل پیکربندی آن num_hidden_layers را 32 و num_key_value_heads را 8 ذکر کرده است. hidden_size برابر با 4096 که بین 32 هد توجه (attention head) تقسیم شده، ابعاد هر هد را 128 به دست میدهد. در حالت f16، هر عنصر 2 بایت است:
2 * 32 * 8 * 128 * 2 = 131072 bytes = 128 KiB per tokenاین مقدار را در context مورد نظر خود و سپس در تعداد درخواستهایی که همزمان اجرا میکنید، ضرب کنید.
The data behind this chart
[
{
"label": "2k context",
"one_request_gib": 0.25,
"four_requests_gib": 1
},
{
"label": "8k context",
"one_request_gib": 1,
"four_requests_gib": 4
},
{
"label": "32k context",
"one_request_gib": 4,
"four_requests_gib": 16
},
{
"label": "128k context",
"one_request_gib": 16,
"four_requests_gib": 64
}
]در context 8k، حافظه cache برابر با 1 گیگابایت است. در 32k این مقدار 4 گیگابایت است که در همان محدوده وزنهای 4-bit قرار دارد. در حداکثر context مدل یعنی 128k، این مقدار برای یک درخواست 16 گیگابایت و اگر چهار درخواست همزمان آن را پر کنند، 64 گیگابایت خواهد بود. وزنها تغییر نکردند، فقط حافظه cache تغییر کرد.
قابلیت Grouped query attention (GQA) در این عدد نقش مهمی دارد. مدل Llama 3.1 8B دارای 8 هد key/value است که به 32 هد query سرویس میدهند، بنابراین چهار هد query از یک جفت key/value ذخیرهشده مشترک استفاده میکنند. مدلی که num_key_value_heads آن با num_attention_heads برابر باشد، در تعداد پارامتر یکسان، چهار برابر بیشتر از حافظه cache استفاده میکند. پیش از آنکه فرض کنید هزینه سرویسدهی دو مدل 8B یکسان است، حتماً آن فیلد را بررسی کنید.
چرا مدلی که در 2k اجرا میشد، در 32k بارگذاری نمیشود
دلیل این است که زمان اجرا (runtime) هنگام بارگذاری مدل، حافظه کش KV را رزرو میکند؛ اندازه این حافظه بر اساس طول متنی (context length) که پیکربندی کردهاید تعیین میشود، نه بر اساس پرامپتی که واقعاً ارسال میکنید. پنجره متنی پیشفرض در Ollama برابر با 4096 توکن است. اگر آن را به 32k افزایش دهید، پیش از آنکه حتی یک توکن دریافت شود، درخواست تخصیص 4 گیگابایت حافظه اضافی کردهاید.
OLLAMA_CONTEXT_LENGTH=32768 ollama serveهمین تنظیم برای هر نشست، از طریق پرامپت تعاملی:
ollama run llama3.1:8b
/set parameter num_ctx 32768خطا در هر پشته (stack) متفاوت ظاهر میشود. vLLM محاسبات را در زمان شروع بررسی کرده و از اجرا امتناع میکند:
ValueError: The model's max seq len (131072) is larger than the maximum number of tokens that can be stored in KV cache (78336). Try increasing `gpu_memory_utilization` or decreasing `max_model_len` when initializing the engine.در یک VPS که فقط از CPU استفاده میکند، چنین بررسیای وجود ندارد، زیرا تخصیص حافظه از نوع RAM معمولی سیستم است. در این حالت، مکانیزم OOM killer (قاتل کمبود حافظه) در هسته سیستمعامل، پردازش را متوقف میکند و شواهد آن را در بافر حلقه هسته (kernel ring buffer) باقی میگذارد:
dmesg -T | grep -i "killed process"وجود خطی که نام پردازش سرویسدهنده شما را ذکر میکند، به این معناست که سرور بیش از حافظه موجود، قول تخصیص داده است. راهحل، کاهش طول متن است، نه بزرگتر کردن فایل swap؛ زیرا کش KV که روی دیسک صفحهبندی (page) شده باشد، برای هر توکن تولیدشده خوانده میشود و سرعت تولید به قدری کاهش مییابد که عملاً غیرقابل استفاده خواهد بود. انتخاب یک عدد منطقی در راهنمای ما درباره num_ctx و طول متن در Ollama پوشش داده شده است.
تأثیر همروندی بر تعداد
هر درخواست در حال پردازش، KV cache اختصاصی خود را حمل میکند. این نکتهای است که اکثر برنامهریزیهای ظرفیت از آن غافل میشوند. چهار کاربر که هر کدام 32k متن (context) در اختیار دارند، علاوه بر وزنهای مدل، به 16 گیگابایت حافظه نیاز دارند.
محیطهای اجرا (Runtimes) در میزان سختگیری نسبت به این موضوع متفاوت هستند. Ollama و llama.cpp فضای متنی (context) درخواستی شما را هنگام بارگذاری مدل رزرو میکنند؛ بنابراین حافظه چه استفاده شود و چه نشود، اشغال شده است. vLLM این استخر حافظه را به بلوکهایی با اندازه ثابت تقسیم کرده و با رشد هر درخواست، آنها را تخصیص میدهد؛ بنابراین یک درخواست 500 توکنی فقط به اندازه 500 توکن فضا اشغال میکند. در هر دو حالت، استخر حافظه محدود است و پس از پر شدن، درخواستهای جدید به جای اجرا شدن، در صف قرار میگیرند. تأثیر این صف بر زمان پاسخدهی در تعداد کاربرانی که یک LLM خودمیزبان میتواند همزمان سرویسدهی کند بررسی شده است.
چهار روش برای کاهش حجم KV cache
- کاهش طول context. این بزرگترین اهرم است و معمولاً کمهزینهترین روش محسوب میشود. اکثر بارهای کاری چت هرگز به نزدیکی 32k نمیرسند.
- کوانتایز کردن (Quantise) خودِ cache. مقدار پیشفرض
OLLAMA_KV_CACHE_TYPEدر Ollama برابر باf16است و ازq8_0نیز پشتیبانی میکند که حدود نصف حافظه را مصرف میکند، و همچنینq4_0که حدود یکچهارم حافظه را اشغال میکند. معادلهای این موارد در llama.cpp عبارتند از-ctk q8_0و-ctv q8_0. - انتخاب مدلی با تعداد سر (head) یا لایههای key/value کمتر. پیش از دانلود 40 GB وزن (weights)،
config.jsonرا مطالعه کنید. - سرویسدهی به تعداد درخواستهای همزمان کمتر و قرار دادن بقیه در صف.
در q4_0، مقدار حافظه برای Llama 3.1 8B از 128 KiB به ازای هر توکن به حدود 32 KiB کاهش مییابد؛ بنابراین 32k از context به جای 4 GiB، حدود 1 GiB هزینه دارد. این صرفهجویی بدون هزینه نیست. کلیدها و مقادیر با دقت کمتری ذخیره میشوند، بنابراین پیش از نهایی کردن، خروجی را با promptهای خودتان مقایسه کنید.
مزیت واقعی کش کردن پرامپت در سرویسدهنده چیست
کش کردن پرامپت در سرویسدهنده، محصولی متفاوت با واحد محاسباتی متفاوت است. شما یک پیشوند ثابت را مشخص میکنید، سرویسدهنده آن را ذخیره میکند و فراخوانیهای بعدی که دقیقاً همان پیشوند را تکرار کنند، به جای قیمت کامل ورودی، با نرخ کاهشیافته محاسبه میشوند.
ضرایب منتشرشده توسط Anthropic تا اوت 2026 به این شرح است: هزینه نوشتن در کش با ماندگاری 5 دقیقه، 1.25 برابر قیمت پایه توکن ورودی است. هزینه نوشتن برای 1 ساعت، 2 برابر و هزینه خواندن از کش، 0.1 برابر است. اگر یک پرامپت سیستمی 20,000 توکنی را با این اعداد محاسبه کنید، ماهیت این معامله مشخص میشود.
The data behind this chart
[
{
"label": "No caching, every call",
"billed_token_equivalents": "20,000"
},
{
"label": "First call, 5 minute cache write",
"billed_token_equivalents": "25,000"
},
{
"label": "First call, 1 hour cache write",
"billed_token_equivalents": "40,000"
},
{
"label": "Every later call, cache hit",
"billed_token_equivalents": "2,000"
}
]آن را به صورت محاسبات ریاضی در نظر بگیرید. هزینه اضافی نوشتن برای 5 دقیقه در اولین فراخوانی معادل 5,000 توکن است: 25,000 در مقابل 20,000 برای ارسال بدون کش. هر فراخوانی بعدی در آن بازه زمانی، 2,000 محاسبه میشود به جای 20,000، که 18,000 توکن صرفهجویی به همراه دارد. بنابراین، کش 5 دقیقهای از فراخوانی دوم به بعد سودآور است.
کش 1 ساعته یک شرطبندی متفاوت است. این کش در زمان نوشتن 40,000 هزینه دارد که معادل 20,000 توکن اضافه است، بنابراین برای سودآور شدن به دو بار استفاده در آن ساعت نیاز دارد. این موضوع به الگوی ترافیک شما مربوط است، نه به مدل. محاسبات کامل، از جمله نحوه انتخاب بازه زمانی، در محاسبات نقطه سربهسر برای کش کردن پرامپت Claude آمده است.
دو جزئیات تعیین میکنند که آیا اصلاً از کش استفاده میکنید یا خیر. اول، پیشوندی که کمتر از حداقل طول مجاز مدل باشد، بدون اطلاع قبلی کش نمیشود: تا اوت 2026، حداقل مقدار مستندشده برای Claude Opus 5 برابر با 512 توکن و برای Claude Sonnet 5 برابر با 1,024 توکن است و درخواستهای کوتاهتر به صورت عادی و بدون بازگرداندن خطا پردازش میشوند. دوم، طول عمر کش از شروع درخواستی که ورودی را مینویسد یا میخواند محاسبه میشود و هر بار خواندن، آن را بدون هزینه اضافی تمدید میکند. بنابراین، یک endpoint پرکار، کش 5 دقیقهای را بهطور نامحدود زنده نگه میدارد. endpointای که هر ده دقیقه یکبار فراخوانی میشود، هر بار هزینه اضافی نوشتن را میپردازد و هرگز از مزایای آن بهرهمند نمیشود.
به جای فرض کردن، پاسخ را بررسی کنید. شیء usage مقادیر cache_creation_input_tokens و cache_read_input_tokens را گزارش میدهد. تعداد خواندن صفر در هر فراخوانی به این معنی است که شما فقط هزینه نوشتن را میپردازید و هیچ بازگشتی دریافت نمیکنید.
جایی که دو حافظه پنهان با هم تلاقی میکنند
یک پرامپت سیستمی طولانی، نقطهای است که این دو با هم تلاقی میکنند و هزینه آن را از هر دو طرف بهطور همزمان پرداخت میکنید.
بهصورت محلی (Local)، یک پرامپت سیستمی 20,000 توکنی حدود 2.4 گیگابایت از حافظه KV cache را روی سرور Llama 3.1 8B با فرمت f16 اشغال میکند و این کار را برای هر درخواست همروندی که شامل آن باشد، بهصورت جداگانه انجام میدهد. از راه دور (Remote)، همان پیشوند (prefix) هزینه یک بار نوشتن در حافظه پنهان و سپس 0.1 برابر ورودی در هر فراخوانی بعدی را دارد. هزینه محلی با تعداد کاربران شما مقیاسپذیر است. هزینه راه دور با ترافیک شما مقیاسپذیر است و در زمانهای بیکاری (idle) بازنشانی میشود.
یک قابلیت محلی وجود دارد که شبیه به کش کردن پرامپت در سمت ارائهدهنده (provider prompt caching) است و دائماً با آن اشتباه گرفته میشود: کش کردن پیشوند (prefix caching). مستندات vLLM، کش کردن خودکار پیشوند را به عنوان کش کردن «حافظه KV کوئریهای موجود، بهطوری که یک کوئری جدید بتواند در صورت اشتراک پیشوند با یکی از کوئریهای موجود، مستقیماً از حافظه KV استفاده مجدد کند» توصیف میکند. سرور llama.cpp بهصورت پیشفرض برای هر اسلات یک کش پرامپت نگه میدارد و --cache-reuse N کوچکترین قطعهای را که برای استفاده مجدد تلاش میکند، تعیین مینماید.
آنچه کش کردن پیشوند ذخیره میکند، توان پردازشی مرحله پیشپر کردن (prefill compute) است. پرامپت سیستمی 20,000 توکنی شما بهجای پردازش در هر درخواست، یک بار پردازش میشود که زمان رسیدن به اولین توکن را بهشدت کاهش میدهد. در vLLM، بلوکهای مشترک بهجای کپی شدن، مورد استفاده مجدد قرار میگیرند، بنابراین مصرف حافظه نیز بهبود مییابد. آنچه این قابلیت هرگز انجام نمیدهد، کوچک کردن حافظه پنهانی است که باید برای توکنهای فعال فعلی نگه دارید. نگه داشتن وزنها (weights) در حافظه بین درخواستها، یک اهرم مرتبط اما جداگانه است که در نگه داشتن مدل Ollama در حافظه بین درخواستها به آن پرداخته شده است.
چه مواردی را روی سرور خود اندازهگیری کنید
مدل را در context مورد نظر خود بارگذاری کنید و سپس بهجای اعتماد به تخمینها، اعداد واقعی را بخوانید.
ollama ps
nvidia-smi --query-gpu=memory.used,memory.total --format=csv
free -gollama ps مدل بارگذاریشده را به همراه اندازه آن و اینکه آیا روی GPU اجرا میشود یا CPU، فهرست میکند. اگر مدلی که انتظار داشتید کاملاً روی GPU قرار بگیرد، گزارش split روی CPU میدهد، به این معناست که KV cache بخشی از آن را به بیرون رانده است و سرعت تولید متن به همان نسبت کاهش مییابد. nvidia-smi مقدار دقیق VRAM را نشان میدهد و free -g همین کار را برای یک VPS که فقط از CPU استفاده میکند، انجام میدهد. context را مرحلهبهمرحله افزایش دهید، مدل را دوباره بارگذاری کنید و تغییر اعداد را مشاهده کنید. محاسبات شما و عدد گزارششده باید به هم نزدیک باشند. وقتی اینطور نیست، این اختلاف معمولاً ناشی از بافرهای محاسباتی خودِ runtime است، نه خطایی در فرمول.
اگر این اعداد شما را به سمت سختافزاری سوق میدهند که ترجیح میدهید اجاره نکنید، مقایسه آن با پرداخت هزینه به ازای هر توکن در مقایسه GPU VPS با توکنهای API بررسی شده است.
FAQ
آیا KV cache همان prompt caching است؟
خیر. KV cache حافظهای مختص هر درخواست در داخل پردازش سرویسدهنده است که بردارهای کلید (key) و مقدار (value) را برای هر توکن در context فعلی نگه میدارد. این حافظه در RAM یا VRAM شما قرار دارد و با پایان درخواست آزاد میشود. قابلیت prompt caching در سرویسدهندهها، یک ویژگی مربوط به صورتحساب است که پیشوند (prefix) ثابتِ prompt را روی زیرساخت سرویسدهنده ذخیره میکند و هنگام ارسال مجدد آن، هزینه کمتری دریافت میکند. تمام شدن فضای KV cache باعث توقف بارگذاری مدل میشود، اما استفاده نکردن از prompt cache تنها منجر به افزایش صورتحساب و زمان رسیدن به اولین توکن (time to first token) میشود.
چرا مدل من در context 2k بارگذاری میشود اما در 32k شکست میخورد؟
زیرا runtime کل فضای KV cache را در زمان بارگذاری تخصیص میدهد؛ این فضا بر اساس طول context پیکربندیشده تعیین میشود، نه بر اساس prompt ارسالی شما. برای مدل Llama 3.1 8B با دقت f16، حافظه کش به ازای هر توکن 128 KiB است؛ بنابراین 2k از context به 0.25 GiB و 32k به 4 GiB فضا نیاز دارد. وزنهای مدل در هر دو حالت جا میشوند، اما رزرو حافظه است که با شکست مواجه میشود. vLLM این وضعیت را با یک ValueError گزارش میدهد که حداکثر تعداد توکنهای قابل ذخیرهسازی را مشخص میکند و پیشنهاد میدهد gpu_memory_utilization را افزایش یا max_model_len را کاهش دهید. در سیستمهای فقط CPU، قابلیت out-of-memory killer در هسته سیستمعامل، پردازش را متوقف میکند که میتوانید آن را با dmesg -T | grep -i "killed process" تأیید کنید.
چگونه اندازه KV cache را برای مدل خود محاسبه کنم؟
عدد 2 را در تعداد لایهها، تعداد سرهای کلید/مقدار (key/value heads)، ابعاد سر (head dimension) و تعداد بایت به ازای هر عنصر ضرب کنید. این محاسبه، بایت به ازای هر توکن را به شما میدهد. سپس آن را در طول context و تعداد درخواستهای همزمان ضرب کنید. تعداد لایهها و سرها را از فایل config.json مدل بخوانید. برای f16 یا bf16 از 2 بایت به ازای هر عنصر استفاده کنید. حافظه q8_0 تقریباً نصف این مقدار و q4_0 تقریباً یکچهارم آن است.
آیا prompt caching حافظه مورد نیاز سرور شخصی من را کاهش میدهد؟
قابلیت prompt caching سرویسدهنده هیچ تأثیری بر سختافزار شما ندارد، زیرا فضای ذخیرهسازی در سمت سرویسدهنده است. معادل محلی آن prefix caching است که توسط vLLM و سرور llama.cpp ارائه میشود. این قابلیت، بردارهای کلید و مقدارِ از پیش محاسبهشده را برای یک پیشوند مشترک بازاستفاده میکند که باعث صرفهجویی در محاسبات prefill و کاهش زمان رسیدن به اولین توکن میشود. در vLLM، بلوکهای مشترک به جای کپی شدن، بازاستفاده میشوند، بنابراین مصرف حافظه نیز بهبود مییابد. هیچکدام از این ویژگیها، حافظه کش مورد نیاز برای توکنهای در حال پردازش (in flight) را کاهش نمیدهند، بنابراین محاسبات شما برای context و همزمانی (concurrency)، همچنان کفِ مصرف حافظه را تعیین میکند.
آیا کش کردن promptی که فقط یک بار ارسال میکنم ارزش دارد؟
خیر. هزینه نوشتن در کش بیشتر از ورودی عادی است؛ طبق نرخهای اوت 2026، این هزینه برای گزینه 5 دقیقهای 1.25 برابر نرخ پایه است، بنابراین پیشوندی که هرگز در آن بازه زمانی دوباره ارسال نمیکنید، مستقیماً باعث ضرر میشود. کش کردن زمانی سودآور است که همان پیشوند تکرار شود، مانند یک system prompt طولانی یا سندی که قرار است چندین سوال درباره آن بپرسید. برای اطمینان از اینکه به جای پرداخت هزینه نوشتن، hit دریافت میکنید، cache_read_input_tokens را در پاسخ API بررسی کنید.