31  Servis ve Çıkarım Altyapısı

Bölüm durumu: çevrildi. Kaynak metin: İngilizce 31. bölüm. Buradaki ölçümler bilerek küçük — bir dizüstünde 3B’lik bir modele karşı 150 kadar kısa istek, toplam birkaç dakika. Bu kadarı yeterli, çünkü önemli olan, işareti ve nedeni her makinede aynı kalan bir oran — en azından buradaki gibi tek ya da küçük batch’li (batch: birlikte işlenen istek grubu) çıkarımda: prefill, yani prompt’un (istem) okunduğu aşama, hesap sınırlıdır (compute-bound: hızını hesap gücü belirler); cevabın token token yazıldığı aşama olan decode ise bellek sınırlıdır (bandwidth-bound: hızını aritmetik değil, ağırlıkların bellekten okunma hızı belirler). Batch büyüdükçe decode da hesap sınırlı hâle gelebilir; bu sınırın nerede olduğunu Bölüm 31.4 hesaplıyor. Oranın büyüklüğü ise makineden makineye değişiyor; bölüm bunu yok saymak yerine, sayıların nerede geçerliliğini yitirdiğini açıkça söylüyor. Gözlemlemek için bir GPU filosu gereken her şey iddia edilmiyor, kaynak gösteriliyor. Her koşul beş kez ölçülüp medyan olarak raporlanıyor; koşullar karışık sırayla çalıştırılıyor, böylece zamanla oluşan kayma uzun prompt satırlarında birikemiyor; ayrıca denetim için sabit, kısa bir prompt oturum boyunca aralıklarla (toplam 11 kez) yeniden ölçülüyor. Burada verilen koşuda bu denetim ölçümü tüm oturum boyunca %2,0 içinde kaldı; tabloların geri kalanını güvenilir kılan da bu. Yazılım: ollama 0.24.0, llama3.2:3b servis ediyor (Q4_K_M, 28 katman, 24 attention head, 8 KV head, head boyutu 128; head: attention (dikkat mekanizması) hesabını paralel yürüten birim; Q4_K_M: ağırlık başına ortalama yaklaşık beş bitlik bir nicemleme biçimi, Bölüm 31.3’te açıklanıyor), num_ctx 8192, temperature 0, OLLAMA_FLASH_ATTENTION=1 (FlashAttention: attention’ı sonucunu değiştirmeden, bellek trafiğini azaltarak hesaplayan çekirdek; bkz. Bölüm 7.6) ve OLLAMA_KV_CACHE_TYPE=q8_0, Python 3.14.5 ve Apple Silicon üzerinde. “Apple Silicon” geniş bir aile; hangi çip olduğu önemli. Zamanlamalar bu çipin yüksek bant genişlikli sınıftan olduğunu gösteriyor: 2,0 GB’lık bir ağırlık dosyasına karşı saniyede 190 decode token’ı, yaklaşık 380 GB/s ağırlık akışı demek; bunu ancak bellek bant genişliği yüksek bir çip sürdürebilir. Aynı model 120 GB/s’lik bir dizüstünde saniyede 60 token civarında tavan yapar. Hızı değil oranı yeniden üretin.

Öğrenme hedefleri. Bu bölümün sonunda:

31.1 Bir istek, birbirinden çok farklı iki yarı

Bu bölümün ardından gelen her şey — Bölüm 32’nin geçidi, Bölüm 34’ün izleri, Bölüm 38’deki fatura — bir isteğin tek bir işlem değil, farklı maliyetleri ve farklı darboğazları olan iki işlem olduğunu gördüğünüzde çok daha kolay anlaşılır.

prefill / decode — bir isteğin iki yarısı: prompt tek bir paralel geçişte okunur, sonra çıktı token’ları her biri bir öncekine bağlı olarak teker teker üretilir. ↩︎

Bu iki yarının adı prefill / decode. Prefill prompt’u okur. Her token aynı anda elde olduğu için tüm prompt modelden tek bir toplu (batch’li) matris çarpımı olarak geçer — donanım meşguldür ve iş hesap sınırlıdır.

Decode cevabı yazar. n+1’inci token, n’inci token var olmadan hesaplanamaz; dolayısıyla her biri modelin tamamı üzerinden ayrı bir geçiştir ve — batch küçük olduğu sürece — makine zamanının çoğunu çarpma yapmaktan çok ağırlıkları bellekten taşımakla geçirir.

Bu asimetri bölümün temeli ve ölçmesi kolay: altı farklı uzunlukta prompt, önbelleğe hiçbir şey düşmesin diye her biri kendi metniyle, her birinde 32 token üretiliyor. Bu bölümdeki çıktı blokları ölçüm betiğinin yazdırdığı biçimde bırakıldı: sayılar İngilizce biçimde (ondalık ayırıcı nokta, binlik ayırıcı virgül; 1,433 bin dört yüz otuz üç demek). Sütunlar sırasıyla prompt token’ı, prefill süresi, saniyede prefill token’ı ve saniyede decode token’ı:

prompt tokens   prefill   prefill tok/s      decode tok/s
           96    0.064s            1,433            191.3
          298    0.153s            2,007            192.2
          572    0.277s            2,047            190.9
        1,095    0.520s            2,104            182.9
        2,156    1.055s            2,043            177.9
        4,247    2.292s            1,848            161.2

Prefill saniyede kabaca 2.000 token, decode ise kabaca 190 token hızında çalışıyor. Bu donanımda token başına prefill süresi, token başına decode süresinden yaklaşık on bir kat kısaydı — 4.247 token’lık satırda 0,54 milisaniyeye karşı 6,2. Bu, süre hakkında bir ifade; sağlayıcıların fiyat listesiyle ilgisi yok (orada çıktı token’ları genellikle girdinin birkaç katına faturalanır). Donanım değiştiğinde de geçerli kalan şey mutlak sayılar değil, bu orandır. İki rakamı da aynı satırdan alın: bağlam büyüdükçe decode yavaşlıyor (96 token’da 5,2 ms, 4.247’de 6,2 ms), yani en iyi prefill’i en kötü decode’la eşleştirmek farkı şişirir. Prefill de en uzun satırda biraz düşüyor; büyük olasılıkla benzer bir nedenle: her prompt token’ı kendinden önceki bütün token’lara attention uyguladığı için prefill’in attention kısmı prompt uzunluğundan daha hızlı büyüyor.

İlk satırdaki 1.433 tok/s daha yavaş bir prefill değil; 96 token’lık bir prompt’un amorti edemediği, istek başına sabit bir ek yük. Beş yineleme bunu doğruluyor ve aynı zamanda onu tablonun en gürültülü satırı yapıyor — her yerde %2–7 iken burada %34,5 yayılım, çünkü bu kadar küçük bir prompt’ta sabit maliyet baskın. Bu tablonun tek ölçümle yapılmış daha önceki bir sürümü son satırda 105 decode tok/s bildiriyordu; ölçüm beş kez yinelendiğinde sonuç 161, üstelik %0,7 yayılımla. Tek bir ölçüme güvenmenin bedeli bu. Her sütun beş koşunun kendi medyanı; bu yüzden tablodaki bir sütunu ötekine bölmek (96 ÷ 0,064 = 1.500) tablodaki hızdan biraz farklı çıkabilir.

Buradan, üzerinde tartışmaya yetecek kadar iyi, kaba bir ilk gecikme modeli çıkıyor:

\[ \text{gecikme} \;\approx\; \underbrace{\frac{\text{prompt token'ları}}{\approx 2{.}000/\mathrm{s}}}_{\text{prefill}} \;+\; \underbrace{\frac{\text{çıktı token'ları}}{\approx 190/\mathrm{s}}}_{\text{decode}} \]

Bir istek: bir paralel geçiş, sonra seri bir döngü
PREFILL · TÜM PROMPT, TEK GEÇİŞ 4.247 token 2,292 s paralel · hesap sınırlı DECODE · TOKEN BAŞINA BİR GEÇİŞ t₁ t₂ t₃ … seri · bu bağlamda 6,2 ms/token · bellek sınırlı kutular büyütüldü — prefill ölçeğinde 32’si toplam 25 px olurdu BAŞKA BİR İSTEK · 7.427 TOKEN GİRDİ, 64 ÇIKTI prefill — 4,715 s · %91,0 decode Beklemenin onda dokuzu ilk karakter belirmeden önce yaşanıyor. Akış o çubuğu kısaltmıyor. Yalnızca geri kalanını görünür kılıyor.

Üst yarı ölçekli değil: decode kutuları, token başına döngü hiç görünsün diye büyütüldü. Wall-clock süresi (gerçekte geçen süre) açısından o 32 geçiş, prefill’in 2,29 saniyesine karşı 0,20 saniye sürüyor; ölçekli çizilseydi prefill’in 286 pikseline karşı yaklaşık 25 piksel kaplarlardı. Alttaki çubuk ölçekli ve akılda tutulması gereken biçim o. Prefill 4.247 token üzerinden tek bir geçiş; decode ise model üzerinden, çıktı token’ı başına bir tane olmak üzere 32 ayrı geçiş. O çubuk başka bir isteğe ait: 7.427 token bağlam, 64 token çıktı; akış (streaming: cevabın üretildikçe parça parça gönderilmesi) açıkken ölçüldü. Üretimdeki isteklerin çoğu bu biçimdedir: wall-clock süresinin neredeyse tamamı ilk karakter belirmeden önce harcanır.

Neye bakacağız. Önce wall-clock süresinin gerçekte nereye gittiğine — ki üretim prompt uzunluklarında bu neredeyse tümüyle prefill’dir — ve akışın buna neden dokunamadığına. Sonra decode’u hızlandıran ve uzun bağlamı pahalılaştıran KV önbelleğine: boyutunu hiçbir şey kiralamadan önce gigabayt cinsinden hesaplayacak, ağırlıklarla birlikte bir kartın ihtiyaç duyduğu belleği nasıl oluşturduğunu göreceğiz. Sonra işletmeciye işlem hacmi kazandıran ve bunun bedelini tek tek kullanıcıya ödeten batchlemeye. Sonra kalite maliyeti olmayan tek kola ve bu bölümde ölçülen en büyük kazanca: prompt önbelleklemeye. Sonra dört sayı ve bir çarpmadan ibaret olan maliyet çizelgesine. Ve en sonunda kalan kollara — önerildikleri sıraya göre değil, başvurulması gereken sıraya göre.

31.2 Wall-clock süresi gerçekte nereye gidiyor

ilk token’a kadar geçen süre (TTFT) — okuyucunun bir şey belirmeden önce ne kadar beklediği: kuyruk, prefill ve ilk decode adımı. Uzun prompt’larda prefill baskındır; akış onu kısaltmaz, kullanıcının hissettiği bekleme hâline getirir. ↩︎

Kullanıcı toplam gecikmeyi hissetmez. Hissettiği, ilk token’a kadar geçen süre (TTFT) ve ardından akan metindir. Akış açıkken, dört bağlam uzunluğunda, her birinde 64 token üretilerek ölçüldüğünde (sütunlar: bağlam token’ı, TTFT, toplam süre ve TTFT’nin toplamdaki payı):

context tokens    TTFT     total    TTFT share
           90   0.132s   0.487s        27.1%
          960   0.506s   0.863s        58.7%
        3,731   2.061s   2.476s        83.2%
        7,427   4.715s   5.182s        91.0%

7.427 token’lık bağlamda isteğin %91’i, ilk karakter belirmeden önce bitmiş oluyor. Model “yavaş” değil; prompt uzun ve kullanıcı prefill’i bekliyor. Bu ölçümler boş bir sunucuda yapıldı, yani TTFT’nin kuyruk bileşeni burada sıfıra yakın; yük altında isteğin sırasını beklediği süre büyüyüp TTFT’ye prefill’den de fazla baskın hâle gelebilir.

Bunun genellikle gözden kaçan iki sonucu var.

Akış TTFT’yi düzeltmez. Ondan sonrasını düzeltir. Akış, 5,2 saniyelik bir beklemeyi 4,7 saniyelik bir beklemeye ve ardından okuma hızında gelen bir metne çevirir; bu algıda büyük bir iyileşmedir — ama o 4,7 saniyeye dokunulmamıştır ve hiçbir ön yüz çalışması ona dokunamaz. TTFT’nin kolu prompt’tur ve bu kolun en ucuz sürümü Bölüm 31.5’tir.

Uzun bağlam yalnızca prefill’i değil, decode’u da yavaşlatır. Üretilen her token kendinden öncekilerin tümüne attention uygular, dolayısıyla token başına maliyet pencerede zaten bulunanla birlikte büyür. Bu etki ayrıca ölçüldü — her yerde olduğu gibi beş yinelemeyle ve hız kararlı çıksın diye her birinde 64 çıktı token’ıyla:

context tokens   decode tok/s   ms per token
            83          189.1           5.29
           965          186.7           5.36
         3,731          160.6           6.23
         7,414          140.9           7.10

Boş bir pencere ile 7.400 token’lık bir pencere arasında %25’lik bir yavaşlama. Bunu bilmek önemli, çünkü etkiler üst üste biniyor: uzun bir prompt size bir kez prefill’de, sonra da sessizce ürettiğiniz her token’da yeniden mal oluyor.

Bu tabloyu ilkiyle karşılaştırırken aynı bağlam uzunluklarını karşılaştırın. İlk tablonun 4.247 token’lık satırı (161 tok/s), gürültü sınırları içinde bu tablonun 3.731 token’lık satırıyla (161) örtüşüyor, 7.414 token’lık satırıyla (141) değil. Yanlış satırdan alınmış bir sayı yine de yanlış bir sayıdır; yalnızca kaynaklı görünür.

Bu tabloların ölçtüğü şeyin iki adı var ve panolar bu adları kullanıyor. Sağdaki sütun — üretilen token başına milisaniye — çıktı token’ı başına süre (TPOT), diğer adıyla token’lar arası gecikme; TTFT ile birlikte tek bir kullanıcının hissettiğini anlatır. İşlem hacmi (throughput) öteki eksen: sunucunun aynı anda yürüttüğü bütün isteklerin toplamında saniyedeki token sayısı. İşletmecinin parasını ödediği sayı odur ve ikisinin neden birbirine ters çektiğini Bölüm 31.4 gösteriyor.

31.3 KV önbelleği ve bağlamın gigabayt cinsinden bedeli

Her adım tüm prompt’u yeniden hesaplasaydı decode’un token başına süresi 5,2 ms’den çok daha kötü olurdu. Öyle olmuyor, çünkü KV önbelleği var: önceki her token için hesaplanan anahtarlar ve değerler saklanıyor, böylece n+1’inci adım tek bir yeni sütun hesaplayıp gerisini okuyor.

KV önbelleği — önceki token’lardan saklanan anahtarlar ve değerler; böylece üretim her adımda tüm prompt’u yeniden hesaplamaz. Hızlıdır ve uzun bağlamın belleğe mal olmasının nedenidir. ↩︎

Bu, zamandan kazanıp bellekle ödenen düz bir ödünleşim (trade-off) ve gereken bellek, siz daha hiçbir şey kiralamadan hesaplanabilir. Önbellek her token için, her katmanda, her KV head’inde bir anahtar ve bir değer tutar:

\[ \text{token başına bayt} \;=\; 2 \times L \times H_{kv} \times d_{\text{head}} \times \text{eleman başına bayt} \]

Bu model fp16’da — 28 katman, 8 KV head, head boyutu 128 — bu \(2 \times 28 \times 8 \times 128 \times 2 = 114{.}688\) bayt, yani token başına 112 KiB eder. Pencereyle çarpın:

Pencerenin bellekte maliyeti, llama3.2:3b
bağlam önbellek, fp16 önbellek, q8_0 fp16 önbellek / 6,4 GB fp16 ağırlık fp16 önbellek / 2,0 GB Q4 dosya
8.192 0,88 GiB 0,46 GiB 0,15 kat 0,47 kat
32.768 3,50 GiB 1,86 GiB 0,59 kat 1,9 kat
131.072 14,00 GiB 7,44 GiB 2,3 kat 7,5 kat

fp16 sütunları (iki oran sütunu da bunları kullanıyor) varsayılanı gösteriyor ve ilk yapılması gereken hesap bu. Bu zamanlamaların geldiği sunucu ise q8_0 sütununu çalıştırıyordu: OLLAMA_KV_CACHE_TYPE=q8_0. Bu, blok ölçeği de sayıldığında eleman başına 1,0625 baytlık 8 bitlik bir önbellek; token başına 59,5 KiB ve tam pencerede 7,4 GiB eder, yani fp16 değerinin yarısından biraz fazlası. Önbelleği nicemlemek ağırlıkları nicemlemekten ayrı bir koldur, gerçekten bağlayıcı olan sayıyı oynatan odur ve varsayılan olarak kapalıdır. Bir kart boyutlandırmadan önce sunucunuzun hangisini çalıştırdığını kontrol edin, çünkü çıktıda bunu size söyleyen hiçbir şey yok.

2,0 GB’lık nicemlenmiş bir model dosyası, 131k’lık penceresi fp16’da dolduğunda 14 GiB önbelleğe ihtiyaç duyar. Ama o son oranı (7,5 kat) aktarmadan önce neyi neyle karşılaştırdığını belirtin: dört bitlik ağırlıklara karşı on altı bitlik bir önbellek; üç geçerli karşılaştırma içinde farkı en büyük gösteren bu. Eşit hassasiyette önbellek ağırlıkların 2,3 katı; bu bölümde fiilen kullanılan sekiz bitlik önbellek ise dosyanın 4,0 katı. Üçü de aynı şeyi söylüyor — kullanımla birlikte büyüyen bellek tahsisi önbellektir ve bütçelerde en sık unutulan da odur — ama kart boyutlandırırken kullanacağınız sayı bunlardan yalnızca biri; hangisi olduğu da neyi çalıştırdığınıza bağlı.

“3B’lik bir model çalıştırabilir miyim?” sorusunun yanlış, “Hangi bağlam uzunluğunda?” sorusunun doğru soru olmasının nedeni bu; yerel bir sunucudaki varsayılan num_ctx değerinin bu kadar önemli olmasının nedeni de: 131k’lık tam pencereyi isterseniz daha ilk istek gelmeden 14 GiB (yaklaşık 15 GB) bellek ayırmış olursunuz.

O hesaptaki 24 attention head’e karşılık 8 KV head, grouped-query attention’ın (GQA; gruplanmış sorgu dikkati) sonucu ve sayının 42 değil 14 GiB olmasının nedeni de bu: her anahtar-değer çiftini üç head paylaşıyor. Bu, istek başına KV önbelleğinin boyutunu (ve decode’un her adımda okuduğu önbellek baytını) küçülten bir optimizasyon; Bölüm 7.6 aynı hesabı 70B sınıfı bir model için yapıyor: 32k bağlamda istek başına, varsayımsal tam multi-head attention’daki 85,9 GB yerine 10,7 GB. Bu, kitapta sık rastlanan bir örüntü: Bölüm 7’deki mimari seçimler burada faturada birer kalem olarak yeniden karşımıza çıkıyor.

31.3.1 Bir kartı baştan sona boyutlandırmak

Bir sunucunun ihtiyaç duyduğu bellek kabaca ağırlıklar + dizi başına KV önbelleği × eşzamanlı dizi sayısı + ara değerler (aktivasyonlar) ve çalışma zamanının kendisi için bir pay. Ağırlıklar parametre sayısı × ağırlık başına bit ÷ 8 eder. Bu modelin 3,2 milyar ağırlığı 16 bitte 6,4 GB, Q4_K_M olarak 2,0 GB tutuyor — bu da ortalama dört değil, yaklaşık beş bit demek; çünkü bazı tensörler daha yüksek hassasiyette tutuluyor ve her küçük ağırlık bloğu kendi ölçeğini taşıyor. fp16’da, 32k token’lık tek bir konuşmayla servis edildiğinde bu, 6,4 GB ağırlık artı 3,5 GiB (3,8 GB) önbellek, yani pay eklenmeden önce yaklaşık 10 GB eder. Aynı anda böyle dört konuşma 14 GiB önbellek ister; artık önbellek ağırlıkların iki katından fazla.

31.3.2 Uzman karışımı ve PagedAttention

Uzman karışımı (MoE) bir modelde (Bölüm 7.7) bu formüle toplam parametre sayısını koyun: her uzmanın bellekte durması gerekir, çünkü herhangi bir token herhangi birine yönlendirilebilir. Token başına hızı belirleyen ise etkin parametre sayısıdır. Bölüm 12’deki qwen3.6:35b-a3b bu yüzden 23,9 GB’lık bir dosyaya ihtiyaç duyduğu hâlde belgeleri yoğun (dense: her token’da bütün parametreleri çalışan) 9,7B’lik modelden biraz daha hızlı işledi. (Büyük batch’lerle çalışırken farklı token’lar farklı uzmanları seçer; böylece her adımda ağırlıkların daha büyük bir kısmı okunur ve bu hız avantajı daralır.)

Filo ölçeğinde aynı basınç PagedAttention’ı doğurdu; bu yöntem önbelleği istek başına tek bir bitişik rezervasyon yerine sanal bellek gibi sabit boyutlu bloklar hâlinde yönetiyor (Kwon vd. 2023). Ortadan kaldırdığı israf parçalanma — 2.000 token’a yer ayırıp 200 üreten bir isteğin boş kalan yeri ve bu tür rezervasyonların arasında kalan kullanılamaz boşluklar — ve onu kaldırmak, bir sunucunun aynı kart üzerinde çok daha fazla eşzamanlı diziyi tutabilmesini sağlıyor. Bloklar paylaşılabildiği için birkaç istek ortak bir prompt ön ekinin tek bir kopyasına işaret de edebiliyor; Bölüm 31.5 bunun üzerine kurulu.

31.4 Batchleme: tek tek kullanıcının hissedemediği işlem hacmi

31.4.1 Decode neden bellek sınırlı ve bu durum nerede sona erer

“Decode bellek sınırlıdır” iddiası şimdiye kadar dört kez öne sürüldü. İşte türetilişi; çünkü türetme, iddianın söylemediği bir şeyi de söylüyor: batchleme bedava olmaktan çıkmadan önce ne kadar büyüyebilir?

\(B\) dizilik bir batch için bir decode adımı yaklaşık \(2NB\) kayan nokta işlemi yapar ve yaklaşık \(N \cdot b\) bayt ağırlık taşır; burada \(N\) parametre sayısı, \(b\) parametre başına bayt. Oranları — aritmetik yoğunluk —

\[I \;=\; \frac{2NB}{N b} \;=\; \frac{2B}{b}\]

ve neyin sadeleştiğine dikkat edin: model boyutu. Yoğunluk yalnızca batch boyutuna ve hassasiyete bağlı. Bir çip, ridge point’inin (kırılma noktası: tepe FLOP/s bölü tepe bant genişliği) üstünde hesap sınırlı olur; H100 sınıfı bir çip için bu kabaca \(989 \text{ TFLOP/s} \div 3{,}35 \text{ TB/s} \approx 295\) FLOP/bayt. Yani fp16’da decode yaklaşık \(B = 295\)’e kadar bellek sınırlı kalıyor; dört bitlik ağırlıkla yaklaşık \(B = 74\)’e kadar.

Prefill için formül aynı; yalnızca \(B\)’nin yerine prompt uzunluğu geçer. Yukarıdaki 4.247 token’lık prompt için bu, fp16’da 4.247’lik bir yoğunluk demek — ridge’in bir büyüklük mertebesinden fazla ötesi. (Bu ridge H100 için hesaplandı; ölçümlerin yapıldığı Apple Silicon çipinde tepe hesap gücünün bant genişliğine oranı çok daha düşük ve ağırlıklar dört bitlik olduğu için yoğunluk daha da yüksek, yani fark orada daha da büyük.) Prefill’in neden hesap sınırlı olduğunun, decode’un neden olmadığının ve tablonun iki yarısının neden bu kadar farklı davrandığının cevabı da bu.

Bu ridge sayısı, aşağıdaki iddianın geçerli olduğu sınırı da çiziyor.

Decode bellek sınırlıdır: donanım zamanını çarpma yaparak değil, ağırlıkları taşıyarak geçirir. Bu da ikinci bir isteğin neredeyse hiç ek maliyeti olmaması demektir, çünkü bir dizi için taşınan ağırlıklar aynı anda birkaç diziye hizmet edebilir. Batchleme bu boşluğu işlem hacmine çevirir — ve ikinci istek neredeyse bedava, ellincisi de öyle; üç yüzüncü civarında ise artık değil, çünkü batch ridge’i geçmiştir ve aritmetik de maliyet yaratmaya başlar.

Pratikte sınır daha erken gelir; formülün dışarıda bıraktığı iki nedenle. Her dizi her adımda kendi KV önbelleğini de okur ve bu trafik batch içinde paylaşılmaz; uzun bağlamda \(B\) ile birlikte büyür ve yoğunluğu aşağı çeker. Ayrıca her dizinin önbelleği belleğe sığmak zorundadır: gerçek kartlarda batch’i çoğunlukla ridge değil bellek sınırlar.

continuous batching (sürekli batchleme) — yeni istekler, tüm batch’in bitmesini beklemek yerine yer açıldıkça çalışan bir batch’e katılır. Her modern sunucudaki işlem hacmi kolu. ↩︎

Naif batchleme dolu bir batch bekler ve sonra onu sonuna kadar çalıştırır; dolayısıyla tek bir uzun üretim tüm batch’i rehin alır. Sürekli batchleme — yineleme düzeyinde çizelgeleme, Orca ile tanıtıldı (Yu vd. 2022) ve o zamandan beri standart — biten bir dizinin ayrılmasına ve kuyruktaki bir dizinin bir sonraki adımda onun yerini almasına izin verir.

Sürekli batchlemeyi burada ölçmedik ve bunu açıkça belirtmek gerekiyor: tek akışlı yerel bir sunucu yanlış alettir ve burada bir sayı uydurmak, bir sayıya kaynak göstermekten daha kötü olurdu. Literatürün bildirdiği rakamlar büyük: Orca’nın yazarları aynı gecikme bütçesinde naif batchlemeye göre büyüklük mertebesinde işlem hacmi kazançları ölçüyor (Yu vd. 2022) ve vLLM, sürekli batchlemeyi sayfalı önbellek yönetimiyle birleştirerek kendinden önceki servis sistemlerine göre 2–4 kat kazanç bildiriyor (Kwon vd. 2023).

Akılda tutulması gereken kat sayısı değil, ödünleşimdir:

Batchlemenin iki sayıya etkisi
işlem hacmi tek istek gecikmesi
Batchleme yok en kötü en iyi
Statik batchleme daha iyi en kötü — en yavaş diziyi bekler
Sürekli batchleme en iyi batchlemesizden biraz kötü, statikten çok daha iyi

Batchleme, bedelini tek tek kullanıcıların gecikmeyle ödediği bir işlem hacmi optimizasyonudur. Her istek makineyi batch arkadaşlarıyla paylaşır; dolayısıyla meşgul bir sunucu, istek başına daha ucuz bir sunucu olduğu hâlde istek başına daha yavaş bir sunucudur. Ürününüzün bir gecikme SLO’su (hizmet düzeyi hedefi) varsa, bu batch boyutu üzerinde bir kısıttır ve o sayının bir sahibi olmalıdır.

Bölümün ana tezi — iki yarının darboğazı farklı — p99 gecikmesinde de kendini gösterir: aynı batch’e giren uzun bir prompt’un prefill’i, o sırada decode yapan bütün dizilerin bir sonraki token’ını geciktirir. Modern sunucular bunu iki yolla yumuşatıyor. Chunked prefill (parçalı prefill) uzun prompt’ları parçalara bölüp her adımda decode adımlarıyla karıştırır; böylece hiçbir decode adımı koca bir prefill’in arkasında beklemez (vLLM ve SGLang bunu destekliyor). Ayrıştırılmış servis (prefill/decode disaggregation) ise iki yarıyı ayrı makinelere koyar: hesap sınırlı prefill bir havuzda, bellek sınırlı decode başka bir havuzda çalışır ve KV önbelleği aralarında aktarılır. İkisi de ödünleşimi ortadan kaldırmaz; yalnızca p99 gecikmesini uzun prompt’lara ve batch boyutuna daha az duyarlı hâle getirir.

31.5 Prompt önbellekleme: ilk başvurulacak kol

Gecikmeye prefill hâkimse ve prompt her seferinde aynı binlerce token’la başlıyorsa — bir sistem prompt’u, bir araç şeması, bir few-shot (az örnekli) örnek bloğu, hakkında art arda sorular sorulan, erişimle getirilmiş bir belge — o ön ekin KV önbelleği bir kez hesaplanıp yeniden kullanılabilir.

prompt önbellekleme — bir prompt ön ekinin KV önbelleğini istekler arasında yeniden kullanmak; böylece tekrarlanan bir sistem prompt’u yeniden hesaplanmaz. ↩︎

Prompt önbellekleme küçük bir iyileştirme değil. İlk tablodaki altı prompt’un her biri iki kez gönderildi (sütunlar: prompt token’ı, soğuk ve sıcak prefill, hızlanma, soğuk ve sıcak toplam süre):

prompt tokens   cold prefill   warm prefill   speedup   cold total   warm total
           95         0.066s         0.016s        4x       0.300s       0.252s
          303         0.151s         0.016s       10x       0.386s       0.252s
          564         0.277s         0.014s       19x       0.514s       0.251s
        1,090         0.538s         0.016s       34x       0.781s       0.259s
        2,149         1.060s         0.012s       88x       1.311s       0.261s
        4,247         2.290s         0.015s      153x       2.562s       0.286s

Sıcak prefill, hiçbir şey yeniden hesaplanmadığı için prompt uzunluğundan bağımsız olarak 12–16 milisaniyede sabit. 4.247 token’da bu, prefill’de 153 kat ve tüm istekte 9,0 kat hızlanma demek.

Ön ek ne kadar uzunsa kazanç o kadar büyük; çoğu optimizasyon bunun tam tersi biçimde ölçeklenir. Prompt önbelleklemenin ilk denenecek şey olmasının nedeni de bu.

Üç koşul var ve üçü de yapılandırma değil, tasarım kararı:

Ön ek bayt bayt aynı olmalı. Prompt’un tepesine yakın bir zaman damgası, bir kullanıcı adı ya da bir oturum kimliği kendinden sonraki her şeyi geçersiz kılar. Önbellekteki ön ekler baştan itibaren eşleştirilir; 12. konumda değişen tek bir token, ardından gelen 4.000 token’ı çöpe atar.

Prompt’u kararlılığa göre sıralayın. Önce sistem talimatları, sonra araç şemaları, sonra few-shot örnekler, sonra erişilmiş bağlam, sonra kullanıcının mesajı. Çoğu takım prompt’ları akıllarına geldikleri sırada yazar; bu da oynak kısmı öne koyar ve önbelleklemeyi çoğu zaman fark edilmeden kapatır.

Ya isabet oranını ölçersiniz ya da bir isabet oranınız yoktur. Ölçüm düzeneği kurmadığınız bir önbellek bir inançtan ibarettir. Bu ölçümün yeri Bölüm 34’tür ve metrik, rota başına önbellekten karşılanan prompt token’larının payıdır.

Ölçülen ve ölçülmeyen. Yukarıdaki 153 kat, yerel bir sunucunun slot (sunucunun bir diziye ayırdığı yuva) düzeyinde ön ek yeniden kullanımı: bir önceki isteğin KV önbelleği hâlâ bellekte duruyor ve sonraki istek onun ön ekini paylaşıyor. Bu, barındırılan bir API’nin kullandığı mekanizmanın aynısı ama aynı sistem değil ve üç fark, üretimde göreceğiniz sayıyı değiştiriyor.

En büyük fark tahliye (eviction), yani önbellekteki ön ekin bellekten atılması. ollama varsayılan olarak model başına tek slot tutuyor (OLLAMA_NUM_PARALLEL=1), dolayısıyla başka bir kullanıcıdan araya giren tek bir istek ön eki siliyor — oysa yukarıdaki ölçüm aynı prompt’u arka arkaya iki kez gönderdi; bu, mümkün olan en elverişli durum. Dönüşümlü istek gönderen iki kullanıcıyla hiçbir hızlanma görülmezdi. vLLM ve SGLang gibi sayfalı bloklar üzerine kurulu sunucular aynı anda çok sayıda önbellekli ön eki tutup istekler arasında paylaşıyor; bu, sorunu yumuşatıyor ama ortadan kaldırmıyor. İkincisi denetim: bazı barındırılan API’ler asgari bir ön ek uzunluğunun üstünde otomatik önbellekliyor, bazıları açık bir işaret istiyor; yerel sunucu ise bu ayarların hiçbirini sunmuyor. Üçüncüsü fiyat: barındırılan API’lerde önbellekten gelen girdi token’ları bedava değil, indirimli faturalanıyor ve bazı sağlayıcılar önbelleği yazan istek için ek ücret alıyor. Bölüm 31.6’daki maliyet çizelgesi de o indirim satırına dayanıyor.

153 katı mekanizmanın üst sınırı olarak alın ve gerçek sistemde gerçek isabet oranını ölçün.

31.6 Maliyet çizelgesi

Gecikme, meselenin ilk yarısıydı; fatura ise ikinci yarısı. İkisi de aynı token’lardan oluşuyor; bu yüzden önce hangi token’lardan söz ettiğimizi saptamak gerekiyor.

Bölüm 14’te 300 kelimelik bir haber metni özetlenirken 890 prompt token’ına karşı 53 üretilmiş token ölçülmüştü — neredeyse 17’ye 1’lik bir oran. Sistem prompt’u, şema ve erişimle getirilmiş bağlam içeren her iş yükü bu biçimdedir; üretimdeki LLM özelliklerinin çoğu da böyledir.

Bu tür bir iş yükünde ödediğinizin çoğu okumaya gidiyor ve bu, nerede optimizasyon yapılacağına dair alışıldık sezgiyi tersine çeviriyor. Buna göre hareket etmeden önce bir dönüştürme gerekiyor: sağlayıcılar çıktı token’larını girdinin birkaç katına faturalandırır, dolayısıyla 17’ye 1’lik bir token oranı parada 3’e 1 ya da 4’e 1’e yakındır ve bir önbellek isabeti girdi tarafını daha da indirir. Çarpmayı kendi fiyat listenizle yapın. Bu oranda sonuç değişmiyor: daha kısa cevaplar istemek az kazandırır, daha az göndermek çok kazandırır. Okuduğundan çok üreten bir görevde ise sonuç tersine dönerdi.

Çizelgenin dört satırı ve bir çarpması var:

Bir maliyet tahmininin ihtiyaç duyduğu her şey
satır nereden geliyor örnek
prompt token’ları sayın; tahmin etmeyin 890
bunun önbellekli olanı isabet oranını ölçün %60
çıktı token’ları sayın 53
aylık istek sayısı mühendislik değil, ürün 2.000.000

Sağlayıcınızın üç fiyatıyla — önbelleksiz girdi, önbellekli girdi, çıktı — çarpın; elinizde, önbellek isabet oranını değiştirerek oynayabileceğiniz aylık bir fatura tahmini olur. Herhangi bir sağlayıcının değil, yalnızca örnek olsun diye seçilmiş fiyatlarla — milyon önbelleksiz girdi token’ı başına 1,00 dolar, milyon önbellekli token başına 0,10 dolar, milyon çıktı token’ı başına 4,00 dolar — örnek şöyle çıkıyor:

Çizelgenin çarpılmış hâli, örnek fiyatlarla
satır aylık token milyon başına fiyat aylık maliyet
önbelleksiz girdi (890’ın %40’ı) 712 M 1,00 dolar 712 dolar
önbellekli girdi (890’ın %60’ı) 1.068 M 0,10 dolar 107 dolar
çıktı (53) 106 M 4,00 dolar 424 dolar
toplam 1.243 dolar

Önbellek olmadan aynı trafik 2.204 dolar tutuyor; bunun 1.780 doları girdi. Buradan iki şey okunuyor: isabet oranı, çıktı tarafındaki her değişiklikten daha değerli; ve önbellek çalışmaya başladığında girdi, token’ların %94’ü olduğu hâlde faturanın ancak üçte ikisi kadar.

Kendi barındırdığınız bir sistemde fiyat listesi yerine bir saat ücreti vardır ve token başına maliyete geçiş tek bir bölmedir:

\[ \text{milyon token başına maliyet} \;=\; \frac{\text{saat ücreti}}{\text{saatte işlenen token} \,/\, 10^6} \]

Paydadaki saatlik işlem hacmi, batchlemenin yükselttiği sayıdır; aynı kartın meşgulken token başına daha ucuz olmasının nedeni bu. Prefill ve decode token’larının süresi çok farklı olduğu için girdi ve çıktı token’larını ayrı saymak daha doğrudur.

Aşağıdaki araç bu çizelgenin zaman tarafını hesaplıyor: dolar değil, istek başına saniye ve aylık makine saati üretiyor. Prompt ve isabet oranı değiştikçe ikisi birlikte oynasın diye Bölüm 31.1’deki gecikme modeline bağlandı; dolar karşılığını yukarıdaki tabloyu kendi fiyatlarınızla çarparak ya da saat ücretinizi makine saatiyle çarparak bulun. Ölçülen hızları sabit tutuyor; bu yüzden uzun bağlamdaki decode yavaşlamasını yok sayıyor ve gerçekte ölçülen 7.400 token’ın ötesine ekstrapolasyon (dışdeğerleme) yapıyor. Uzun prompt tarafındaki sonuçları bir alt sınır olarak okuyun.

Yukarıda ölçülen sayılar üzerinde gecikme ve makine süresi — prompt'u oynatın, ne olduğuna bakın

31.7 Kollar, başvurulacak sırayla

Sıra listeden daha önemli, çünkü ucuz kollar aynı zamanda kalite maliyeti olmayanlar ve ekipler çoğu zaman listenin en altından başlıyor.

Altı kol ve her birinin feda ettiği
kol ne kazandırır neye mal olur ne zaman başvurulur
Daha az token göndermek prefill süresinde ve faturada doğrusal kazanç prompt’lar ve erişim üzerinde mühendislik zamanı her zaman, ilk
Prompt önbellekleme prefill’de 153 kata kadar (yukarıda ölçüldü) kararlılığa göre sıralanmış bir prompt her zaman, ilk
Daha küçük model her şeyde 3–10 kat kalite, ölçülebilir biçimde değerlendirme kalitenin korunduğunu gösterdiğinde
Nicemleme bellek ve bant genişliği bir miktar kalite, göreve bağlı bağlayıcı kısıt bellek olduğunda
Spekülatif kod çözme öncelikle decode gecikmesi; maliyet servis verimliliğine ve fiyatlandırmaya bağlı barındırılacak bir taslak model yavaş yarı decode olduğunda — uzun cevaplar; prefill baskınken yararı az (ayrıntısı aşağıda)
Sürekli batchleme işlem hacmi kullanıcı başına gecikme sunucu meşgulken, öncesinde değil

İkisi birkaç cümleyi hak ediyor, çünkü ikisi de sıklıkla yanlış anlaşılıyor.

Nicemleme (quantization) ağırlıkları düşürülmüş hassasiyette saklar — bu bölümde ölçülen model Q4_K_M, ağırlık başına ortalama yaklaşık beş bit. Decode her adımda her ağırlığı bir kez okuduğu için ağırlık başına daha az bayt, decode tavanını da yükseltir; kısa bağlamda tavan kabaca bant genişliği ÷ ağırlık baytıdır (uzun bağlamda her adımda okunan KV önbelleği baytları da paydaya eklenir — decode’un 7.400 token’da saniyede 141 token’a düşmesinin başlıca nedeni). Bölüm başındaki not 380 GB/s’yi, 2,0 GB’lık dosyada saniyede 190 token’dan tam böyle çıkardı; aynı çip 6,4 GB’lık fp16 ağırlıklarda saniyede 60 token civarında tavan yapardı. Modern eğitim sonrası nicemleme yöntemleri her ağırlığı aynı biçimde yuvarlamak yerine hangi ağırlıkların hassas kalacağını seçiyor: GPTQ katman düzeyinde bir yeniden kurma problemi çözüyor (Frantar vd. 2023), AWQ ise aktivasyon dağılımına göre en önemli olan küçük ağırlık kesimini koruyor (Lin vd. 2024). İkisi de naif yuvarlamadan çok daha az bozulma bildiriyor, ama ikisinde de bozulma hâlâ var. Bu, kalite maliyeti olan bir bellek optimizasyonu ve maliyet göreve bağlı — yani yeri bir blog yazısındaki benchmark (kıyaslama) tablosu değil, sizin değerlendirme setiniz.

Spekülatif kod çözme doğrudan seri kısmı hedef alıyor. Küçük bir taslak model birkaç token öneriyor; büyük model hepsini tek bir paralel geçişte puanlıyor ve değiştirilmiş bir ret örneklemesi (rejection sampling) adımı kaçının tutulacağına karar verip reddedilen ilk konumu düzeltilmiş bir dağılımdan yeniden örnekliyor (Leviathan vd. 2023). “En uzun doğru ön eki kabul eder” kullanışlı bir zihinsel model ve sıcaklık 0’da (temperature 0) mekanizmanın tam kendisi: taslak token’lar, büyük modelin kendi greedy (açgözlü) seçimiyle örtüştükçe tutulur. Örneğin taslak model “Türkiye’nin başkenti Ankara’dır ve” sonrası için beş token önerir; büyük model tek geçişte ilk dördünü kendi seçimiyle aynı bulup kabul eder, beşincisini reddeder ve yerine kendi token’ını koyar: büyük modelin tek geçişinden beş token. Örnekleme açıkken bu resim yalnızca yaklaşık olarak doğru: kabul olasılıksaldır ve akla yatkın bir taslak token bile reddedilebilir. Buna karşın çıktı dağılımının değişmediği kanıtlanmıştır — bu bir yaklaşıklama değildir — ve wall-clock süresindeki kazanç, taslak model büyük modelle sık sık aynı token’ı seçtiğinde tipik olarak 2–3 kat. Yapmadığı şeye dikkat edin: büyük model hâlâ her token’ı işliyor, üstelik reddedilen taslakları da puanlıyor; dolayısıyla FLOP sayısı düşmüyor, biraz artıyor. Token başına API fiyatlandırmasında fatura da düşmüyor. Kendi barındırdığınız sistemde maliyet GPU süresini ve işlem hacmini izler; bu yüzden hızlanma, tamamlanan cevap başına maliyeti düşürebilir. Bunu kendi servis yükünüzde ölçün. Birincil kazancı ise gecikmedeki düşüştür.

Tüm bunların büyük ölçekteki aritmetiği için — model boyutu, batch boyutu ve çipin bellek bant genişliğinin ulaşılabilir hızı birlikte nasıl belirlediği — Pope ve arkadaşlarının çalışması konuyu derli toplu işliyor (Pope vd. 2023); Bölüm 8’deki \(6ND\) ise eğitim tarafındaki karşılığı.

31.8 Sezgi kontrolleri

Her birini açmadan önce cevaplayın. Dördü bu bölümdeki ölçümler ve hesaplarla çözülüyor — iki hız, TTFT payı, KV önbelleği aritmetiği ve soğuk bir prefill’in sıcak birine karşı maliyeti — son ikisi ise bu bölümün bilerek ölçmediği şeyler, çünkü tek akışlı bir dizüstü batchleme için, genel bir benchmark da nicemleme için yanlış alet.

Modele bakmadan önce, prompt token’larının çıktı token’larına oranına.

Prefill ve decode çok farklı hızlarda çalışıyor — burada ölçüldüğü hâliyle saniyede yaklaşık 2.000’e karşı 190 token — dolayısıyla uzun prompt’lu ve kısa cevaplı bir istek zamanının neredeyse tamamını okuyarak geçirir. 7.427 token bağlamda wall-clock süresinin %91’i, ilk karakter belirmeden önce bitmişti.

Biçiminiz buysa kollar prompt uzunluğu ve prompt önbellekleme olur; özellikle spekülatif kod çözmenin uçtan uca yararı sınırlı kalır, çünkü yalnızca decode’u hızlandırır. Gerçekten daha hızlı bir model yine de işe yarar, çünkü prefill hesap sınırlıdır ve daha küçük bir modelin yapacak daha az hesabı vardır; yardım etmeyecek olan, token token ilerleyen yarıya yönelik her şeydir. Buna karşılık binlerce token üretiyorsanız cevap tam tersi olur — oranın en son değil en önce ölçülecek şey olmasının nedeni de bu.

Hayır. İlk token’dan sonraki algılanan gecikmeyi azaltır ve öncesinde hiçbir şeyi değiştirmez.

Akış, prefill bitmeden başlayamaz; dolayısıyla TTFT akışla da akışsız da aynıdır. Akışın kazandırdığı şey, kalan saniyelerin beklenerek değil okunarak geçmesi — gerçek ve büyük bir ürün iyileştirmesi, ama bir performans iyileştirmesi değil.

Bir takımın bu ikisini karıştırdığının işareti, akış devreye alındığında iyileşen bir gecikme panosudur. Ne TTFT’nin ne de toplam sürenin değişmesi gerekirdi. Sayı düştüyse, büyük olasılıkla istemci saati artık son parçada değil ilk parçada durduruyordur.

Çünkü ayak izi ağırlıklardan ibaret değil. KV önbelleği penceredeki her token için, her katmanda ve her KV head’inde bir anahtar ve bir değer tutuyor.

Bu model fp16’da — 28 katman, 8 KV head, head boyutu 128 — token başına 112 KiB eder; yani 131.072 token’lık bir pencere, 2,0 GB’lık model dosyasının üstüne 14 GiB (yaklaşık 15 GB) önbellek. Ama o oranın neyi neyle karşılaştırdığını belirtin: dört bitlik ağırlıklara karşı on altı bitlik bir önbellek. Eşit hassasiyette önbellek ağırlıkların 2,3 katı. 8 bitlik bir önbellek, önbellek tarafını kabaca yarıya indirir; varsayılan olarak kapalı olan bu kolu bilmek gerekir.

“Sığar mı?” sorusunun doğru cevabının bağlam uzunluğuyla ilgili bir soru olmasının ve yerel bir sunucunun varsayılan penceresinin, kolaylık ayarı kılığına girmiş bir bellek kararı olmasının nedeni bu.

Prompt’un tepesine yakın oynak bir şey var.

Ön ek önbellekleri eşleştirmeyi baştan itibaren yapar: 12. konumdaki tek bir farklı token kendinden sonraki her şeyi geçersiz kılar. Bir zaman damgası, bir kullanıcı kimliği, bir oturum tanımlayıcısı ya da örnek sırası her istekte yeniden karıştırılan bir few-shot bloğu bunu yapar ve hiçbiri bir performans hatasına benzemez.

Çözüm prompt’u kararlılığa göre sıralamak — talimatlar, sonra şemalar, sonra örnekler, sonra erişilmiş bağlam, sonra kullanıcının mesajı — ve kontrol, aynı isteği iki kez gönderip prefill sürelerini karşılaştırmak. Soğuk ile sıcak bir büyüklük mertebesi ya da daha fazla farklı olmalı; burada ölçüldüğü hâliyle 2,290 s’ye karşı 0,015 s.

Neredeyse kesinlikle hayır ve muhtemelen istek başına biraz kötüleşti.

Batchleme, decode’un bellek sınırlı olmasından yararlanıyor: tek bir dizinin ihtiyaç duyduğu ağırlık taşımasını birkaç dizi paylaşabiliyor. Bu, sunucu genelinde saniyedeki token sayısını yükseltiyor; her bir dizi ise daha hızlı ilerlemiyor — üstelik artık makineyi paylaşıyor.

Yani iki sayı zıt yönlerde hareket ediyor ve yalnızca iyileşenini raporlamak, bir değerlendirme toplantısını yanıltmanın yaygın bir yolu. Bir gecikme SLO’nuz varsa, o batch boyutu üzerinde bir tavandır ve birinin sahibi olması gerekir.

Bağlayıcı kısıtınız bellek değilken ya da nicemlenmiş model sizin görevinizde değerlendirilmemişken.

Nicemleme, kaliteden ödün vererek bellek ve bant genişliği kazandırır. Modern yöntemler naif yuvarlamadan çok daha azını kaybediyor — GPTQ da AWQ da her şeyi aynı biçimde ölçeklemek yerine hangi ağırlıkların korunacağını seçiyor — ama “çok daha azı” “hiç” değildir ve kayıp, genel bir benchmark’ın size söylemeyeceği biçimlerde göreve bağlıdır.

Kısıtınız prompt uzunluğuysa ağırlık nicemlemesi sorunu küçültmez: KV önbelleği kendi kolu olan (yukarıdaki 8 bitlik önbellek) ayrı bir tahsistir; yine de ağırlıklardan boşalan bellek daha fazla önbellek tutabilir. Ağırlık nicemlemesine model sığmıyorsa başvurun ve bir model değişimi için kullanacağınız değerlendirmenin aynısından geçirin.

31.9 Mülakat soruları

Kaynak konusunda — Bölüm 2’deki çekincenin aynısı. Aşağıdaki sorular, bu kitabın baştan sona kullandığı iki güvenilir açık kaynakta sık geçen sorulardan ve bu bölümün kendi ölçümleriyle cevaplanabilen sorulardan oluşuyor.

31.9.1 Makineyi anlayıp anlamadığınızı sınayanlar

S: Bir istek bir LLM sunucusuna geldiğinde ne olduğunu anlatın.

Farklı özellikleri olan iki aşama. Prefill tüm prompt’u tek bir paralel, hesap sınırlı geçişte okur. Decode çıktı token’larını teker teker üretir; her biri model üzerinden ayrı, bellek sınırlı bir geçiştir ve KV önbelleğini yeniden kullandığı için önceki token’lar yeniden hesaplanmaz. Hız farkını söyleyin — bu bölümün ölçümlerinde token başına yaklaşık on bire bir — ve size sorulan istek biçiminde hangi yarının baskın olduğunu belirtin. İki aşamayı ayırmadan tek parça bir “çıkarım” anlatan adaylar sisteme hiç yakından bakmamıştır.

S: KV önbelleği neden eşzamanlılığı sınırlayan şeydir?

Çünkü önbellek token sayısı çarpı dizi sayısıyla büyür; ağırlıklar ise sabit bir maliyettir. Ağırlıklar bir kez yüklenir ve karttaki bütün istekler onları paylaşır; önbellek ise dizi başınadır ve her prompt token’ıyla ve üretilen her token’la büyür. Aritmetiği verin — token başına \(2 \times L \times H_{kv} \times d_{\text{head}}\) değer, çarpı değer başına bayt — ve grouped-query attention’ın büyük ölçüde onu küçültmek için var olduğunu belirtin. PagedAttention sistem tarafındaki cevap: bloklar hâlinde yönetin ki 2.000 token’a yer ayırıp 200 kullanan bir dizi gerisini israf etmesin.

S: Batchleme nerede yardım eder, nerede zarar verir?

İşlem hacmine yardım eder, çünkü decode bellek sınırlıdır ve ağırlık taşıması paylaşılır. İstek başına gecikmeye zarar verir, çünkü her dizi artık makineyi paylaşmaktadır. Sürekli batchleme ödünleşimin en kötü sürümünü — statik batchlemedeki kuyruk başı tıkanmasını — ortadan kaldırır ama ödünleşimin kendisini kaldırmaz. Batchlemenin bedava olduğunu söyleyen bir aday hiç gecikme SLO’su işletmemiştir.

31.9.2 Gerçekten bir sistem yayına alıp almadığınızı sınayanlar

S: Geçen ay fatura ikiye katlandı, trafik katlanmadı. Teşhis edin.

Önce girdi token’ları, çünkü baskın olan onlar: bu kitabın özetleme ölçümünde 53’e karşı 890. Büyümüş bir prompt arayın — daha fazla parça döndüren bir erişim adımı, bir bölüm kazanmış bir sistem prompt’u, kırpılması durmuş bir konuşma geçmişi. Sonra önbellek isabet oranına bakın, çünkü prompt’un tepesine taşınmış oynak bir token önbelleklemeyi sessizce kapatır ve tıpkı bir fiyat değişikliği gibi görünür. Model ya da sağlayıcı değişikliklerine ancak ondan sonra bakın.

S: Modeli değiştirmeden gecikmeyi nasıl yarıya indirirsiniz?

Önce ayrımı saptayın — %90’ı prefill’se çıktı tarafındaki değişiklikler sizi oraya götüremez. Sonra: kararlı ön ek için prompt önbellekleme (burada prefill’de 153 kat hızlanma ölçüldü); prompt’un kendisini kısaltmak (kazancı doğrusaldır); ve kalan beklemeyi okuma süresine çevirmek için akış. Her birinden sonra neyi ölçeceğinizi söyleyin ve spekülatif kod çözmenin listede yalnızca bir taslak model barındırabiliyorsanız yer aldığını belirtin. Kazandırdığı şey decode gecikmesidir; maliyeti de düşürüp düşürmediği, hesaplama gücü için nasıl ödeme yaptığınıza bağlıdır.

S: 99. yüzdelikte (p99) bir gecikme SLO’su istiyoruz. Neye ihtiyacınız var?

Bir batch boyutu tavanına, kuyruk uzadığında ne olacağını belirleyen bir kabul (admission) politikasına, uzun bağlamlı isteklerin kısa olanların önünü tıkamaması için ayrı bir rotaya ve tek bir küresel sayı yerine rota başına TTFT ölçüm düzeneğine. Sonra rahatsız edici kısmı söyleyin: bir p99 SLO’su ile azami işlem hacmi doğrudan çatışır ve karar vericilere hangisini seçtikleri açıkça söylenmelidir.

31.10 2026 alet çantası

Bir dizüstünde 3B’lik bir modele karşı yaklaşık 150 istek — 20 koşul üzerinde her biri beş kez tekrarlanan 130 zamanlanmış çağrı (önbellek koşulu her tekrarda bir soğuk bir sıcak çağrı gönderiyor), artı 11 kanarya (denetim) çağrısı ve atılan 2 ısınma çağrısı — yalnızca iki şeye ihtiyaç duyuyor; bunlar aşağıdaki birinci tabloda. İkinci tablo, gerçekte üzerinde servis yapacağınız araçlar. Hiçbiri burada kullanılmadı: bu bölümün aktardığı şey oranlardır ve gerçek bir karta geçildiğinde de geçerli kalan kısım onlardır.

Bu sayıların geldiği yığın. Python 3.14.5, Apple Silicon
kütüphane kullanılan sürüm burada ne işe yarıyor
ollama 0.24.0 yerel sunucu; /api/generate ucu istek başına prefill ve decode ayrımını bildiriyor, bu ölçümleri ucuzlatan şey de o
llama3.2:3b Q4_K_M sınanan model; ısıl kısıtlamaya girmeden bir dizüstünde ölçülebilecek kadar küçük

Yukarıda kullanılmayan, ama bilinmesi gerekenler:

Daha geniş ekosistem, yazım anındaki sürümler
proje güncel sürüm ne zaman başvurursunuz
vllm 0.28.0 referans açık kaynak sunucu: sürekli batchleme, sayfalı KV önbelleği, ön ek önbellekleme (Kwon vd. 2023)
sglang 0.5.18 yapılandırılmış programlar ve agresif ön ek yeniden kullanımı çevresinde kurulmuş bir servis çalışma zamanı
text-generation-inference 3.3.7 Hugging Face’in sunucusu, onların ekosistemine en yakın olanı; artık bakım modunda ve Hugging Face yeni kurulumlar için vLLM ya da SGLang öneriyor
llama.cpp b10690 (yuvarlanan) CPU ve Apple Silicon çıkarımı; ollama’nın üzerine kurulduğu motor (yeni ollama sürümleri birçok modeli aynı ggml kütüphanesi üzerindeki kendi motorlarıyla da çalıştırıyor). Yapı başına etiketleniyor, bir tanesini sabitleyin
tensorrt-llm 1.2.1 işlem hacminin son %20’si önemliyse NVIDIA’nın derleyici tabanlı çalışma zamanı
llmcompressor 0.13.0 servis etmeyi düşündüğünüz bir modele GPTQ ve AWQ nicemlemesi uygulamak (Frantar vd. 2023; Lin vd. 2024)

31.11 Lab: kendi iki yarınızı ölçün

Zamanlama bildiren herhangi bir yerel sunucu iş görür; amaç bu bölümdeki sayıları yeniden kullanmak değil, kendi sayılarınızı üretmek.

31.11.1 Adım 1 — modelinizin prefill ve decode hızlarını alın

Altı prompt uzunluğu, hiçbir şey önbelleğe girmesin diye her biri ayrı metinle, sabit ve kısa bir çıktı. Prompt token’ı, prefill saniyesi, çıktı token’ı, decode saniyesi kaydedin. Bu kayıtlardan iki hız hesaplanır — prefill ve decode hızı — ve akılda tutulacak sayı ikisinin oranıdır.

31.11.2 Adım 2 — TTFT eğrinizi bulun

Akışı açın ve üç bağlam uzunluğunda ilk token’a kadar geçen süreyi kaydedin. TTFT’yi toplamın payı olarak çizin. Dik biçimde yükselmiyorsa prompt’larınız üretimdeki prompt’ların çoğundan kısadır ve gerçekçi bir tanesiyle yeniden sınamalısınız.

31.11.3 Adım 3 — önbelleğinizi ölçün, sonra bozun

Aynı uzun prompt’u iki kez gönderip ikisini de kaydedin. Sonra prompt’un tepesine her çağrıda yeni bir zaman damgası koyarak iki kez daha gönderin — aynı damgayı iki kez koymak yine önbelleğe düşerdi. İkinci çift hızlanmayı tümüyle yitirmeli. İki sayıyı da saklayın; aradaki fark prompt sıralamasının değeridir.

31.11.4 Adım 4 — KV önbelleğini gigabayt cinsinden boyutlandırın

Modelinizin üstverisinden (metadata) katman sayısını, KV head sayısını ve head boyutunu alıp token başına baytı hesaplayın. Sonucu, yapılandırmak üzere olduğunuz bağlam uzunluğuyla çarpın. Cevap sizi şaşırtıyorsa, adımın amacı zaten oydu.

31.11.5 Adım 5 — çizelgeyi kurun

Prompt token’ları, önbellekli pay, çıktı token’ları, aylık istek sayısı, üç fiyat. Aylık bir rakam üretin; sonra prompt’ta %50’lik bir azalmanın ve %60’lık bir önbellek isabet oranının ayrı ayrı ne kazandırdığını hesaplayın. Sıralayın.

31.11.6 Adım 6 — bir değişikliği savunun

İlk çekeceğiniz tek kolu, adım 5’ten onu haklı çıkaran sayıyla ve kabul ettiğiniz kalite riskiyle birlikte adlandıran bir paragraf.

31.11.7 Teslim edilecekler

İki hızınız ve oranları; gerçekçi bağlam uzunluğunuzdaki TTFT payı; aynı prompt için önbellekli ve önbelleksiz prefill; yapılandırdığınız pencere için KV önbelleği boyutu; ve adım 6’daki paragraf.

31.12 Alıştırmalar

  1. İki hızınız. Çalıştırabileceğiniz bir model için prefill ve decode saniyedeki token sayısını ölçün. Oranı raporlayın ve ölçmeden önce 4.000 token’lık bir prompt ile 100 token’lık bir cevabın toplam gecikmesini tahmin edin. Sonra ölçün.

  2. Önbelleği bilerek bozun. Uzun ve kararlı bir ön eki olan bir prompt alıp soğuk ve sıcak prefill’i ölçün. Bir zaman damgasını prompt’un altından tepesine taşıyıp tekrarlayın. İki sayıyı da ve takımınız için yazacağınız tasarım kuralını raporlayın.

  3. Bir pencereyi boyutlandırın. Dağıtmayı düşünebileceğiniz üç model için üstverilerinden token başına KV önbelleği baytını hesaplayın ve bağlam uzunluğuna karşı bellek tablosu üretin. 24 GB’lık bir kartta 32k bağlamda hangi modeli çalıştıracağınızı söyleyin.

  4. Gerçek sayılarla çizelge. Gerçekten yayına aldığınız ya da tarifini yazdığınız bir özelliği alın. Gerçek prompt ve çıktı token’larını sayın — tahmin değil, sayın — ve kendi trafiğinizde aylık faturayı hesaplayın. Sonra prompt %40 kısayken yeniden hesaplayın ve prompt’un hangi kısımlarını keseceğinizi söyleyin.

  5. Bağlama karşı TTFT. Kendi kurulumunuz için en az beş uzunlukta, prompt uzunluğuna karşı ilk token’a kadar geçen süreyi çizin. Ölçümlere bir doğru uydurun. Eğimi kullanarak sınamadığınız bir bağlam uzunluğundaki TTFT’yi tahmin edin, sonra sınayın. Prefill’in attention kısmı prompt uzunluğundan hızlı büyüdüğü için uzun bağlamda doğrusal uydurma TTFT’yi olduğundan düşük tahmin eder; doğrusallıktan sapmayı da raporlayın.

  6. Çıktı uzunluğu miti. Birinin modelden daha kısa cevaplar isteyerek para tasarrufu önerdiği bir yer bulun. Ölçtüğünüz girdi/çıktı oranını kullanarak o değişikliğin gerçekte ne kazandıracağını hesaplayın.

31.13 Daha derine

Okumadan önce: kendi ürününüzden gerçek bir istek alın ve prompt token’ı ile çıktı token’ı sayılarını yazın. Aşağıdakilerin neredeyse hepsi, kendi oranınızı bildiğinizde çok daha kolay değerlendirilir.

Makalelerin ötesi
kaynak ne olduğu
vllm referans açık kaynak sunucu; kodu okunabilir ve çizelgeleyici sürekli batchlemenin somutlaştığı yer
vLLM’in PagedAttention tasarım notu KV önbelleği sayfalar hâlinde, uygulayan insanlar tarafından anlatılıyor (Kwon vd. 2023)
Transformer Inference Arithmetic prefill/decode asimetrisinin nereden geldiği, iddia edilerek değil türetilerek
How To Scale Your Model bellek bant genişliği ve aritmetik yoğunluk özenle işlenmiş; Pope vd.’nin (2023) kitap boyundaki sürümü
Databricks’in çıkarım performansı yazısı gerçek donanımda ölçülmüş batchleme, TTFT ve işlem hacmi — bir dizüstünün gösteremeyeceği şey
llama.cpp bu bölümdeki sayıların altındaki motor ailesi; CPU ya da Apple Silicon üzerinde servis yapıyorsanız KV önbelleği işleyişini okuyun
tensorrt-llm işlem hacminin son %20’sinin yapı karmaşıklığında neye mal olduğu, ki isteyip istemediğinize karar verebilesiniz
llm-compressor servis etmeyi düşündüğünüz bir modele nicemleme uygulamak, değerlendirme döngüsü de ekli
Birincil kaynaklar
konu kaynak
sayfalı bellek olarak KV önbelleği Kwon vd. (2023)
sürekli batchleme ve statik batchlemenin bir kartı neden israf ettiği Yu vd. (2022)
4 bite eğitim sonrası nicemleme Frantar vd. (2023)
aktivasyona duyarlı ağırlık nicemlemesi Lin vd. (2024)
spekülatif kod çözme Leviathan vd. (2023)
büyük modeller için çıkarım aritmetiği Pope vd. (2023)

31.14 Akılda kalması gerekenler

  • Bir istek tek değil, iki işlemdir. Prefill prompt’u tek bir paralel geçişte okur; decode cevabı seri biçimde teker teker token yazar. Burada kabaca saniyede 2.000’e karşı 190 token ölçüldü — bir token yazmak, bir token okumaktan yaklaşık on bir kat uzun sürüyor. Her servis kararı isteklerinizin hangi yarıdan yapıldığından çıkar.

  • Gecikme genellikle prompt’tur. 7.427 token’lık bağlamda wall-clock süresinin %91,0’ı ilk karakter belirmeden önce bitmişti. Akış o sayıyı değiştirmez; yalnızca isteğin geri kalanını görünür kılar. İlk token’a kadar geçen sürenin kolu prompt uzunluğu, ondan sonra da önbelleklemedir.

  • Uzun bağlamın bedeli iki kez alınır. Bir kez prefill’de, sonra da üretilen her token’ı yavaşlatarak: decode boş bir pencere ile 7.400 token’lık bir pencere arasında saniyede 189’dan 141 token’a düştü.

  • KV önbelleği modelin kendisinden büyük olabilir. Token başına 112 KiB’de, 131k’lık bir pencere 14 GiB önbellek demek — 2,0 GB’lık dört bitlik bir dosyanın 7,5 katı, aynı ağırlıkların fp16 hâlinin 2,3 katı; burada fiilen kullanılan sekiz bitlik önbellekle (7,4 GiB) dosyanın 4,0 katı. Kurulumunuza uyan oranı kullanın; ama “sığar mı?” her hâlükârda bir bağlam uzunluğu sorusudur. O sayının 42 değil 14 olması grouped-query attention sayesinde. Bir kartı ağırlıklar + önbellek × eşzamanlı dizi + pay diye boyutlandırın; uzman karışımı bir modelde etkin değil toplam parametreleri sayın.

  • Prompt önbellekleme ilk koldur ve kazancı küçük değildir. Sıcak prefill prompt uzunluğundan bağımsız olarak 12–16 ms’de sabitti: 4.247 token’da prefill’de 153 kat, tüm istekte 9,0 kat hızlanma. Bayt bayt aynı bir ön ek gerektiriyor; öyleyse prompt’u kararlılığa göre sıralayın ve tepesine yakın oynak hiçbir şey koymayın.

  • Batchleme işlem hacmi alır, gecikme öder. Decode bellek sınırlıdır, dolayısıyla diziler ağırlık taşımasını paylaşabilir; sürekli batchleme en yavaş diziyi beklemek yerine yuvaları dolu tutar (Yu vd. 2022; Kwon vd. 2023). İşlem hacmi kazancını gecikme maliyetinden söz etmeden raporlamak, bir değerlendirme toplantısını yanıltmanın tipik yoludur.

  • Ödediğinizin çoğu okumaya gidiyor. Bu kitabın kendi özetleme ölçümünde 53 üretilmiş token’a karşı 890 prompt token’ı — bu bir token oranı; çıktı token’ları daha pahalı fiyatlandığında parada 3’e 1 ya da 4’e 1’e yaklaşır. Daha kısa cevaplar istemek az kazandırır; daha az göndermek çok kazandırır. Kalıp (boilerplate) metni ayıklamak, kalite riski olmayan tek “daha az gönder” biçimidir; model değiştirmek daha çok kazandırabilir ama bir kalite kararıdır — erişilmiş bağlamı ya da few-shot örnekleri atmak gibi, değerlendirmeyle sınanmalıdır.

  • Kolları size neye mal olduklarına göre sıralayın. Gerçekten gereksiz token’ları çıkarmak ve önbellekteki bir ön eki yeniden kullanmak kalite riski taşımaz ve önce gelir; modelin kullandığı bağlamı atmak bir kalite kararıdır. Daha küçük bir model ve nicemleme (Frantar vd. 2023; Lin vd. 2024) kaliteden ödün vererek, spekülatif kod çözme (Leviathan vd. 2023) ise barındırılacak ek bir taslak model karşılığında daha düşük gecikme kazandırır; üçünün de yeri başkasının çalıştırdığı bir benchmark tablosu değil, sizin değerlendirmenizdir.