Ana sayfa → Teknik
Prompt Cache: Ucuzlatma Değil, Tasarım Kararı
Salı sabahı, sağlayıcının kullanım sayfası: dünkü LLM maliyeti, bir önceki Pazartesinin 2,5 katı. Trafik aynıydı, soru sayısı aynıydı, model aynıydı. Değişen tek şey, Cuma akşamı system prompt’un ilk satırına eklenen bir cümleydi: “Müşterimiz {ad} ile konuşuyorsun.”
- Prompt cache, prompt’un başını hatırlar. Eşleşme ilk token’dan başlar, ilk farklı token’da biter. Sonrası her seferinde baştan işlenir.
- Başa konan tek değişken, arkasındaki her şeyi ıskalatır. Müşteri adını ilk satıra koyduk; hit oranı %86’dan %4’e düştü.
- Hata vermez, sadece pahalanır. Cevaplar doğruydu, testler yeşildi. Dört gün boyunca kimse fark etmedi; fazladan yaklaşık 550 $ ödedik.
- Kural: sabit olan önce, değişen sonra. Modele göre önemli olan başta; önbelleğe göre sabit olan başta. İkisini karıştırma.
- Etkisi sadece fatura değil. p95 ilk token süresi 1,4 saniyeden 2,3 saniyeye çıktı.
- Hit oranı bir metrik, önek sayısı da. Bir saatte kaç farklı sabit önek görüyorsan önbelleğin o kadar parçaya bölünmüş demek.
Sahadan: bir satırın faturası
Ürün ekibinin isteği makuldü: destek asistanı müşteriye adıyla hitap etsin. İşi alan arkadaş en doğal yeri seçti. Müşterinin adını ve hesap tipini system prompt’un ilk satırına koydu; gerekçesi de makuldü: “Model en önemli bilgiyi başta görsün.” PR’ı ben onayladım. Cuma 17:00’de canlıya çıktı.
Hafta sonu trafik düşük, kimse bakmadı. Pazartesi normal bir gündü. Salı sabahı aylık raporu hazırlarken kullanım sayfasına baktım: Pazartesi 450 $, bir önceki Pazartesi 180 $. Cevaplar doğruydu, hata oranı sıfırdı, latency alarmımız çalmamıştı. Her şey çalışıyordu; sadece 2,5 kat pahalıya.
Sebebi bulmak bir saat sürdü, düzeltmek bir satır. Önbellekten okunan token sayısı, Cuma 17:00’de uçurumdan düşer gibi düşmüştü. Müşteri adını prompt’un sonuna taşıdık. Hit oranı %4’ten %61’e çıktı — ama eski %86’ya dönmedi. İkinci sebep aynı sürümün içinde saklanıyordu; ona aşağıda geleceğim.
Nasıl çalışıyor: sabit önek
Model bir prompt’u okurken her token için bir ara hesap üretir. Prompt caching bu hesabı kısa bir süre saklar. Sonraki istek aynı başlangıçla gelirse, o kısım yeniden hesaplanmaz. İki kazanç var: o token’lar daha ucuza faturalanır ve ilk token daha erken gelir.
Kritik kelime “başlangıç”. Eşleşme prompt’un ilk token’ından başlar, ilk farklı token’da biter. Ortada bir kelime değişirse ondan sonraki her şey baştan işlenir, geri kalanı ne kadar aynı olursa olsun. Önbellek bir sözlük değil, bir önektir.
Rakamlar sağlayıcıya göre değişiyor, bu yüzden kendi durumumuzu yazayım. Bizim sağlayıcıda önbellekten okunan girdi token’ı normal fiyatın onda biri; önbellek birkaç dakika kullanılmazsa siliniyor; çok kısa önekler hiç önbelleğe alınmıyor. Bazı sağlayıcılar önbelleğe yazmayı ayrıca ücretlendiriyor. Kendi sağlayıcının belgesine bak; ama mantık her yerde aynı.
# ONCE (Cuma surumu) - ilk satir her musteride farkli
[1] Musterimiz Ayse Kaya ile konusuyorsun. Hesap tipi: premium. <-- degisken
[2] tool tanimlari ~1.100 token # hepsi yeniden islenir
[3] talimatlar ~ 900 token # hepsi yeniden islenir
[4] politika ozetleri ~1.200 token # hepsi yeniden islenir
[5] konusma gecmisi + soru ~ 600 token
# SONRA - sabit olan once, degisen sonra
[1] tool tanimlari ~1.100 token # onbellekten
[2] talimatlar ~ 900 token # onbellekten
[3] politika ozetleri ~1.200 token # onbellekten
---- buraya kadar her istekte birebir ayni ----
[4] musteri: Ayse Kaya, premium # degisken, sonda
[5] konusma gecmisi + soru ~ 600 token
Model için ikisi arasında fark yok; müşteri adını sonda da görüyor ve adıyla hitap ediyor. Bunu önce 40 konuşmada karşılaştırarak test ettik, cevaplarda fark bulamadık. Fark sadece önbellek için var ve o fark 3.200 token.
Ne bozuyor: gizli değişkenler
Müşteri adı bariz olanıydı. Asıl tehlikeli olanlar, prompt’a değişken koyduğunu bilmeden koyduğun şeyler:
- Tarih ve saat (“Bugün 14 Nisan, saat 09:32”)
- İstek kimliği, oturum kimliği, trace id
- Müşteri adı, segment, dil tercihi
- A/B deney etiketi ya da deneye göre değişen talimat
- Sırası garanti olmayan bir yapıdan üretilen liste: tool’lar, örnekler, politika maddeleri
- Şablon motorunun bıraktığı boşluk ya da satır sonu farkı
İkinci sebep: tool listesinin sırası
Müşteri adını sona taşıdıktan sonra hit oranı %61’de takıldı. Aynı Cuma sürümünde
yeni bir tool eklenmişti ve tool tanımları artık bir set’ten
üretiliyordu. Python’da metinlerin hash’i her süreçte farklı tohumla
hesaplanıyor; yani aynı set, her pod’da farklı sırayla dönüyordu.
8 pod, 4 hesap tipi: 32 farklı önek. Yoğun saatte her biri kendi önbelleğini ısıtıyordu,
ama sakin saatlerde bir önek birkaç dakika kullanılmadan kalıp siliniyordu. Tool listesini
ada göre sıraladık, ertesi gün hit oranı %87 oldu. Değişiklik: bir sorted().
Maliyet ve ilk token
Dört dönemi yan yana koyunca etkinin tamamı görünüyor. Soru başı maliyet, olaydan önceki seviye 100 kabul edilerek endekslendi:
| Dönem | Hit oranı (girdi token’ı) | Soru başı maliyet | p95 ilk token |
|---|---|---|---|
| Cuma öncesi | %86 | 100 | 1,4 sn |
| Cuma 17:00 – Salı (ad başta, sırasız tool listesi) | %4 | 251 | 2,3 sn |
| Salı öğleden sonra (ad sonda) | %61 | 146 | 1,8 sn |
| Çarşamba ve sonrası (tool listesi sıralı) | %87 | 98 | 1,4 sn |
Maliyet farkının neden bu kadar büyük olduğunu hesap gösteriyor. Bizde bir soru ortalama 3.800 girdi token’ı ve 250 çıktı token’ı harcıyor; çıktı token’ı, girdinin dört katı fiyatlı. Girdinin %86’sı önbellekten gelince girdi faturası çıktı faturasından küçük kalıyor. Önbellek bozulunca girdi, faturanın en büyük kalemi oluyor.
İlk token süresi de aynı sebeple uzuyor: model 3.200 token’ı her seferinde baştan okuyor. Kullanıcı bunu cevabın başlamasını beklerken hissediyor (latency yazısında neden ortalamanın değil ilk token’ın önemli olduğunu anlatmıştım). Token başına yazma hızı değişmiyor; sadece başlangıç gecikiyor. Latency alarmımız 3 saniyeye kuruluydu, 2,3 saniye onu tetiklemedi.
Neden dört gün fark etmedik
Olaydan sonra sorduğum soru “neden bozuldu” değil, “neden dört gün sürdü” oldu. Cevap rahatsız ediciydi: bu değişikliği yakalayacak hiçbir kontrolümüz yoktu. Üç ayrı ağ vardı ve üçünden de geçti.
- Testler davranışa bakıyordu. Test seti “asistan doğru cevabı veriyor mu” diye soruyordu. Veriyordu. Hiçbir test “bu cevap ne kadara mal oldu” diye sormuyordu.
- Alarmlar hataya bakıyordu. Hata oranı sıfırdı. Latency alarmı 3 saniyedeydi. Maliyet için alarm yoktu, çünkü maliyeti bir metrik olarak değil, ay sonunda gelen bir fatura olarak görüyorduk.
- Kod incelemesi içeriğe bakıyordu. PR’da cümlenin doğru olup olmadığını okudum; nereye konduğunu düşünmedim. Prompt’u bir metin gibi inceledim, bir veri yapısı gibi değil.
Üçünün ortak noktası şu: prompt’un ne söylediğine bakıyorduk, nasıl kurulduğuna bakmıyorduk. Önbellek ise sadece ikincisiyle ilgileniyor. Aşağıdaki tasarım kuralları ve izleme listesi bu boşluğu kapatmak için var.
Tasarım kararı: prompt’u katmanlara ayır
Olaydan sonra prompt’u bir metin olarak değil, katmanlar olarak düşünmeye başladık. Her katmanın bir değişme sıklığı var ve sıralama bu sıklığa göre:
- Sürümle değişen: tool tanımları, talimatlar, politika özetleri. Sadece deploy’da değişir.
- Müşteriyle değişen: ad, hesap tipi, açık emirlerin özeti. Konuşma boyunca sabit.
- Her mesajda değişen: konuşma geçmişi ve yeni soru.
Bu sıralamanın bir faydası daha çıktı: konuşma içinde ikinci katman da önbellekten geliyor. Aynı müşterinin üçüncü mesajında ad, hesap tipi ve ilk iki mesaj artık önbellekte. Yeter ki geçmişi sadece sona ekle. Eski mesajları kırpmak ya da özetlemek o noktadan sonrasını yeniden işletir. Uzun konuşmalarda kırpmak yine gerekli; ama her mesajda değil, belli bir eşikte ve tek seferde.
- Sabit katmanı başa, değişkeni sona koy
- Listeleri deterministik sırala
- Prompt’u kod içinde tek bir fonksiyonla kur, parça parça değil
- Sabit kısmın hash’ini logla
- Konuşma geçmişine sadece sondan ekle
- “Önemli bilgi başta olsun” diye değişkeni ilk satıra koymak
- Tarih, saat ya da istek kimliğini system prompt’a gömmek
- Her mesajda geçmişi yeniden özetlemek
- Küçük bir A/B deneyi için sabit katmanı ikiye bölmek
- Hit oranını tek bir genel sayı olarak izlemek
A/B deneyleri ayrıca dikkat istiyor. Talimatlarda deneme yapınca sabit önek ikiye bölünüyor. Trafiğin %5’ini alan deney kolunun önbelleği sık sık soğuyor ve o kolun maliyeti, deneyin kendi etkisinden bağımsız olarak yükseliyor. Deney sonucunu okurken bunu hesaba katmak gerekiyor; yoksa “yeni talimat pahalı” diye yanlış bir sonuca varıyorsun.
Önbellekten okunan token’ın kotaya nasıl yazıldığı da sağlayıcıya göre değişiyor. Rate limit yazısındaki TPM hesabını yaparken bunu sağlayıcının belgesinden kontrol et; tahminle hesap yapma.
Ne izlemeli?
| Ne | Neden |
|---|---|
| Hit oranı: önbellekten okunan / toplam girdi token’ı, özellik başına | Genel ortalama, küçük bir özelliğin çöküşünü saklar |
| Saatte görülen farklı sabit önek hash’i | Sürüm başına 1 olmalı; artıyorsa bir yerde gizli değişken var |
| Soru başı maliyet | Toplam maliyet trafikle oynar; soru başı maliyet oynamamalı |
| p95 ilk token süresi | Önbellek bozulduğunda kullanıcının hissettiği tek şey |
Alarm: hit oranı geçen haftanın aynı saatinden 20 puan düşükse bildirim. Cuma 17:00’de bu alarm olsaydı, fark dört gün sonra değil yirmi dakika sonra görünürdü. Bir de deploy sonrası kontrol listesine tek satır ekledik: “Sabit önek hash’i değişti mi? Değiştiyse bekleniyor muydu?”
Bende işe yaramayanlar
- Önbelleği sıcak tutmak için ping atmak. Gece hit oranı düşüyor diye her dört dakikada bir sahte istek gönderdik. Kazanç günde 3 $ civarıydı; sahte isteklerin kendisi, logları ve karmaşası buna değmedi. Kapattık.
- Kişiselleştirmeyi tamamen kaldırmak. İlk tepki “adla hitabı geri alalım” oldu. Sorun ad değildi, adın yeriydi. Özellik kaldı, sadece yeri değişti.
- Tek bir genel hit oranı izlemek. İlk panomuz bütün özellikleri tek sayıda topluyordu. Trafiğin çoğu destek asistanında olduğu için iç araçlardaki bir çöküş o sayıda neredeyse hiç görünmüyordu.
Kontrol listesi
- Prompt’un ilk satırından sabit kısmın sonuna kadar her istekte birebir aynı mı?
- Tarih, saat, ad ya da kimlik gibi bir değişken sabit kısma sızdı mı?
- Tool’lar, örnekler ve politika maddeleri deterministik sırayla mı üretiliyor?
- Konuşma geçmişi sadece sondan mı büyüyor, yoksa her mesajda yeniden mi yazılıyor?
- Hit oranını özellik başına görüyor muyum?
- Sabit önek hash’i loglanıyor mu, saatte kaç farklı değer var?
- Deploy sonrası hit oranına kim bakıyor?
- Bu değişiklik bir A/B deneyiyse, önbelleğin bölünmesini sonuca kattım mı?
Sonuç
O Cuma akşamı yaptığımız değişiklik yanlış değildi. Müşteriye adıyla hitap etmek iyi bir istekti ve model de bunu doğru yaptı. Yanlış olan, bir satırın yerini kimsenin düşünmemiş olmasıydı — PR’ı onaylayan ben dahil.
Prompt cache’i bir indirim kuponu gibi düşünüyorduk: açarsın, fatura düşer. Değilmiş. Prompt’un nasıl kurulduğuna dair bir karar; her yeni satırda yeniden verilmesi gereken bir karar.
Test basit: yarın biri system prompt’a bir satır eklese, hangi sırayla ekleneceğini kim söyler? Cevap “kimse” ise önbelleğin var ama tasarımın yok.