Büyük Dil Modelleri (LLM'ler) üretim uygulamalarının vazgeçilmez bir parçası hale geldikçe, çıktı gecikmesi darboğazı model eğitiminden dağıtım verimliliğine kaymıştır. H100 gibi modern GPU'lar devasa işleme gücü sunarken, bellek bant genişliği kritik bir kısıtlama olmaya devam ediyor. Her oluşturulan token, tüm bağlam penceresini okumayı gerektirir ve bu da konuşma uzunlukları büyüdükçe önemli gecikme artışlarına yol açar. Bu yazı, bu sorunu hafifletmek için Redis gibi dağıtık bir bellek deposuna Anahtar-Değer (KV) Önbelleğini taşıyan sağlam bir mimari desenini incelemektedir.
Sorun: Bellek Sınırlı Çıktı
Standart Transformer mimarilerinde, dikkat mekanizması bağlamdaki tüm tokenlar arasındaki ilişkileri hesaplar. Tekrarlanan hesaplamaları optimize etmek için sistemler, önceki tokenlardan gelen anahtarları ve değerleri saklamak amacıyla KV Önbelleğini kullanır. Ancak bu önbellek GPU VRAM'inde bulunur. Bağlam pencereleri 128 bin veya daha fazla tokena genişledikçe, VRAM tüketimi patlama yapar; bu da toplu işlem boyutlarını sınırlar ve istek bekleme sürelerini artırır. Bu statik veriyi GPU'dan dışarı taşımak, değerli VRAM'i hesaplamalar için serbest bırakır ve daha yüksek eşzamanlılığa olanak tanır.
Neden Redis?
Redis sadece basit bir anahtar-değer deposu değildir; ikili verileri işleme yeteneği, alt milisaniye gecikme süresi sağlama ve gelişmiş veri yapılarını destekleme özellikleri onu KV Önbelleği taşıma için ideal kılar. Disk tabanlı veritabanlarının aksine, Redis veriyi bellekte tutarak "bellek duvarı" darboğazını, hızdan ödün vermeden pahalı GPU VRAM'den maliyet etkin sistem RAM'ine veya ağa bağlı depolamaya taşır.
Mimari Uygulama
Entegrasyon, LLM motorunun (örneğin vLLM veya Triton) oluşturmadan önce mevcut KV girdilerini Redis'te kontrol ettiği bir işlem hattını içerir. Önbellek mevcutsa, alınır ve GPU'nun bağlamına yüklenir. Yoksa, yeni tokenlar hesaplanır ve KV çiftleri daha sonra Redis'e yazılır.
Redis İstemcisini Kurma
İlk olarak, redis-py istemcisinin yüklü olduğundan ve bir Redis örneğinin çalıştığından emin olun. KV matrislerini verimli bir şekilde saklamak için HASH veri yapısını kullanacağız, çünkü bunlar bellekte bitişik bloklardır.
import redis
import numpy as np
import json
class KVCacheManager:
def __init__(self, host='localhost', port=6379, db=0):
self.r = redis.Redis(host=host, port=port, db=db, decode_responses=False)
def save_kv_cache(self, session_id: str, key_tensor: np.ndarray, value_tensor: np.ndarray):
"""
KV önbellek tensörlerini Redis'e kaydeder.
"""
# Numpy dizilerini baytlara seri hale getir
key_bytes = key_tensor.tobytes()
value_bytes = value_tensor.tobytes()
# Atomiklik için pipeline kullan
pipe = self.r.pipeline()
pipe.hset(session_id, "keys", key_bytes)
pipe.hset(session_id, "values", value_bytes)
pipe.expire(session_id, 3600) # TTL'yi 1 saate ayarla
pipe.execute()
def load_kv_cache(self, session_id: str):
"""
KV önbellek tensörlerini Redis'ten alır.
"""
key_bytes = self.r.hget(session_id, "keys")
value_bytes = self.r.hget(session_id, "values")
if not key_bytes:
return None, None
# Şekilleri model yapılandırmanıza göre belirleyin
# Örnek: batch_size=1, num_heads=32, seq_len=mevcut_bağlam varsayılıyor
# LLM'nize özgü şekil yeniden yapılandırma mantığını işlemelisiniz
return key_bytes, value_bytes
Çıktı Motoruyla Entegrasyon
Çıktı döngünüzde, token oluşturmadan önce bir kontrol uygulayacaksınız:
def generate_token(model, session_id, prompt):
# 1. Mevcut önbellek için Redis'i kontrol et
cached_keys, cached_values = load_kv_cache(session_id)
if cached_keys:
# 2. Önbelleğe alınmış KV çiftlerini GPU belleğine yükle
load_to_gpu(cached_keys, cached_values)
else:
# 3. İlk ileri geçişi çalıştır ve KV önbelleğini hesapla
run_initial_forward(model, prompt)
# 4. Sonraki tokenı oluştur
next_token = model.generate()
# 5. Yeni KV girdileriyle Redis'i güncelle
new_keys, new_values = model.get_new_kv_cache()
save_kv_cache(session_id, new_keys, new_values)
return next_token
Pratik Düşünceler ve Takaslar
Redis'e taşıma ağ aşırı yüklemesi getirirken, KV önbellek verilerini seri hale getirme ve iletme gecikme maliyeti, sonraki dikkat adımlarında GPU VRAM'inden yeniden okumanın bellek bant genişliği maliyetinden genellikle önemli ölçüde daha düşüktür. Ancak ağ geçişinizi izlemelisiniz. Ultra düşük gecikme gereksinimleri için, Redis'i çıkarım düğümlerinizle aynı konuma yerleştirmeyi veya CPU'yu atlamak için RDMA (Uzaktan Doğrudan Bellek Erişimi) kullanmayı düşünün.
Ayrıca, sağlam bir geri çağırma (eviction) politikası uygulayın. Tüm konuşmalar sonsuza kadar önbelleğe alınmamalıdır. Redis kümenizdeki bellek baskısını yönetmek için en az kullanılanı (LRU) stratejilerini veya süre sonu (TTL) ayarlarını kullanın.
Sonuç
LLM çıktısını optimize etmek artık sadece daha büyük modeller hakkında değil; verimli veri hareketi hakkındadır. KV Önbelleği taşıma için Redis'ten yararlanarak geliştiriciler, bellek depolamayı hesaplamadan ayırabilir ve ölçeklenebilir, maliyet etkin ve düşük gecikmeli dağıtımlar sağlayabilir. Bu yaklaşım, daha az GPU ile daha uzun bağlam pencereleri sunmanıza olanak tanır; bu da doğrudan kâr marjınızı ve kullanıcı deneyiminizi etkiler. Küçük başlayın, gecikme iyileştirmelerinizi ölçün ve belirli iş yükünüz için mükemmel dengeyi bulmak üzere önbellekleme stratejinizi iteratif olarak geliştirin.