ChatGPT’ye uzun bir prompt gönderdiniz, yanıt gelmeye başlıyor. Her kelime birer birer ekrana düşüyor. Perde arkasında ne oluyor?
Model, yeni her kelimeyi üretmek için tüm önceki bağlamı yeniden okumak zorunda. Transformer mimarisinin attention mekanizması böyle çalışır: her yeni token, kendisinden önce gelen tüm tokenlara bakarak çıktı üretir. 10 token’lık bir yanıt için bu hesap 10 kez yapılır; 1000 token’lık yanıt için 1000 kez. Üstelik her seferinde aynı hesapların büyük kısmı tekrar ediyor.
KV cache bu tekrarı keser. Key ve Value matrislerini GPU belleğinde tutarak sonraki her token için yalnızca gerçekten yeni olan hesabı yapar. Görünürde küçük bir optimizasyon gibi dursa da üretim ortamında çıkarım hızı ve maliyet üzerindeki etkisi belirleyicidir.

Attention Mekanizması: Q, K, V Üçgeni
Transformer modellerinin kalbinde self-attention katmanları yer alır. Her dikkat katmanı, girişteki her token için üç farklı vektör hesaplar:
- Query (Q): O token’ın “ne aradığını” kodlar
- Key (K): Diğer token’ların “ne sunduğunu” temsil eder
- Value (V): O token’dan gerçekte alınacak bilgiyi taşır
Attention(Q, K, V) = softmax(QKᵀ / √d_k) · V
Her token, tüm önceki token’ların Key vektörleriyle karşılaştırılarak bir ağırlık dizisi üretir. Bu ağırlıklar Value vektörlerine uygulanınca o token’ın bağlamdan ne çıkaracağı belirlenir.
Kritik nokta şu: 500. token’ı üretirken, önceki 499 token’ın Key ve Value vektörlerinin zaten hesaplanmış olması gerekir. Bu vektörler saklanmazsa her yeni token için baştan hesaplanır. 500 token’lık bir yanıtta bu, yaklaşık 1 + 2 + 3 + … + 499 = 124.750 tekrarlayan hesap demektir.
Dikkat miktarı girişle karesel büyür, dolayısıyla uzun yanıtlarda hesaplama yükü hızla tırmanır. Bu yükün büyük kısmı da aslında daha önce yapılmış hesapların yeniden yapılmasından ibarettir.
KV Cache Tam Olarak Ne Yapar?
KV cache bu hesaplama israfını durdurur. Mantık basittir: bir token’ın Key ve Value vektörlerini bir kez hesapla, GPU belleğinde sakla, bir daha hesaplama.
LLM çıkarım süreci iki ayrı aşamada işler:
Prefill aşaması: Girişteki tüm token’lar bir kerede işlenir. Kullandığınız sistem promptu ya da kullanıcının yazdığı mesaj bu aşamada ele alınır. GPU paralelleştirmesinden tam olarak yararlanılır; tüm token’lar aynı anda matris çarpımına girer. Sonuçta elde edilen K ve V vektörleri belleğe yazılır.
Decode aşaması: Model token’ları tek tek üretir. Her yeni token için yalnızca o token’ın K ve V değerleri hesaplanır. Önceki tüm token’ların değerleri önbellekten okunur. Bu aşama bellek bant genişliğiyle sınırlıdır; GPU hesaplama kapasitesinden değil, VRAM okuma hızından darboğaz oluşur.
KV cache olmadan decode aşaması her adımda tüm geçmişi yeniden hesaplar. 100 token’lık giriş için 100 token’lık çıktı üretiliyorsa, hesaplama maliyeti O(n²) büyür. KV cache ile bu O(n) düzeyine iner.
Bellek açısından bakılınca her katman, her token için iki matris (K ve V) tutar. Büyük modellerde 1000 token’lık bağlam birkaç GB bellek tüketebilir. Bu yüzden context window büyüdükçe KV cache boyutu ciddi bir kısıt haline gelir.
Prefix Caching: Aynı Başlangıcı Defalarca İşleme
KV cache mantığını bir adım öteye taşıyan bir yöntem var: prefix caching.
Birçok uygulamada her kullanıcı isteği aynı sistem promptuyla başlar. “Sen XYZ şirketinin müşteri destek asistanısın. Daima kibarca yanıt ver. Aşağıdaki kurallara uy: [uzun liste]…” gibi bir sistem promptunu her istek için baştan işlemek kaynak israfıdır.
Prefix caching, bu sabit ön ekin KV değerlerini bir kez hesaplayıp cache’de tutarak sonraki tüm isteklerde yeniden kullanılmasını mümkün kılar. Aynı ön ekle başlayan yeni bir istek geldiğinde prefill aşaması bu kısmı atlayıp doğrudan asıl sorguya geçer.
Büyük API sağlayıcıları bu optimizasyonu kullanıcılara doğrudan yansıtıyor:
- Anthropic Prompt Caching: En az 1024 token eşiğine ulaşan sabit içerikler cache’leniyor. Cache’e giren tokenlar için normal fiyatın %10’u ödenirken, cache’den okunanlar %90 indirimli fiyatlanıyor.
- OpenAI Cached Input Tokens: Tekrarlayan giriş prefix’leri otomatik tanınıp cache’leniyor; ek yapılandırma gerektirmiyor.
- Google Context Caching (Gemini API): En az 32.768 token içerik için 1 saate kadar cache seçeneği var.
2000 token’lık sabit sistem promptu kullanan bir müşteri destek botu her sorguda 100 token’lık kullanıcı sorusu alıyor. Prefix caching olmadan her istek 2100 token’lık giriş ücreti keser. Prefix caching devrede olduğunda ise yalnızca 100 token işleme ücreti ödenir; geri kalan 2000 token önbellekten gelir.
Prompt Engineering Nedir? konusunda çalışırken bu optimizasyonu da hesaba katmak gerekir. Sabit içerikleri giriş başına öne toplamak, prefix caching’in çalışma oranını doğrudan artırır.
vLLM’de PagedAttention ve KV Cache Yönetimi
Açık kaynak inference motorlarında KV cache yönetimi ayrı bir zorluk taşır. Birden fazla kullanıcı isteğini aynı GPU’da işlerken her isteğin KV cache’ini nereye yerleştireceğiniz de hesabın bir parçası olur.
Standart yaklaşım şöyle çalışır: her istek için maksimum context window kadar alan önceden ayrılır. 4096 token’lık bağlam için 4096 × katman sayısı × gizli boyut büyüklüğünde bir bellek bloğu tahsis edilir. Bu bloğun büyük kısmı çoğu zaman boş kalır; ama başka bir isteğe de verilemez.
Bu fragmantasyon sorunu, eş zamanlı istek kapasitesini kısıtlar. GPU’nun toplam belleğinin yalnızca küçük bir kısmı gerçek KV değerleri için kullanılırken geri kalanı rezerve edilmiş boş alanlarda bekler.
vLLM Nedir? makalesinde ayrıntılı incelediğimiz PagedAttention, bu sorunu işletim sistemlerindeki sanal bellek mantığından uyarlayarak çözdü. KV cache artık önceden büyük bir blok olarak ayrılmıyor; ihtiyaç arttıkça küçük fiziksel bloklar ekleniyor, istek tamamlandığında bloklar serbest bırakılıyor. Aynı prefix’i paylaşan iki istek, o prefix’in KV cache bloklarını kopyalamak yerine aynı fiziksel bloklara referans verebiliyor.
Bu yaklaşım gerçek trafik altında bellek kullanımını ciddi ölçüde iyileştiriyor; aynı GPU daha fazla eş zamanlı isteği karşılıyor.
# vLLM'de prefix caching etkinleştirmek
from vllm import LLM, SamplingParams
llm = LLM(
model="meta-llama/Llama-3.1-8B-Instruct",
enable_prefix_caching=True # varsayılan olarak True
)
params = SamplingParams(temperature=0.7, max_tokens=512)
outputs = llm.generate(["KV cache nedir?"], params)
Speculative Decoding ile KV Cache Koordinasyonu
Speculative Decoding yöntemi, küçük bir taslak modelin birkaç token öne tahmin yapmasını, büyük hedef modelin ise bu tahminleri toplu olarak doğrulamasını içerir. Tek bir adımda birden fazla token kabul edilebildiğinde decode aşaması hızlanır.
KV cache bu noktada ek bir karmaşıklık getirir. İki model kullanıldığı için iki ayrı KV cache yönetilmesi gerekir:
- Taslak model cache’i: Hızlı ve küçük; önerileri üretirken paralel olarak büyür.
- Hedef model cache’i: Büyük ve yavaş; toplu doğrulama sonrası güncellenir.
Taslak modelin önerileri hedef tarafından reddedildiğinde, hem taslak hem hedef modelin cache’leri geri alınarak yeniden senkronize edilmesi gerekir. Bu gereksinimlerin doğru karşılanması implementasyonu ciddi ölçüde karmaşıklaştırır. Ama doğru yapıldığında ikisi birlikte çalışan iki mekanizmanın ürettiği hız artışı, bu karmaşıklığı haklı kılar.
Uzun Bağlamlarda KV Cache Boyutu Nasıl Büyür?
Context window genişledikçe KV cache bellek gereksinimi doğrusal artar. Her token, her katman başına iki vektör saklar: biri K için, biri V için. Llama 3.1 8B modeli için yaklaşık değerler:
- Katman sayısı: 32
- Gizli boyut başına KV head boyutu: 128 (float16 ile 256 bayt)
- Her token başına KV belleği: 32 katman × 2 matris × 256 bayt ≈ 16 KB
1 milyon token bağlam için hesap: 1.000.000 × 16 KB ≈ 16 GB. Bu, modelin ağırlık boyutuna (yaklaşık 16 GB FP16) yaklaşan bir değerdir. Daha büyük modellerde veya çok sayıda eş zamanlı istekte bu yük birkaç katına çıkar.
Long Context LLM Nedir? makalesinde bu sınırların pratikte nasıl aşıldığını ele alıyoruz. KV cache sıkıştırma, sliding window attention ve sparse attention yöntemleri bu çerçevede değerlendirilen başlıca tekniklerdir.
KV Cache Quantization ile Bellek Tasarrufu
Bellek baskısının bir diğer çözümü: KV değerlerini düşük hassasiyetle saklamak.
Standart float16 (FP16) format yerine şu seçenekler değerlendirilebilir:
- INT8 (8-bit): Bellek tüketimini yarıya indirir. Kalite kaybı çoğu kullanım senaryosunda göz ardı edilebilir düzeyde kalır. Üretim sistemlerinde yaygın tercih budur.
- INT4 (4-bit): Belleği dörtte bire indirgeyebilir. Hassasiyet gerektiren görevlerde ölçülebilir kalite düşüşü görülebilir; uzun bağlam uygulamalarında bu ödünleşim çoğunlukla kabul edilebilir.
- FP8: NVIDIA H100 gibi modern GPU’larda donanım destekli format. Hem hız hem kalite dengesi açısından INT8’e güçlü bir alternatif.
Quantization Nedir? yazısında model ağırlıklarının nicelenmesini ayrıntılı işledik. KV cache nicelenmesi bundan bağımsız bir işlemdir. Bir modeli FP16 ağırlıklarla yükleyip KV cache’i INT8’de tutmak mümkündür; ikisi birbirini dışlamaz.
vLLM --kv-cache-dtype fp8 parametresiyle H100 üzerinde FP8 KV cache kullanır. HuggingFace Transformers kütüphanesi de cache_implementation="quantized" parametresiyle INT8 ve INT4 KV cache desteği ekledi.
Pratik Rehber: KV Cache’ten Gerçekten Yararlanmak
API kullananlar için:
Sistem promptunuzun sabit kısmını giriş başına öne alın ve içeriği sık değiştirmeyin. Anthropic, OpenAI ve Google’ın prefix caching mekanizmaları aynı ön ekle gelen istekleri otomatik tanır. “Cache ısınması” için ilk birkaç istekten sonra maliyet belirgin biçimde düşer.
API maliyetlerinizi izliyorsanız, önbelleğe alınan token oranı (cache hit rate) önemli bir metrik haline gelir. Düşük oran, sistem promptunuzun ya çok sık değiştiğini ya da farklı oturumlar arasında tutarsız kaldığını gösterir.
Kendi sunucunuzu işletenler için:
vLLM’de prefix caching varsayılan olarak açıktır (enable_prefix_caching=True). Aynı prefix’i paylaşan çok sayıda istek bekliyorsanız bu ayarı teyit edin. KV cache boyutunu tahmin etmek için model kartındaki num_key_value_heads ve num_hidden_layers değerlerine bakın. VRAM’ın yaklaşık yarısını model ağırlıklarına, geri kalanını KV cache’e ayırmak makul bir başlangıç noktasıdır.
Donanım seçerken:
Decode aşaması bellek bant genişliğiyle sınırlı olduğundan VRAM kapasitesi ve bellek bant genişliği, ham hesaplama gücü (TFLOPS) kadar önem taşır. A100’ün HBM2e belleği bu yüzden LLM inference için hâlâ güçlü bir seçenek olmaya devam ediyor.
KV cache bir optimizasyon detayı gibi görünse de çıkarım maliyetinin ve gecikme süresinin büyük kısmı bu yapının yönetilme biçiminden kaynaklanır. API kullanıcısıysanız sistem promptunuzun önbellek oranını takip etmek, modelinizi kendiniz çalıştırıyorsanız VRAM planlamasında bunu hesaba katmak, büyük bağlamlarda kaçınılmaz olmayan performans tavan sorusuna peşinen cevap vermenizi kolaylaştırır.