KV Cache Locality: هزینه پنهان سرویسدهی LLM
خلاصهٔ کاملتر
وقتی یه درخواست به سرور LLM میرسه، مدل دو فاز داره: prefill که key-value پرامپت رو محاسبه میکنه (گرونترین بخش)، و decode که توکنهای خروجی رو یکییکی تولید میکنه. ابزارهایی مثل vLLM این KV cache رو در حافظه GPU نگه میدارن تا درخواستهای بعدی با پیشوند مشابه، مستقیم از decode شروع کنن و prefill رو skip کنن. روی CodeLlama 13B این فرق به ۱۸ms در برابر ۵۰۰ms میرسه؛ یعنی ۲۸ برابر اختلاف در time-to-first-token.
مشکل اینجاست که KV cache به هر GPU اختصاص داره. GPU 0 از cache GPU 3 خبر نداره. وقتی لود بالانسر از round-robin استفاده میکنه، درخواستها تقریباً تصادفی توزیع میشن و هر GPU باید prefill رو مستقل محاسبه کنه. در یه کلاستر ۸ GPU، فقط ۱۲.۵٪ درخواستها بهشانس به GPU درستی میرسن.
در بنچمارکهای واقعی روی ۸x A100 با CodeLlama 13B و ۳۰ کاربر همزمان، نتایج اینطوری بوده:
- Round-robin: نرخ cache hit: ۱۲.۵٪ | P99 TTFT: 6800ms | throughput: 36.3 req/s
- Prefix-aware routing: نرخ cache hit: ۹۷.۵٪ | P99 TTFT: 1000ms | throughput: 44.4 req/s
همون سختافزار، همون مدل، فقط تغییر در اینکه کدوم درخواست به کدوم GPU بره.
این بهبود ۲۲.۳٪ توی throughput به معنای واقعی کلمه پولساز یا پولسوزه. روی یه نود ۸ GPU با هزینه ۱۰ دلار در ساعت، round-robin هر ماه بین ۱۲۰۰ تا ۱۸۰۰ دلار GPU-hour رو هدر میده؛ فقط برای محاسبهای که قبلاً انجام شده.
سود این رویکرد با سه متغیر اصلی مقیاس میشه: اندازه مدل، طول prefix مشترک، و نسبت اشتراک prefix بین درخواستها. مدلهای ۱۳B تا ۷۰B بیشترین بهره رو دارن. مدلهای خیلی کوچیک (زیر ۸B) چون prefillشون اصلاً گرون نیست، overhead مسیریابی ممکنه سودش رو خنثی کنه. در مدل ۷۰B هم throughput کلی تغییر نمیکنه چون GPU اشباعه، ولی latency هر درخواست موفق بهطور محسوسی بهتره.
طول prefix هم مهمه. در ۸۱۹۲ توکن، یه cache miss حدود ۶۳۸ms طول میکشه در برابر ۴۴۸ms برای hit. در ۱۶۳۸۴ توکن این فاصله به ۸۱۷ms در برابر ۴۶۱ms میرسه. با بزرگتر شدن context window مدلها، این شکاف بیشتر میشه.
یه نکته مهم: prefix-aware routing میتونه load imbalance ایجاد کنه. اگه ۸۰٪ ترافیک یه system prompt مشترک داشته باشه، همهشون سمت یه GPU میرن. راهحل، یه fallback مبتنی بر load هست که وقتی درخواستهای در پرواز یه backend بیشتر از دو برابر میانگین شد، درخواست رو جای دیگهای میفرسته. این کار cache hit rate رو حدود ۵ درصد پایین میاره ولی P95 رو ۳۶٪ و P99 رو ۴۵٪ بهتر میکنه.
برای اندازهگیری وضعیت فعلی، اکثر deploymentهای vLLM متریکها رو از طریق Prometheus expose میکنن:
vllm:gpu_prefix_cache_hit_rate
# یا در نسخههای قدیمیتر:
vllm:gpu_prefix_cache_queries_total
vllm:gpu_prefix_cache_hits_total
اگه نسبت P99 به P50 بالای ۵ باشه، احتمالاً cache miss زیر load داری و prefix-aware routing میتونه کمک کنه.
نکات کلیدی:
- KV cache در LLMها per-GPU هست و round-robin routing این مزیت رو از بین میبره
- با prefix-aware routing روی CodeLlama 13B، throughput تا ۲۲٪ بالا رفت و P99 latency تا ۸۵٪ کاهش پیدا کرد
- مدلهای ۱۳B تا ۷۰B بیشترین بهره رو دارن؛ مدلهای زیر ۸B معمولاً سودی نمیبرن
- هرچه prefix مشترک بین درخواستها بلندتر باشه، صرفهجویی بیشتره (RAG یه use case ایدهآله)
- در sharing ratio حتی ۵۰٪ هم، prefix-aware routing به ۹۱٪ cache hit میرسه
- load-aware fallback برای جلوگیری از hot spot ضروریه و باید کنار prefix affinity باشه
- tail latency بهتر از throughput نشوندهنده تجربه واقعی کاربره




