Mehmet Can Akay
← Tüm yazılar

Yerel LLM sistemlerinde backpressure ve ölçekleme

Kapasite istek sayısı değil, KV bütçesidir. Eşzamanlılık, preemption, admission control ve goodput ile yerel LLM servisini hedeflenen gecikmede tutmak.

· 32 dk okuma

  • llm
  • backpressure
  • vllm
  • ölçekleme

Yüksek trafikli web sistemleri uzun süredir durumsuz kabul edilir. Yük artınca replika sayısı artırılır, gecikme yükselince yük dengeleyicinin arkasına yeni kopyalar konur, veritabanının önüne önbellek alınır, bağlantı havuzları ayarlanır. Bu düzen, isteklerin birbirinden bağımsız ve kısa ömürlü olduğu sistemlerde geçerlidir.

Llama, Mistral veya Qwen gibi modeller şirket içi GPU altyapısında (vLLM, TGI, SGLang, Ollama) çalıştırıldığında aynı düzen kısmen geçerliliğini yitirir. Az sayıda kullanıcıda yanıt süresi kabul edilebilir kalır. Eşzamanlı kullanıcı, agent çağrısı ve gece çalışan toplu işler arttığında tablo değişir. Tipik bir doygunluk anı şöyle görünür:

Incoming Requests    : 10.247
GPU Utilization      : %98
KV Cache Utilization : %96
Queue Depth          : 6.431
P95 TTFT             : 38 sn

API ayaktadır, pod’lar sağlıklıdır, GPU çalışmaktadır. İsteklerin büyük kısmı buna rağmen hedeflenen sürede tamamlanmaz. Kesinti yoktur. Sistem, kapasitesinin üzerinde iş kabul ettiği için cevap üretememektedir.

Klasik API’ler çoğunlukla hesaplama yoğun ve durumsuzdur. Yerel Large Language Model: parametrelerinde sıkıştırılmış dil ve dünya bilgisiyle metin üreten model. servisleri bellek ve bellek bant genişliği ile sınırlıdır ve istek sürdükçe durum tutar. Sonuç ölçülebilir: 10.000 isteğin tamamını içeri almak yerine, hedeflenen gecikme içinde tamamlanabilecek kadarını kabul edip kalanını kapıda yönetmek gerekir. Buna Kapasite dolduğunda yeni işi içeri almak yerine kapıda yavaşlatmak veya reddetmek. Kuyruk borcu saklar, kapasite üretmez. denir.

Aşağıda önce kapasitenin nasıl hesaplandığı kurulur. Ardından yerel LLM altyapısında görülen yirmi darboğaz, donanım nedeni ve uygulanabilir karşılığıyla sıralanır. Noktalı terimlerin üzerine gelince kısa açıklama açılır.

Kapasitenin yeni birimi

Her istek eşit değildir

Aynı endpoint’e gelen iki istek düşünün:

Request A                     Request B
---------                     ---------
Input  :    300 token         Input  : 30.000 token
Output :    100 token         Output :  4.000 token

API Gateway açısından ikisi de 1 istektir. Rate limiter açısından da. Prometheus’taki http_requests_total sayacı açısından da. GPU açısından ise aralarında iki büyüklük mertebesi fark vardır.

Yıllardır kapasiteyi RPS, CPU, memory ve latency üzerinden düşündük. LLM tarafında bunların yanına yeni bir boyut giriyor: token. Artık anlamlı olan metrikler requests/second değil, input tokens/second, output tokens/second, active tokens ve queued tokens. 1.000 ağır istek, 10.000 hafif istekten daha fazla baskı oluşturabilir.

LLM sisteminde istek sayısı, iş yükünü tek başına anlatmaz.

KV bütçesi

LLM servisinde eşzamanlılık sınırını belirleyen şey çoğu zaman hesaplama gücü değil, Üretilen her token için Key ve Value vektörlerinin VRAM'de tutulması. Eşzamanlı context'in asıl bellek maliyetidir. için ayrılabilen VRAM’dir. Bu, kâğıt üzerinde tahmin edilebilen bir sayıdır. max_num_seqs gibi parametreler bu tahminden başlar; kesin sınır, motorun açılışta ölçtüğü boş bellek ve yük testidir.

KV cache’in token başına maliyeti şu formülle bulunur:

KV bytes/token = 2 × katman_sayısı × kv_head_sayısı × head_dim × byte/eleman

Baştaki 2, Key ve Value tensörleridir.

Llama-3-70B için (80 katman, GQA ile 8 KV head, head_dim 128, FP16):

2 × 80 × 8 × 128 × 2 = 327.680 byte ≈ 0,32 MB / token

Buradaki kritik nokta kv_head_sayısı değerinin attention_head_sayısı ile karıştırılmamasıdır. Llama-3-70B’nin 64 attention head’i vardır ama Grouped Query Attention sayesinde yalnızca 8 KV head tutar. Bu 8 kat fark, uzun context’in bellek maliyetini belirler. GQA olmasaydı aynı mimari 128K context’te yaklaşık 320 GB KV cache isterdi; GQA ile bu rakam yaklaşık 40 GB’a iner.

Modelin tek karta sığması ayrı bir konudur. FP16 ağırlık yaklaşık 140 GB tuttuğu için 80 GB’lık bir kartta servis, kuantizasyonla mümkün olur. GQA context bütçesini küçültür. Kuantizasyon ağırlık bütçesini küçültür.

Aşağıdaki tablo ölçüm değil, büyüklük tahminidir. gpu-memory-utilization her GPU’da ayrı uygulanır. Aktivasyon ve CUDA graph satırı varsayımdır. vLLM açılışta boş belleği kendisi profiller; üretimdeki tavan bu tablonun kendisi değil, o profil ve yük testidir.

2× H100 (160 GB toplam), FP8 kuantize edilmiş 70B model:

Kalem Tahmini
Toplam VRAM 160 GB
gpu-memory-utilization 0.90 ile ayrılan 144 GB
Model ağırlıkları (FP8) −70 GB
Aktivasyon + CUDA context + CUDA graph −9 GB (varsayım)
KV cache bütçesi ≈ 65 GB
Cache tipi Token başına Kapasite
FP16 KV 0,32 MB ≈ 200.000 token
FP8 KV 0,16 MB ≈ 400.000 token

Bu 200.000 token, sistemin aynı anda hayatta tutabileceği toplam context tahminidir. Eşzamanlı istek sayısına çevirmek için ortalama context uzunluğuna bölünür:

Ortalama context Eşzamanlı sequence
4.000 token ~50
16.000 token ~12
32.000 token ~6

Aynı donanım, aynı model, aynı GPU. Aradaki fark yaklaşık 8 kattır. max_num_seqs = 128 bu hesabı yapmadan verilirse, sistem 4K ortalama context’te bile tahminî bütçesinin iki buçuk katını kabul etmeye çalışır. Sonucu preemption bölümünde görüyoruz.

Hedef throughput değil, goodput

İkinci temel kavram: kuyruk kapasite üretmez.

Sistem saniyede 300.000 token işleyebiliyor ama gelen yük 800.000 token/saniye ise, her saniye 500.000 token borçlanılır. Kuyruk bu borcu bir süre saklar, ödemez. Little Yasası bunu şöyle yazar:

L = λ × W

L : sistemdeki ortalama istek sayısı
λ : varış hızı
W : sistemde geçirilen ortalama süre

λ servis hızını aştığı anda, sonsuz kuyrukta W büyümeye devam eder.
Kuyruk uzunluğu artık kapasiteyi değil, teslim edilmeyecek işin miktarını ölçer.

M/M/1 yaklaşımında kuyrukta bekleme süresi, servis süresine göre doluluk oranı ρ ile ρ/(1−ρ) oranında ölçeklenir:

Doluluk (ρ) Bekleme çarpanı
%50 1×
%80 4×
%90 9×
%95 19×
%98 49×

Bu ρ, kuyruk teorisindeki servis doluluğudur. nvidia-smi üzerindeki GPU yüzdesi ile aynı sayı değildir. Kart %98 dolu, kuyruk kısa ve gecikme hedefi tutuluyorsa sistem hedefini tutturuyor olabilir. GPU yüzdesi tek başına başarı ya da çöküş ilanı değildir. Üretim hedefi GPU’yu doldurmak değildir.

Doğru hedef Belirlenen gecikme hedefinin altında tamamlanan iş. Zaman aşımına uğrayan isteğin token'ı throughput'a yazılır, goodput'a yazılmaz.’tur: belirlenen SLO (örneğin P95 Time to First Token: kullanıcının ilk çıktı parçasını görmesine kadar geçen süre. Uzun context bunu şişirir. 2 saniyenin altında, P95 TPOT 50 ms altında) ile tamamlanabilen istek sayısı. Zaman aşımına uğramış bir isteğin ürettiği token throughput’a sayılır, goodput’a sayılmaz. Bu ayrım, yazının geri kalanındaki kararların ölçütüdür.

Sistemin şekli

Kapasite bu şekilde tanımlandığında mimari şu hâli alır:

   Client
     │
     ▼
  ┌──────────────────┐
  │  LLM Gateway     │  auth, kota, tokenizer ile boyut ölçümü
  └────────┬─────────┘
           ▼
  ┌──────────────────┐
  │ Admission Ctrl   │  kabul / reddet / küçük modele yönlendir
  └────────┬─────────┘
           ▼
  ┌──────────────────┐
  │ Token-Aware Queue│  TTL, öncelik, tenant adaleti
  └────────┬─────────┘
           ▼
  ┌──────────────────┐
  │   Scheduler      │  continuous batching, chunked prefill
  └────────┬─────────┘
           ▼
  ┌──────────────────┐
  │ Inference Engine │
  └────────┬─────────┘
           ▼
      ┌────┴────┐
   Compute   KV Cache          ← gerçek sınır burada
           │
           └──────────────────────┐
                                  │
     Kapasite baskısı yukarı akar │
     (backpressure)               │
  ←───────────────────────────────┘

En alttaki sınırın en üstteki kabul kararını etkilemesi gerekir. Bu geri bildirim hattı yoksa sistem aynı şekilde dolar: kapıdan sınırsız kabul, ortada sınırsız kuyruk, sonda sabit donanım.

1. KV cache tüketimi ve GPU bellek taşması

Mekanizma. KV cache, Transformer modellerinde üretilen her token için geçmiş token’ların Key ve Value vektörlerinin VRAM’de saklanmasıdır. llama.cpp, vLLM, SGLang veya Hugging Face kullanırken varsayılan olarak devrededir. Kapatılırsa token başına üretim karmaşıklığı O(N)’den O(N²)’ye, tüm dizi için O(N²)’den O(N³)’e çıkar ve model pratikte kullanılamaz hâle gelir.

70B bir model tek 80 GB’lık karta FP16 olarak sığmaz. FP16/BF16’da 70 milyar parametre yaklaşık 140 GB yer kaplar. Tek karta sığdırmaktan söz ediliyorsa kuantizasyon da söz konusudur. Aşağıdaki “KV için kalan” sütunu, ağırlık düşüldükten sonra kabaca bırakılan paydır. Aktivasyon ve CUDA graph bu payı daha da küçültür.

Precision 70B ağırlık Tek H100 (80 GB) KV için kalan
FP16/BF16 ~140 GB sığmaz —
FP8 / INT8 ~70 GB çok dar ~2 GB
INT4 (AWQ/GPTQ) ~35 GB rahat ~37 GB

KV için kalan alan, eşzamanlılık kapasitesine dönüşür. INT4 ile servis edilen bir 70B, FP8 versiyondan daha fazla KV payı bıraktığı için daha fazla eşzamanlı sequence taşıyabilir. Ağırlıkların küçülmesi doğrudan concurrency alanı açar. Hız ve doğruluk takası, tensor parallelism bölümündedir.

PagedAttention. Geleneksel tahsiste VRAM üzerinde ardışık alan aranır ve modelin maksimum context’i peşinen rezerve edilir. vLLM duyurusu ve PagedAttention makalesi, bu yaklaşımda KV bellek israfını %60–80 olarak ölçer. PagedAttention, işletim sistemlerindeki sanal bellek sayfalama mantığını VRAM’e taşır: belleği 16 token’lık bloklara böler, token üretildikçe fiziksel belleğin boş bir noktasından blok tahsis eder. Aynı kaynaklar israfı %4’ün altına indirir.

SOSP 2023 makalesi, bu bellek düzeni ve sürekli batching birlikteyken vLLM’in aynı gecikmede FasterTransformer ve Orca’ya göre 2–4 kat throughput verdiğini raporlar. Kazanç uzun dizilerde, büyük modellerde ve karmaşık decode algoritmalarında büyür. Tek başına sayfalama katmanına ait 15–20 katlık bir katsayı yoktur.

Sert eşzamanlılık sınırları. vLLM tarafında iki parametre kapıyı tutar:

  • max_num_seqs — tek iterasyonda paralel işlenebilecek maksimum aktif oturum sayısı. Dolduğunda yeni istek VRAM’e sokulmaz, motorun bekleme kuyruğunda kalır.
  • max_num_batched_tokens — bir iterasyonda Tensor çekirdeklerine gönderilecek toplam token tavanı.

Bu iki değer KV bütçesinden türetilir, sonra yük testiyle doğrulanır. max_num_seqs değerini bütçenin üstünde tutmak, sistemi kabul ettiği işi tamamlayamadığı bir rejime sokar.

KV cache kuantizasyonu. KV cache’i FP8’e düşürmek token başına maliyeti yarıya indirir, yani aynı VRAM’de eşzamanlı kapasite yaklaşık iki katına çıkar:

vllm serve <model>
    --kv-cache-dtype fp8
    --gpu-memory-utilization 0.90
    --max-num-seqs <bütçeden türet>

Bedelsiz değildir. FP8 KV cache uzun context’te doğruluk kaybı yaratabilir. Verimli çalışması Hopper ve Ada sınıfı donanımda beklenir. Ampere’de FP8 depolama her sürümde bellek kazancına dönüşmez. Kendi değerlendirme setinizle ölçmeden production’a almayın.

2. Preemption ve KV cache thrashing

max_num_seqs değerini bütçenin üstünde verirseniz sistem her zaman OOM vermez. Motor, KV taşmasını preemption ile keser. Bunun yerine aynı işi iki kez yapmaya başlar.

Mekanizma. Çalışan sequence’ler token ürettikçe KV cache büyür. Bütçe dolduğunda motorun elinde tek seçenek vardır: çalışan bir sequence’i preempt etmek. vLLM V1 mimarisinde varsayılan preemption modu SWAP değil RECOMPUTE’tur. Sequence’in KV blokları serbest bırakılır ve sıra ona tekrar geldiğinde o ana kadar üretilmiş context sıfırdan yeniden hesaplanır.

t0   [A][B][C][D][E]   KV %88
t1   [A][B][C][D][E]   KV %97   ← bütçe sınırı
t2   [A][B][C][D] ✂E   E preempt edildi, KV blokları iade
t3   [A][B][C][D][F]   F alındı, KV tekrar %96
t4   [A][B][C] ✂D [F]  D preempt edildi
t5   [A][B][C][E'][F]  E yeniden başlıyor — 2.400 token PREFILL TEKRAR

Dışarıdan görünen belirti P95 TPOT’un bozulmasıdır. GPU kullanımı %95’in üzerinde kalabilir, çünkü kart boşuna çalışmaktadır. Ayrı bir istisna fırlamayabilir. vLLM scheduler uyarı satırı basabilir; asıl kayıt num_preemptions_total sayacıdır.

Neden bu bir kısır döngüdür. Yeniden hesaplama ek prefill yükü yaratır, ek prefill decode’ları geciktirir, decode’lar geciktikçe sequence’ler KV’de daha uzun yaşar, KV baskısı artar, daha fazla preemption olur.

Ne yapmalı.

  • vllm:num_preemptions_total metriğini panoya koyun. Sayaç sürekli artıyorsa sistem kapasitesinin üzerinde çalışıyordur. Tek bir ani yük de sayacı bir kez oynatır; karar, sürekli artışa bakarak verilir.
  • max_num_seqs ve max_num_batched_tokens değerlerini KV bütçesinden türetin, sonra gerçek trafik dağılımıyla yük testi yaparak doğrulayın. Sabit uzunluklu sentetik testler bu problemi göstermez. Gerçek iş yükünün input ve output uzunluk dağılımı ağır kuyrukludur.
  • --gpu-memory-utilization değerini yükseltmek KV bütçesini büyütür. Aktivasyon ve CUDA graph için pay bırakılmazsa aynı ayar OOM’a döner.
  • Eşzamanlılığı kapıda sınırlamak, motorun içinde preemption ile sınırlamaktan ucuzdur. Preemption yapılmış işi atar. Kapıda reddedilen istek GPU’da iş başlatmaz.

3. VRAM fragmantasyonu

Mekanizma. Değişken boyutlardaki prompt ve yanıtlar nedeniyle standart CUDA bellek yöneticisi sürekli yer ayırıp serbest bırakır. Toplamda 15 GB boş VRAM olsa bile bu alan ardışık tek blok hâlinde olmadığı için 4 GB’lık yeni bir tahsis başarısız olur ve OOM alınır.

Çözüm. vLLM gibi modern motorlar dinamik tahsis yerine başlangıçta VRAM’in belirli bir yüzdesini tek büyük blok olarak rezerve eder (--gpu-memory-utilization). Tahsis ve iade, CUDA seviyesinde değil, motorun kendi bellek yöneticisi ve PagedAttention blokları üzerinden yürür.

PyTorch tabanlı özel servislerde ilk denenecek ayar PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True olur. Caching allocator’ın segmentleri büyütebilmesi fragmantasyonu azaltır.

Bir de gizli tüketici vardır: CUDA graph capture. Motor başlarken çeşitli batch boyutları için graph yakalar ve bu graph’lar VRAM tutar. --enforce-eager ile kapatılabilir. Birkaç GB geri gelir, kernel launch overhead’i yüzünden özellikle küçük batch’lerde decode gecikmesi artar. Varsayılan olarak kapatılmaz.

4. Prefill ve decode

Mekanizma. LLM çıkarımı iki fazdan oluşur ve bu iki faz donanımın farklı kaynaklarını tüketir:

Prompt
  │
  ▼
┌──────────────┐   Tüm context tek seferde matris çarpımına girer.
│   PREFILL    │   Tensor çekirdekleri dolu.
└──────┬───────┘   COMPUTE-BOUND.
       ▼
  First Token   ← TTFT burada ölçülür
       │
       ▼
┌──────────────┐   Her token tek tek üretilir. Ağırlıklar her adımda
│    DECODE    │   VRAM'den okunur.
└──────┬───────┘   MEMORY-BANDWIDTH-BOUND.
       ▼
   token → token → token   ← TPOT burada ölçülür

Tek bir kapasite rakamı iki fazı birden açıklamaz. Sistem prefill tarafında compute doygunluğuna, decode tarafında bant genişliği doygunluğuna ulaşır ve bunlar farklı yüklerde gerçekleşir.

Darboğaz. Ekranda kelimeleri akan 20 kullanıcının arasına 30.000 token’lık bir doküman özeti isteği girdiğinde, klasik scheduler hesaplama birimlerini bu yeni isteğin prefill adımına kilitler. Mevcut kullanıcıların token akışı saniyelerce durur. Model yavaşlamış değildir. O turda başka bir isteğin prefill’i çalışıyordur.

Chunked prefill. Uzun prompt tek iterasyonda işlenmez. Token bütçesine sığacak parçalara bölünür. Her iterasyonda bir prefill parçası ile aktif kullanıcıların birer decode token’ı birlikte hesaplanır.

Chunked prefill kapalı
  it.1  [30K prefill.........................]   diğer herkes bekliyor
  it.2  [A][B][C][D] decode
  it.3  [A][B][C][D] decode

Chunked prefill açık
  it.1  [prefill 1/8 ][A][B][C][D] decode
  it.2  [prefill 2/8 ][A][B][C][D] decode
  it.3  [prefill 3/8 ][A][B][C][D] decode

vLLM V1’de chunked prefill, model desteklediği sürece varsayılan olarak açıktır. Scheduler önce bekleyen decode isteklerini batch’ler, max_num_batched_tokens bütçesinden yer kaldıkça prefill planlar, sığmayan prefill’i parçalar. Ayarlanan şey özelliğin kendisi değil, max_num_batched_tokens değeridir. Küçük değer TTFT’yi kötüleştirir, TPOT’u sabitler. Büyük değer tersini yapar.

Prefix caching. Kurumsal senaryolarda isteklerin çoğunda sistem direktifleri, kurumsal şablonlar veya RAG’in sabit talimat bloğu (1.000–2.000 token) ortaktır. Prefix caching bu ortak blokların KV cache’ini tutar ve sonraki isteklerde ilgili prefill adımını atlar. Model destekliyorsa bu da vLLM’de varsayılandır. Kapatmak için enable_prefix_caching=False verilir.

İki nüans saatlerce uğraştırabilir:

  1. Prefix cache eşleşmesi ilk token’dan itibaren birebir olmak zorundadır. Sistem promptunun başına kullanıcı adı, tarih veya istek kimliği koyulursa hit oranı sıfırdır. Değişken alanlar prompt’un sonuna alınır.
  2. Erken vLLM V1 sürümlerinde chunked prefill açıkken uzun prompt parçalandığında cache kontrolü ilk parçada kalıyor, sonraki parçalar çalışan istek sayıldığı için yeniden bakılmıyordu. Bu davranış sürüme bağlıdır. Güncel scheduler, istek kuyruktan çıkarken önbelleğe bakacak şekilde değişmiş olabilir. İki özellik de açıkken hit oranı düşükse önce token eşleşmesi, sonra o sürümün chunked prefill ile cache etkileşimi doğrulanır.

Prefill/decode ayrışması. İleri seviye mimarilerde donanım ikiye bölünür: prefill için hesaplama odaklı GPU’lar, decode için bant genişliği odaklı GPU’lar. Prefill tarafında hesaplanan KV verisi, CPU ve işletim sistemi katmanını bypass eden RDMA ya da kasa içi NVLink/PCIe hatları üzerinden decode tarafına aktarılır. DistServe, Splitwise, NVIDIA Dynamo ve Mooncake bu yöndedir. Her iki fazı da aynı SLO ile sıkıştırmakta zorlanan, yüksek hacimli sistemler için anlamlıdır. On-prem tek rack kuran çoğu ekip için erkendir.

5. Head-of-line blocking ve boyut bazlı yönlendirme

Mekanizma. Tek havuzda hem 100 token’lık chatbot yanıtları hem 32.000 token’lık doküman özetleri işlendiğinde, büyük istekler küçükleri arkalarında bekletir.

Kuyruk:  A(500) → B(40.000) → C(300) → D(600)

FIFO ile C ve D, B'nin arkasında saniyelerce bekler.
İkisinin birlikte maliyeti, B'nin maliyetinin küçük bir payıdır.

Boyut bazlı yönlendirme. İstekler doğrudan GPU’ya gönderilmez. Gateway katmanında tokenizer ile token boyutu ölçülür ve iki havuza ayrılır:

                  ┌──> [1024 token altı]  ──> Havuz A
                  │                          küçük batch, düşük latency
Gelen İstek ──> Gateway                      interaktif chat
                  │
                  └──> [1024 token ve üstü] ──> Havuz B
                                               chunked prefill
                                               yüksek throughput, RAG/batch

Token sayımı için modelin kendi tokenizer’ı kullanılır. tiktoken OpenAI’ın BPE’sidir. Llama, Qwen veya Mistral tokenizer’larıyla uyuşmaz. Sapma Türkçe metinde büyür. Hugging Face tokenizers kütüphanesi, modelin kendi tokenizer dosyasıyla kullanılır.

Yönlendirme kararı “ilk kim geldi” değildir. Sınırlı GPU kapasitesinin o anda hangi işe verileceğidir. Karar girdileri: tahmini token maliyeti, tenant, öncelik sınıfı, SLA sınıfı, kuyrukta bekleme süresi.

Sürekli küçük işleri öne almak, büyük isteklerin sıra bulamamasına yol açar. Bekleme süresi karara girer. Bir istek kuyrukta bekledikçe önceliği artar (aging).

Girdi uzunluğu ölçülür. Çıktı uzunluğu ölçülmez. 300 token girdi, 4.000 token yanıt üretebilir. Yönlendirme tahmindir ve rezervasyon mantığıyla desteklenir.

6. Çok kiracılılık, adalet ve gürültülü komşu

Kurumsal LLM altyapısında sık görülen arıza, tek bir tenant’ın paylaşılan bütçeyi tüketmesidir. Finans ekibinin gece batch’i, müşteri hizmetlerinin gündüz trafiğini durdurabilir.

Mekanizma. Tek bir tenant, tek bir agent ya da tek bir script, saniyeler içinde token bütçesini tüketebilir. Klasik rate limiter bunu engellemez, çünkü istek sayısını sayar. Dakikada 60 istek limiti içinde kalan bir batch job, her isteği 32K context taşıyorsa sistemin tamamını tek başına tüketir.

Kota token cinsinden tanımlanır. Ticari sağlayıcıların TPM (tokens per minute) kullanmasının sebebi budur. Pratikte iki limit birlikte uygulanır:

Limit Ne sınırlar
Input tokens / dakika Prefill maliyeti
Output tokens / dakika Decode maliyeti
Eşzamanlı aktif sequence KV bütçesindeki pay
İstek / dakika Kötüye kullanım koruması

Kotalar tavanı belirler. Tavanın altında da paylaşım gerekir. Weighted fair queuing ya da deficit round robin ile her tenant’a token bütçesinden ağırlıklı bir pay verilir. “Fairness in Serving Large Language Models” makalesindeki Virtual Token Counter, her tenant için tüketilen token ile ilerleyen sanal bir saat tutar. Scheduler, sanal saati en geride olan tenant’ı seçer. Az kullanan hızlı cevap alır. Çok kullanan yavaşlar ve sırasız kalmaz.

Pratikte iş yükleri üç sınıfa ayrılır:

Sınıf SLO hedefi Davranış
Interactive TTFT 1 sn altında Önceliklidir, kuyruk TTL’i kısadır
Standard TTFT 5 sn altında Normal akış
Batch SLO yok Yalnızca boş kapasitede çalışır, gece penceresine ötelenebilir

Batch sınıfının ayrı bir API sözleşmesiyle çalışması, agent döngüleri bölümünde ele alınır.

7. Tensor parallelism ve replika

GPU eklenip sistemin hızlanmamasının sık sebebi topolojidir.

Mekanizma. Tensor parallelism (TP) modeli katman içinde GPU’lara böler. Her katmanda, her token için GPU’lar arasında all-reduce yapılır. Bu iletişim NVLink/NVSwitch üzerinde hızlıdır. PCIe üzerinde decode fazında maliyet artar, çünkü decode zaten bant genişliği kısıtlıdır ve araya senkron bir iletişim adımı girer.

Model tek GPU'ya sığıyor mu?
  │
  ├── EVET → N adet bağımsız replika.
  │          Her replika kendi batch'ini çalıştırır.
  │          İletişim maliyeti yoktur.
  │          Throughput neredeyse doğrusal ölçeklenir.
  │
  └── HAYIR → TP kullan:
              • TP grubunu NVLink'li GPU'lar içinde tut
              • TP derecesini modelin sığdığı en küçük değerde bırak
              • Node sınırını aşıyorsa node içinde TP,
                node'lar arasında pipeline parallelism

INT4 kuantize 70B tek H100’e sığıyorsa, 4 kart için TP=4 yerine 4 bağımsız replika çoğu iş yükünde daha yüksek toplam throughput verir. TP gecikme için, replika throughput için kullanılır.

Kuantizasyonun da fazlara göre takası vardır:

  • W4A16 (INT4 ağırlık) decode’u hızlandırır. Bant genişliği kısıtlı fazda daha az bayt okunur. Prefill’de dequantization overhead’i yüzünden yavaşlatabilir.
  • FP8, Hopper sınıfı donanımda hem belleği hem throughput’u iyileştirir. Native FP8 tensor core desteği vardır.

Hangi kuantizasyonun daha hızlı olduğu, iş yükünün prefill/decode oranına bağlıdır.

8. Kontrolsüz kuyruk büyümesi

Mekanizma. Gelen istekler sınırsız bir FIFO kuyruğuna atıldığında, binlerce istek biriktiği anda sıradaki bir isteğin GPU’ya ulaşması dakikaları bulur. İstemci zaman aşımına uğradığı için kuyruktaki isteklerin büyük kısmı işlenmeden önce ölüdür. Sistem, kimsenin beklemediği cevapları üretmek için sıraya girer.

Kuyruk derinliğinin üst sınırı hesaplanabilir:

Maksimum kuyruk derinliği = servis hızı × kabul edilebilir bekleme süresi

Örnek: 8 istek/sn tamamlanıyor, kabul edilebilir bekleme 5 sn
       → kuyruk tavanı ≈ 40 istek

Bu tavanın üzerindeki istek, SLO'yu zaten tutturamayacağı için
kuyrukta yer kaplamaz.

Kuyruğa giren her isteğe bir yaşam süresi atanır (Queue-TTL, örneğin 5 sn). Bekleme bu eşiği aştığında istek inference sunucusuna iletilmeden gateway’de düşürülür:

İstek gelir
   │
   ▼
Kuyruk doluluğu tavanın altında mı?
   ├── HAYIR ──> 429 + Retry-After, anında
   ▼ EVET
Kuyruğa gir, timestamp yaz
   │
   ▼
Scheduler sırayı aldığında: (şimdi − timestamp) > TTL ?
   ├── EVET ──> düşür, GPU'ya gönderme, 503 dön
   ▼ HAYIR
Motora gönder

İstemcinin vazgeçeceği isteği kuyrukta tutup GPU’ya sokmak yerine kapıda reddetmek, içerideki canlı isteklerin gecikmesini korur.

Sabit limit vermek istenmezse adaptif yaklaşımlar vardır. Netflix concurrency-limits kütüphanesindeki gradient algoritmaları ya da CoDel, gecikmedeki değişimi izleyerek eşzamanlılık tavanını ayarlar. LLM tarafında izlenecek sinyal RTT değil, TTFT ve kuyrukta geçen süredir.

429 “çok fazla istek gönderdin, yavaşla” demektir. 503 “şu an kapasite yok” demektir. İkisinde de Retry-After başlığı gider, çünkü istemcinin backoff hesabı bu süreye bakar.

9. Retry fırtınaları ve devre kesiciler

Mekanizma. Kuyruk tıkandığında istemciler başarısız istekleri sabit aralıklarla tekrar dener. Başarısız istekler kısa sürede katlanır ve sistem toparlanamaz. Toparlanmayı zorlaştıran şey çoğu zaman kesintinin kendisi değil, kesintiye verilen istemci tepkisidir.

Full jitter ile exponential backoff:

Bekleme = random(0, min(MaksimumBekleme, Taban × 2^DenemeSayısı))

random(0, …) olmadan yalnızca üstel backoff, aynı anda hata alan istemcilerin aynı anda tekrar denemesine yol açar. Dalga seyrekleşir, fırtına sürer. AWS’in “Exponential Backoff and Jitter” yazısındaki ölçümler, full jitter’ın iş miktarı ve tamamlanma süresi açısından güçlü bir varyant olduğunu gösterir.

Backoff tek başına yetmez, çünkü her istek en az bir kez daha denenebilir. Google SRE pratiğindeki retry bütçesi bir tavan koyar: istemci, son bir dakikadaki başarılı istek sayısının belirli bir yüzdesinden (örneğin %10) fazla retry yapamaz. Bütçe dolunca hata yukarı verilir. Retry trafiği sabit bir tavanla sınırlanır.

İstemcinin 15 saniyelik timeout’u varsa, bu süre istekle birlikte aşağı akar. Gateway’e 15 saniyelik deadline ile giren bir istek 12. saniyede scheduler’a ulaşıyorsa motora gönderilmez.

Gateway, motordan dönen 5xx ve timeout oranlarını izler. Hata oranı eşiği (örneğin %20) aştığında devre açılır:

        ┌─────────┐  hata oranı > eşik   ┌──────┐
        │ CLOSED  │ ───────────────────> │ OPEN │
        │ normal  │                      │ tümü │
        │  akış   │ <─────────────────── │ 503  │
        └─────────┘   deneme başarılı    └──┬───┘
             ▲                              │ soğuma süresi doldu
             │                              ▼
             │        başarısız        ┌───────────┐
             └───────────────────────  │ HALF-OPEN │
                                       │ sınırlı   │
                                       │ deneme    │
                                       └───────────┘

Open durumunda gelen istekler GPU’ya uğramadan 503 ve Retry-After ile döner. Devre kesici, iyileşmekte olan downstream’e yeni yük bindirmemek için vardır.

10. max_tokens ve rezervasyon bazlı kabul

Çıktı uzunluğu bilinmez. Bu bilinmezlik, admission control’ün açık noktasıdır.

Mekanizma. İstemci max_tokens göndermezse model varsayılan tavana kadar (çoğu kurulumda 2.048 veya 4.096) üretmeye devam eder. Kısa bir girdiye binlerce token üreten bir istek, KV bütçesini tek başına tüketir. Bu çoğu zaman kötü niyet değil, alanın boş bırakılmasıdır.

Kötüye kullanım tarafı da vardır. Klasik WAF kuralları bunu görmez:

Vektör Etkisi
“Bu kelimeyi sonsuza kadar tekrarla” Tek istek, tavan kadar decode
n / best_of = 20 Maliyet katlanır, istek sayısı 1 kalır
Aynı uzun prompt’un onlarca kez gönderilmesi KV bütçesi tek başına dolar
Stop sequence olmadan uzun üretim Decode bitmez

Admission controller isteği kabul ederken maliyeti o anda rezerve eder:

Rezerve edilen KV = (input_tokens + max_tokens) × bytes_per_token

Karar akışı:
  input_tokens ölç (modelin kendi tokenizer'ı ile)
       │
       ▼
  max_tokens verilmiş mi?
       ├── HAYIR ──> sunucu tarafı varsayılanı uygula (örn. 512)
       ▼ EVET
  max_tokens tenant tavanını aşıyor mu?  ──> kırp veya 400 dön
       │
       ▼
  n / best_of tavanı aşıyor mu?          ──> reddet
       │
       ▼
  Rezervasyon, kalan KV bütçesine sığıyor mu?
       ├── HAYIR ──> kuyruğa al veya 429
       ▼ EVET
  Kabul et, bütçeden düş, istek bitince iade et

Gateway’de max_tokens zorunluluğu, gereksiz decode yükünün önemli bir kısmını keser. Sunucu tarafında toplam üretim süresi için de bir tavan gerekir. max_tokens token sayısını sınırlar. Süre kesici, tek isteğin dakikalarca GPU tutmasını engeller.

11. Hayalet hesaplama ve iptal yayılımı

Mekanizma. İstemciler genellikle 15–30 saniyede zaman aşımına uğrar ya da kullanıcı sayfayı kapatır. HTTP bağlantısı kopsa bile inference motoru bunu algılamazsa arka planda token üretmeye devam eder. Yanıt iletilmez. GPU o süre boyunca meşgul kalır.

Sinyalin istemciden motora kadar taşınması gerekir. .NET tarafında bu CancellationToken, Go tarafında context.Context, Python tarafında asyncio task cancellation’dır. Zincirdeki bir halka sinyali yutarsa mekanizma çalışmaz:

Tarayıcı sekmesi kapandı
        │  TCP FIN
        ▼
Reverse proxy (NGINX/Envoy/YARP)
        │  upstream bağlantısını kapatır
        │  proxy buffering açıksa bunu geç fark eder
        ▼
API sunucusu
        │  request scope iptal edilir
        ▼
Inference motoru
        │  abort(request_id)
        ▼
Scheduler sequence'i batch'ten çıkarır
        │
        ▼
KV blokları iade edilir

Python’da iptal asyncio.CancelledError ile gelir. Bu istisna BaseException’dan türer, Exception’dan değil. Genel except Exception bloğu bu senaryoda çalışmaz ve abort çağrılmaz. Temizleme, iptalden etkilenen blokta değil, finally benzeri bir blokta olur. Streaming yanıt döndüren fonksiyon handler’ın kendisi değil, handler’ın sardığı bir üreteç olur.

KV cache harici bir Redis önbelleği değil, o isteğin çalışan durumudur. Model sonraki token’ı üretebilmek için önceki token’ların K ve V vektörlerine bakar. Bağlantı koptuğunda bu bloklar iade edilir. Amaç, iletilmeyecek bir yanıt için GPU döngüsü harcamamaktır. Üretilmiş metin duruyorsa aynı metin yeni bir istek olarak tekrar gönderilebilir. Prefix caching açıksa ortak prefix’e ait bloklar sonraki isteklere yarayabilir. Bu bir yan etkidir, devam etme tasarımı değildir.

12. Çalıştırılmayan istek

Önceki bölümler kapasiteyi nasıl koruyacağımızı anlatır. Bu bölüm, kapasiteyi nasıl hiç harcamayacağımızı.

Kurumsal soru dağılımı ağır kuyrukludur. “İzin nasıl talep edilir”, “fatura kesme adımları neler”, “VPN’e nasıl bağlanılır” soruları günde yüzlerce kez gelir ve her seferinde büyük model sıfırdan çalıştırılır. Prefix caching prefill maliyetini azaltır, decode maliyetini azaltmaz. Bir üst katman, yanıt seviyesinde cache’tir.

İstek
  │
  ▼
[1] Exact cache          prompt hash'i birebir eşleşiyor mu?
  │                      HIT: yanıt milisaniyede döner
  ▼ MISS
[2] Semantic cache       embedding benzerliği eşiği aşıyor mu?
  │                      HIT: kaydedilmiş yanıt döner
  ▼ MISS
[3] Router               soru küçük model sınıfında mı?
  │                      EVET: 8B modele git
  ▼ HAYIR
[4] 70B model

Yeni sorguyu, kapıdan geçmiş (onaylı veya NLI doğrulamalı) cevapların vektörleriyle karşılaştırır. Eşik yalnızca 'aynı soru'yu gösterir; kaliteyi beğeni veya guardrail belirler. yanlış kurulursa yanlış cevap üretir:

  • Benzerlik eşiği düşükse farklı sorulara aynı cevap döner. Eşik kendi verinizle kalibre edilir.
  • Kişiselleştirilmiş veya yetkiye bağlı yanıtlar cache’lenmez. Cache anahtarına yetki bağlamı girer.
  • Zamana bağlı bilgi (fiyat, stok, politika) için TTL kısa tutulur.
  • Hit oranı ve hit’lerin doğruluğu ayrı metriklerdir.

İkinci kaldıraç, her isteğe en büyük modeli vermemektir. Küçük model (3B–8B) cevaplar. Doğrulayıcı ya da güven skoru yetersizse büyük modele yükseltilir. Sınıflandırma, özetleme, format dönüştürme ve rutin SSS trafiğinin çoğu 8B ile karşılanabilir. Bu, aynı donanımda da yapılabilir.

13. RAG ve agent fan-out

Kullanıcının bir isteği, bir LLM çağrısı demek değildir. Bir RAG akışında:

1 kullanıcı sorusu
   │
   ├─> query rewriting      → 1 LLM çağrısı
   ├─> embedding            → 1 GPU çağrısı
   ├─> vector search        → CPU / vektör DB
   ├─> reranking            → 1 GPU çağrısı
   └─> generation           → 1 LLM çağrısı

   Toplam: 4 GPU çağrısı, 2 farklı model

Agent akışında Reason + Act: modelin düşünmesi, araç kullanması ve sonucu gözlemlemesi üzerine kurulu ajan döngüsü. döngüsü tipik olarak 5–15 LLM çağrısı üretir. Multi-agent kurgularda çarpan büyür. Kapasite planı “günde 5.000 kullanıcı isteği” üzerinden yapılırsa birkaç kat sapar.

Embedding ve reranking modelleri aynı GPU’yu paylaşıyorsa KV bütçesinden ve bant genişliğinden pay alır. Panelde yalnızca ana LLM metriklerine bakmak, VRAM’in nereye gittiğini açıklamaz. İki seçenek vardır: embedding ve rerank’i ayrı karta taşımak, ya da bunları ana modelin bütçesine dahil ederek planlamak.

Planlama tablosunda birim, kullanıcı isteği değil model çağrısı ve token’dır:

İş yükü Kullanıcı isteği/gün Çarpan Model çağrısı/gün
Basit chat 3.000 1,0× 3.000
RAG sorgusu 1.500 4,0× 6.000
Doküman agent’ı 200 9,0× 1.800
ERP agent’ı 400 6,0× 2.400
Toplam 13.200

Aynı tablo token cinsinden kurulduğunda donanım ihtiyacı ortaya çıkar. Bu, ilk tahminin birkaç katı olabilir. Çarpanlar örnektir. Kendi akışınızın araç sayısıyla değiştirilir.

14. Kaçak agent döngüleri

ReAct veya multi-agent mimarilerinde bir araç hatalı ya da beklenmedik formatta çıktı döndürürse agent, hedefi tutturana kadar kendini tekrar tetikleyebilir. Tek bir oturum, dakikada yüzlerce çağrı yaparak kapasiteyi tüketebilir. Model kendi kendine durmaz. Sınırlar orkestrasyon katmanına konur.

                    ┌──────────────────────────┐
                    │  Oturum başlar           │
                    │  iterasyon = 0           │
                    │  token bütçesi = 4.000   │
                    └────────────┬─────────────┘
                                 ▼
                    ┌──────────────────────────┐
              ┌────>│  LLM'i çağır             │
              │     └────────────┬─────────────┘
              │                  ▼
              │        iterasyon += 1
              │        harcanan token += kullanım
              │                  │
              │                  ▼
              │        iterasyon > max ?  ──── EVET ──> LoopLimit
              │                  │ HAYIR
              │                  ▼
              │        token bütçesi aşıldı ? ── EVET ──> Budget
              │                  │ HAYIR
              │                  ▼
              │        aynı araç + aynı argüman
              │        arka arkaya 3. kez mi ?  ── EVET ──> iptal
              │                  │ HAYIR
              │                  ▼
              │        model bitti mi ? ── EVET ──> sonucu döndür
              │                  │ HAYIR
              │                  ▼
              │        aracı çalıştır, sonucu bağlama ekle
              └──────────────────┘

Dört kesici birlikte gerekir:

  • İterasyon limiti — döngünün mutlak tavanı.
  • Token bütçesi — iterasyon sayısı düşük olsa da her adım uzun context taşıyorsa iterasyon limiti yetmez.
  • Döngü tespiti — aynı aracın aynı argümanla tekrar çağrılması, agent’ın ilerlemediğini gösterir.
  • Derinlik limiti — alt agent açan mimarilerde özyineleme derinliği sınırlanır.

Oturum başına maliyet tavanı da konur. Tavan aşılınca oturum kapanır. Tavan olmayan bir oturum, gece boyunca tekrarlayan bir döngüde bütçeyi tek başına tüketebilir.

15. Streaming, soket ve port tükenmesi

Mekanizma. LLM yanıtları Server-Sent Events ile iletildiğinde TCP soketi yanıt tamamlanana kadar (30–90 sn) açık kalır. Klasik API’lerde milisaniyeler süren bağlantıların yerini uzun ömürlü bağlantılar alır.

Dört tükenme noktası vardır:

Katman Sınır Belirti
İşletim sistemi file descriptor Too many open files
Proxy → upstream ephemeral port aralığı cannot assign requested address
Kubernetes node conntrack tablosu rastgele bağlantı düşmesi
Tarayıcı HTTP/1.1’de domain başına 6 bağlantı aynı uygulamadaki diğer istekler askıda kalır

Yalnızca ulimit -n artırmak birinci satırı düzeltir. Sık görülen tıkanma ikinci ve dördüncü satırdadır.

  • Dosya tanımlayıcı limitini yükseltin. NGINX kullanılıyorsa worker_rlimit_nofile ve worker_connections ayrıca artar.
  • Proxy ile upstream arasında keep-alive kullanın. Her istek için yeni TCP bağlantısı açan bir proxy, ephemeral port aralığını yüksek trafikte tüketir.
  • HTTP/2 kullanın. HTTP/1.1’de tarayıcılar domain başına 6 eşzamanlı bağlantı açar. Uzun SSE akışları bu kotayı doldurur. HTTP/2 multiplexing bu sınırı kaldırır. Stream tavanı ayrıca vardır. Tek bağlantının stream limitine takılmamak için birden fazla HTTP/2 bağlantısına izin verilebilir.
  • Proxy tamponlamasını kapatın. NGINX’te proxy_buffering off. Ek güvence için yanıtta X-Accel-Buffering: no. Tamponlama açıkken token’lar bloklar hâlinde ulaşır.
  • Yük dengeleyici ve ingress üzerindeki idle timeout değerlerini uzun üretime göre ayarlayın. 60 saniyelik varsayılan timeout, 90 saniyelik yanıtı sona doğru keser.
  • Uzun düşünme veya araç kullanımı aralarında heartbeat gönderin. Aradaki katmanlar sessiz bağlantıyı kapatabilir.

16. API sunucusu ve detokenization

Yüksek eşzamanlılıkta darboğaz her zaman GPU’da değildir. GPU’nun önündeki API süreci de dolar.

Mekanizma. Her üretilen token için token id’den metne çevirme, SSE çerçevesi, JSON serileştirme ve socket’e yazma yapılır. Yüzlerce eşzamanlı akışta bu zincir saniyede binlerce kez çalışır. Tek bir CPython sürecinde iş GIL’e takılır. GPU boşta beklerken uçtan uca gecikme artar.

Belirti. GPU kullanımı düşer, kuyruk büyür, motor metrikleri normal görünür, uçtan uca gecikme artar.

  • API sunucusunu motor sürecinden ayırın. vLLM bunu çok süreçli mimariyle destekler. API katmanını çoğaltmak GPU eklemeden kapasite kazandırır.
  • Token’ları tek tek değil küçük gruplar hâlinde flush edin. 2–3 token’lık gruplama syscall sayısını düşürür.
  • Detokenizer’ın Rust tabanlı implementasyonunu kullanın.
  • Gateway’de akışı parse edip yeniden üretmeyin. Baytlar olduğu gibi geçebilir.

17. Structured output maliyeti

Yanıtın belirli bir JSON şemasına uyması isteniyorsa (guided / constrained decoding), motor her adımda hangi token’ların geçerli olduğunu belirleyen bir durum makinesi ya da gramer kullanır. Bu yapının derlenmesi CPU tarafında yapılır. Karmaşık şemalarda saniyeler sürebilir. Derleme sırasında ilgili batch adımı bekleyebilir.

Ara sıra görülen TTFT sıçramasının sebebi, daha önce görülmemiş bir şemanın ilk kez derlenmesidir.

  • Şemaları önceden derleyip cache’leyin. Uygulama açılışında bilinen şemaları ısıtın.
  • Şema sayısını sınırlayın. Her istekte alan adları değişen şemalar cache’i işe yaramaz hâle getirir.
  • Derin iç içe geçmiş ve anyOf / oneOf ağırlıklı şemalardan kaçının. Gramer boyutu hızla büyür.
  • Şemayı sade tutup doğrulamayı uygulama tarafında yapmak çoğu durumda daha ucuzdur.

18. Soğuk başlatma ve otomatik ölçekleme

Kubernetes HPA yük geldiğinde yeni GPU pod’ları açmaya çalışır. 70B bir modelin diskten okunup VRAM’e yüklenmesi ve CUDA graph’ların hazırlanması dakikalar sürer. Reaktif ölçekleme, saniyeler içinde gelen bir dalgada geç kalır.

Adım Tipik süre
Bulut sağlayıcıdan GPU node tahsisi 2–10 dk
Container imajının çekilmesi 1–15 dk
Ağırlıkların diskten VRAM’e yüklenmesi 1–5 dk
CUDA graph capture ve ısınma 0,5–2 dk
Toplam 5–30 dk

Ağırlıklar imajın içindeyse çekme süresi bu aralığın üstüne çıkar. Aralıklar kurulumdan kuruluma değişir. On-prem, ağırlıklar yerel diskteyse yükleme dakikaların altına inebilir.

  • Ağırlıkları container imajından çıkarın. Paylaşımlı bir volume’dan ya da nesne depolamadan yükleyin.
  • En az bir boşta warm GPU pod’u bulundurun.
  • Ölçekleme metriği GPU kullanımı olmasın. Preemption sırasında GPU kullanımı yüksek görünür. Sinyaller: kuyruk derinliği, kuyrukta bekleme süresi, bekleyen token sayısı, P95 TTFT. KEDA bu metriklerden ölçeklemeyi destekler.
  • Yukarı hızlı, aşağı yavaş ölçekleyin. Dakikalarca açılan bir pod’u hemen kapatmak, sonraki dalgada aynı süreyi tekrar ödetir.
  • Her senaryo için ayrı 70B pod’u açmayın. S-LoRA, Punica ve vLLM’in çok-LoRA desteği, tek baz model üzerinde istek anında adaptör ağırlıklarını yükler.

19. Deploy, probe ve kendi kendini vurma

Bu bölüm bir performans konusu değildir. Üretimdeki LLM kesintilerinin bir kısmı buradan gelir.

Rolling update akan stream’leri keser. Standart bir deploy pod’a SIGTERM gönderir ve kısa süre sonra süreci kapatır. O anda devam eden uzun üretimler ortasından kesilir.

SIGTERM
   │
   ▼
Readiness probe başarısız döner
   │  load balancer yeni istek göndermeyi bırakır
   ▼
Yeni istek kabul etme, mevcut akışları bitir
   │
   ▼
Aktif sequence sayısı 0
   │
   ▼
Süreci kapat

terminationGracePeriodSeconds, en uzun makul üretim süresinden
uzun olmalıdır. 30 saniyelik varsayılan uzun üretim için yetmez.

Liveness probe sağlıklı pod’u öldürür. Uzun bir prefill sırasında event loop meşguldür, liveness probe cevap alamaz, kubelet pod’u yeniden başlatır. O pod’un yükü kalan pod’lara biner. Onlar da probe’u kaçırır. Sistemi düşüren şey trafik değil, sağlık kontrolüdür.

  • Liveness ve readiness ayrılır. Liveness yalnızca sürecin canlı olup olmadığını sorar. Model çıkarımı yapmaz.
  • Liveness endpoint’i motorun meşgul event loop’undan bağımsız olur. Değilse timeoutSeconds ve failureThreshold cömert tutulur.
  • Model yüklenirken pod’un öldürülmemesi için startupProbe kullanılır.
  • Readiness, kuyruk doluluğuna bağlanabilir. Pod kapasitesinin üstündeyken kısa süre hazır değil döner ve yük diğer replikalara kayar.

Spot veya preemptible GPU kullanılıyorsa node kısa sürede geri alınabilir. Kesinti bildirimi geldiğinde yeni istek kabulü durur, mevcut işler aynı drain akışıyla biter.

20. Donanım olayları

  • XID hataları ve ECC. GPU sürücüsünden gelen XID hataları sessiz kalabilir. Pod ayaktadır, kart bozuktur. nvidia-smi ve DCGM metrikleri toplanır. Düzeltilemeyen ECC hatasında node cordon edilir.
  • Termal kısıtlama. Yoğun yükte kart saat hızını düşürür. Throughput gerekçesiz düşmüşse sıcaklık ve güç limiti grafiklerine bakılır.
  • MIG. Tek kartı bölmek izolasyon sağlar. Her bölüme düşen bellek bant genişliği de bölünür. LLM decode bant genişliği kısıtlı olduğu için MIG çoğu LLM iş yükünde uygun değildir.
  • Topoloji. Kartların NVLink ile mi PCIe ile mi bağlı olduğu ölçülür: nvidia-smi topo -m. Tensor parallelism kararı bu tabloya bakar.

21. İzlenmesi gereken metrikler

Klasik panoda CPU, memory ve RPS vardır. LLM panosunda bunlar az şey anlatır. İşlevsel bir pano üç katmandan oluşur:

KULLANICI DENEYİMİ
  P50 / P95 / P99  TTFT
  P50 / P95 / P99  TPOT
  P95 uçtan uca süre
  Goodput: SLO ile tamamlanan istek/sn
  Zaman aşımı ve iptal oranı

KUYRUK VE KABUL
  Bekleyen istek sayısı ve bekleyen token sayısı
  Kuyrukta geçen süre ile motorda geçen süre, ayrı
  TTL nedeniyle düşürülen istek sayısı
  429 / 503 oranı, devre kesici durumu
  Tenant başına token tüketimi

MOTOR VE DONANIM
  KV cache kullanım oranı
  Aktif sequence sayısı
  Preemption sayacı
  Prefix cache hit oranı
  Input tokens/sn, output tokens/sn
  GPU compute, VRAM, sıcaklık, güç

İki ayrım panoyu değiştirir:

  1. Kuyrukta geçen süre ile motorda geçen süre ayrılır. Kuyrukta 27 saniye bekleyip motorda 1,2 saniye çalışan bir istekte yeni API pod’u açmak kapasite getirmez.
  2. KV cache kullanımı, GPU compute’un yanına konur. GPU %72 ve KV %96 ise yeni bir sequence için bellek kalmamış olabilir.

Bu metrikler gerçekçi bir yük altında anlam kazanır. Sabit uzunluklu sentetik istekler bu yazıdaki problemlerin çoğunu göstermez. Gerçek trafiğin input ve output uzunluk dağılımı çıkarılır, aynı dağılımla test edilir, max_num_seqs ile max_num_batched_tokens taranarak goodput’un tepe yaptığı nokta bulunur. vLLM benchmark aracı, GuideLLM ve genai-perf bu iş için kullanılabilir.

22. Gateway

Bu yazıdaki kararların büyük kısmı inference motorunda değil, önündeki gateway’de uygulanır. .NET ile kuran ekipler için bileşen haritası:

İstemci
   │  HTTP/2, SSE
   ▼
┌────────────────────────────────────────────────────────────┐
│  ASP.NET Core + YARP                                       │
│                                                            │
│  1  Kimlik / tenant çözümleme                              │
│  2  Tokenizer ile boyut ölçümü     HF tokenizers           │
│  3  Kota: token bazlı limiter      System.Threading        │
│       TPM + eşzamanlı sequence        .RateLimiting        │
│  4  Rezervasyon: input + max_tokens                        │
│  5  Kuyruk + TTL                   Channel, bounded        │
│  6  Devre kesici + retry           Polly                   │
│  7  Havuz seçimi (kısa/uzun)       YARP destination        │
│  8  İptal yayılımı                 CancellationToken       │
│  9  Akışı tamponlamadan geçir      IAsyncEnumerable        │
└────────────────────────────────────────────────────────────┘
   │
   ▼
Inference havuzu A / B
  • Bounded channel kullanın. Channel.CreateBounded ile kapasite vermek, kuyruk tavanının karşılığıdır. Sınırsız kanal, kuyruk bölümündeki problemi geri getirir.
  • Limiti LLM token cinsinden tanımlayın. TokenBucketRateLimiter içindeki token, bir LLM token’ı değil, bir izin birimidir. Kovadan düşülen izin sayısı, ölçülen LLM token sayısına eşitlenirse limit doğru birime taşınır.
  • İptal zincirini kırmayın. HTTP isteğinin iptal belirteci, upstream çağrısına kadar taşınır. Zincirin ortasında yeni bir belirteç üretmek, iptal yayılımını keser.
  • Yanıt tamponlamasını kapatın. Streaming yanıtta tamponlama açık kalırsa TTFT ölçümü de kullanıcıya giden akış da bozulur.
  • Bağlantı havuzunu ayarlayın. Upstream’e giden SocketsHttpHandler üzerinde bağlantı ömrü ve host başına bağlantı sayısı, uzun ömürlü akış profiline göre gözden geçirilir.
  • Kuyrukta geçen süreyi ayrı ölçün. Gateway, isteğin kuyruğa giriş ve motora çıkış zaman damgalarını tutan yerdir.

Kapanış

LLM altyapısında donanım kısıtları fizikle sınırlıdır. Değişen şey, kapasiteyi ölçme birimidir.

Web sistemlerinde uzun yıllar boyunca istek temel iş birimiydi. LLM sistemlerinde bir istek 300 token olabilir, diğeri 30.000. Biri GPU’da kısa süre yaşar, diğeri KV cache’te uzun süre kalır. Kapasite, istek/saniye değil, aynı anda hayatta tutulabilen context miktarıdır.

Kararlı sistem, 10.000 isteğin tamamını işlemeye çalışan sistem değildir. Kararlı sistem:

  • kapasitesini KV bütçesinden hesaplayan,
  • kabul kararını bu bütçeye göre kapıda veren,
  • kabul ettiğini öngörülebilir gecikmeyle tamamlayan,
  • kapasite fazlasını 429 veya 503 ile reddeden,
  • ölü istek, iptal edilmiş üretim, cache’lenebilir soru ve kaçak döngüyü başlatmayan sistemdir.

Modeli ayağa kaldırmak kurulum işidir. Herkes aynı anda kullanmaya başladığında servisi hedeflenen gecikmede tutmak, kapasite hesabı ve kapıdaki kabul kararıyla olur.

Kaynaklar

  • PagedAttention / vLLM — Kwon vd., SOSP 2023. Bellek israfı ve 2–4 kat throughput karşılaştırmasının kaynağı. Karşılaştırma FasterTransformer ve Orca’yadır.
  • vLLM optimization — chunked prefill, prefix caching ve V1 preemption varsayılanları. Sürümler arasında değişir.
  • GQA — Ainslie vd. KV head sayısındaki 8 kat farkın kaynağı.
  • Exponential Backoff and Jitter — AWS Architecture Blog.
  • Google SRE — retry bütçeleri, deadline yayılımı ve kaskad arıza.
  • DistServe ve Splitwise — prefill ile decode fazlarının ayrıştırılması.
  • S-LoRA ve Punica — tek baz model üzerinde çok sayıda LoRA adaptörü.
  • Fairness in Serving Large Language Models — Virtual Token Counter.
  • Netflix concurrency-limits ve CoDel — adaptif eşzamanlılık ve kuyruk gecikmesi.