Model Serving Altyapısı
RehberModel Serving01 · LLM Serving

vLLM &
TensorRT-LLM.

PagedAttention ile KV cache fragmentasyonunu ortadan kaldır. Continuous batching, speculative decoding, tensor parallelism ve TensorRT-LLM ile NVIDIA optimize edilmiş yüksek verimli LLM serving.

00 LLM Serving Zorlukları

Üretimde LLM çalıştırmak neden bu kadar karmaşık? Bellek yönetimi ve batching verimsizliğinin anatomisi.

Bir LLM'i tek kullanıcı için çalıştırmak zaten zordur; ama aynı modeli yüzlerce eş zamanlı kullanıcıya servis etmek bambaşka bir mühendislik problemidir. İki temel engel öne çıkar: KV cache bellek parçalanması ve batching verimsizliği.

KV Cache Memory Sorunu

Her transformer katmanındaki attention mekanizması, daha önce üretilen token'ların Key ve Value matrislerini hafızada tutmak zorundadır — buna KV cache denir. Sequence uzadıkça bu cache büyür; farklı uzunluktaki sequence'lar için ayrı ayrı bellek bloğu tahsis edilmesi gerekir. Geleneksel sistemlerde bu bellek statik olarak önceden ayrılır; yüksek fragmentasyon ve düşük kullanım oranı kaçınılmazdır.

Problem 40 GB GPU, 7B model ağırlıkları → ~14 GB
Kalan   26 GB KV cache için kullanılabilir
Statik  Her sequence için max_len × layer × head × d_head ayrılır
Sonuç   Çoğu blok boş, GPU dolmuş görünür — utilization %30-40

Batching Verimsizliği

Static batching'de tüm sequence'ların aynı anda tamamlanması beklenir. Kısa bir yanıt veren kullanıcı, en uzun sequence bitene kadar bekler. GPU o süre boyunca kısmen boş çalışır — padding waste olarak adlandırılan bu kayıp, throughput'u ciddi ölçüde düşürür.

SorunNedeniEtkisivLLM Çözümü
KV FragmentasyonuStatik bellek tahsisi%60-70 VRAM atılPagedAttention
Batching GecikmeStatic batch senkronizasyonuThroughput düşükContinuous batching
TTFT YüksekSıra beklemeKötü UXPreemption + prioritization
Memory OOMTahmin edilemeyen sequence uzunluğuSunucu çökmesiDinamik block allocation
İPUCU

vLLM, orijinal Orca paper'ındaki continuous batching fikrini PagedAttention ile birleştirerek 24× daha yüksek throughput elde eder. Bu sayının arka planını anlamak için önce bu iki kavramı kavramak şarttır.

01 PagedAttention Mimarisi

Sanal bellek ilkesini KV cache'e uyarla — virtual KV cache ve block table ile fragmentasyonu sıfıra indir.

PagedAttention, işletim sistemlerindeki virtual memory paging ilkesini KV cache yönetimine uygular. Fiziksel GPU belleği sabit boyutlu bloklara (block size = 16 token) bölünür. Her sequence için bir block table tutulur; bu tablo sanal blok numaralarını fiziksel bloklara eşler.

Fiziksel GPU VRAM → N adet eşit boyutlu blok (ör. 16 token/blok)
Sanal   Her sequence kendi sanal blok listesine sahip
Tablo   block_table[seq_id] = [phys_blk_3, phys_blk_7, phys_blk_12, ...]
Tahsis  Yeni token üretildikçe boş fiziksel blok talep edilir
Serbest Sequence tamamlandığında bloklar hemen geri verilir

Copy-on-Write ile Beam Search

Beam search veya parallel sampling gibi senaryolarda aynı prefix farklı sequence'lar arasında paylaşılabilir. PagedAttention, aynı fiziksel bloğa birden fazla sequence'ın referans vermesine olanak tanır. Bir sequence o bloktaki içeriği değiştirmeye kalkarsa copy-on-write tetiklenir, sadece o an değiştirilen blok kopyalanır.

ÖzellikGelenekselPagedAttention
Bellek tahsisimax_seq_len önceden ayrılırToken üretildikçe dinamik
FragmentasyonYüksek (%60+)Neredeyse sıfır (<4%)
Prefix paylaşımıYokCopy-on-write ile var
Batch boyutuBellek sınırlı (küçük)Büyük batch mümkün
OOM riskiTahmin edilemezBelirleyici, kontrollü
TEKNİK NOT

Block size seçimi kritiktir. Küçük bloklar (ör. 8 token) daha fazla esneklik sağlar ama block table overhead'i artar. Büyük bloklar (ör. 32 token) overhead'i düşürür ama son blokta iç fragmentasyon oluşur. vLLM varsayılanı 16'dır.

02 Continuous Batching

Static batching'in GPU boşa harcama sorununu her iteration adımında çözüme kavuşturan yaklaşım.

Static batching'de bir batch oluşturulur, tüm sequence'lar tamamlanana kadar beklenir, sonra yeni batch oluşturulur. Bu yaklaşımda bir sequence erken biterse GPU kapasitesi atıl kalır.

Continuous batching'de her token üretim adımında (iteration level) scheduler devreye girer: tamamlanan sequence'lar batch'ten çıkarılır, bekleme kuyruğundaki yeni sequence'lar eklenir. GPU hiçbir zaman boş kalmaz.

Iter 1 Batch = [A(10), B(50), C(30)] token kaldı
Iter 2 A tamamlandı → Batch = [B(49), C(29), D(yeni)]
Iter 3 Batch = [B(48), C(28), D(devam)]
...
Sonuç GPU utilization %90+ (static'te %40-60)

Preemption ve Priority

Bellek yetersiz kaldığında vLLM preemption uygular: düşük öncelikli sequence'ların KV cache'i diske (swap) veya başka GPU'ya taşınır. Bu mekanizma OOM yerine kontrollü performans düşüşü sağlar.

max_num_seqs Aynı anda işlenecek maksimum sequence sayısı (varsayılan: 256)
max_num_batched_tokens Bir iterasyonda işlenecek maksimum token sayısı
swap_space CPU RAM'de KV cache swap için ayrılan alan (GB)
block_size PagedAttention blok boyutu (token sayısı, varsayılan: 16)

03 vLLM Kurulumu ve Temel Kullanım

vLLM'i CUDA ortamına kur, ilk modeli yükle ve offline/online inference çalıştır.

bash
# CUDA 12.1+ gerektirir
pip install vllm

# Belirli CUDA versiyonu için
pip install vllm --extra-index-url https://download.pytorch.org/whl/cu121

# Docker ile (önerilen production yöntemi)
docker pull vllm/vllm-openai:latest

Offline Inference (LLM sınıfı)

python
from vllm import LLM, SamplingParams

# Modeli yükle
llm = LLM(
    model="meta-llama/Meta-Llama-3-8B-Instruct",
    tensor_parallel_size=1,   # tek GPU
    gpu_memory_utilization=0.90,
    max_model_len=4096,
    dtype="bfloat16",
)

# Sampling parametreleri
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.95,
    max_tokens=512,
    stop=["<|eot_id|>"],
)

# Batch inference — tüm prompts tek seferde işlenir
prompts = [
    "Transformer mimarisini açıkla:",
    "Python'da async/await nedir?",
    "Docker container neden kullanılır?",
]

outputs = llm.generate(prompts, sampling_params)

for output in outputs:
    prompt = output.prompt
    generated_text = output.outputs[0].text
    print(f"Prompt: {prompt!r}")
    print(f"Output: {generated_text!r}")
    print("-" * 60)

Chat Template ile Kullanım

python
from vllm import LLM, SamplingParams

llm = LLM(model="meta-llama/Meta-Llama-3-8B-Instruct")

# Chat formatında mesajlar
conversation = [
    {"role": "system", "content": "Sen yardımcı bir asistansın."},
    {"role": "user",   "content": "vLLM neden bu kadar hızlı?"},
]

# Tokenizer'ın chat template'ini kullan
tokenizer = llm.get_tokenizer()
prompt = tokenizer.apply_chat_template(
    conversation,
    tokenize=False,
    add_generation_prompt=True,
)

outputs = llm.generate([prompt], SamplingParams(max_tokens=256))
print(outputs[0].outputs[0].text)

04 vLLM OpenAI-Compatible API

vLLM'i OpenAI API formatında bir HTTP sunucusu olarak çalıştır — mevcut OpenAI istemcileri sıfır değişiklikle çalışır.

bash
# Sunucuyu başlat
python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Meta-Llama-3-8B-Instruct \
  --host 0.0.0.0 \
  --port 8000 \
  --tensor-parallel-size 1 \
  --gpu-memory-utilization 0.90 \
  --max-model-len 8192 \
  --served-model-name llama-3-8b
python — OpenAI istemcisi
from openai import OpenAI

# vLLM sunucusuna yönlendir
client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="token-abc123",  # herhangi bir değer
)

# Chat completion
response = client.chat.completions.create(
    model="llama-3-8b",
    messages=[
        {"role": "system", "content": "Kısa ve net yanıt ver."},
        {"role": "user",   "content": "PagedAttention nedir?"},
    ],
    max_tokens=256,
    temperature=0.7,
    stream=False,
)
print(response.choices[0].message.content)
python — Streaming
import sys

stream = client.chat.completions.create(
    model="llama-3-8b",
    messages=[{"role": "user", "content": "Bana bir hikaye anlat."}],
    max_tokens=512,
    stream=True,
)

for chunk in stream:
    delta = chunk.choices[0].delta
    if delta.content:
        sys.stdout.write(delta.content)
        sys.stdout.flush()
print()  # newline

Kullanılabilir Endpoint'ler

EndpointMetotAçıklama
/v1/chat/completionsPOSTChat formatı (GPT-4 uyumlu)
/v1/completionsPOSTLegacy text completion
/v1/embeddingsPOSTEmbedding modelleri için
/v1/modelsGETYüklü modelleri listele
/healthGETSunucu sağlık kontrolü
/metricsGETPrometheus metrikleri

05 Tensor Parallelism

Büyük modelleri birden fazla GPU'ya yay — tensor parallelism ile 70B modelleri çalıştır.

Tensor parallelism, model matrislerini farklı GPU'lara böler. Attention head'leri ve MLP katmanları sütun/satır bazında parçalanır. Her GPU hesabın bir bölümünü yapar, sonuçlar all-reduce ile birleştirilir.

bash — 4 GPU ile 70B model
# 4 GPU için tensor parallelism
python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Meta-Llama-3-70B-Instruct \
  --tensor-parallel-size 4 \
  --pipeline-parallel-size 1 \
  --gpu-memory-utilization 0.95 \
  --max-model-len 8192 \
  --dtype bfloat16

# 8 GPU: tensor × pipeline parallelism
python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Meta-Llama-3-70B-Instruct \
  --tensor-parallel-size 4 \
  --pipeline-parallel-size 2 \
  --max-model-len 4096
python — programatik
from vllm import LLM

# 4 GPU'ya tensor parallelism
llm = LLM(
    model="meta-llama/Meta-Llama-3-70B-Instruct",
    tensor_parallel_size=4,
    gpu_memory_utilization=0.95,
    dtype="bfloat16",
    enforce_eager=False,   # CUDA graph kullan (daha hızlı)
)

# GPU kullanımını kontrol et
import torch
for i in range(torch.cuda.device_count()):
    mem = torch.cuda.memory_allocated(i) / 1024**3
    print(f"GPU {i}: {mem:.1f} GB kullanımda")
--tensor-parallel-size Tensor parallelism derecesi (GPU sayısı, 2'nin kuvveti olmalı)
--pipeline-parallel-size Pipeline parallelism derecesi (katman bölümlemesi)
--distributed-executor-backend ray veya mp (multiprocessing) — varsayılan: mp

06 Speculative Decoding

Küçük taslak model ile büyük hedef modelin birlikte çalışması — latency'yi 2-3x düşür.

Speculative decoding'de küçük (draft) bir model hızlıca birkaç token üretir; büyük (target) model bu token'ları paralel olarak doğrular. Hedef model kabul ederse token'lar ücretsiz gelmiş olur, reddettiğinde kendi token'ını üretir.

bash — Eagle speculative decoding
# Eagle draft model ile speculative decoding
python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Meta-Llama-3-8B-Instruct \
  --speculative-model eagle \
  --num-speculative-tokens 5 \
  --speculative-draft-tensor-parallel-size 1

# Küçük model ile (harici draft)
python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Meta-Llama-3-70B-Instruct \
  --speculative-model meta-llama/Meta-Llama-3-8B-Instruct \
  --num-speculative-tokens 5 \
  --tensor-parallel-size 4
python — Medusa heads
from vllm import LLM, SamplingParams

# Medusa: hedef modele eklenen ek head'ler
llm = LLM(
    model="FasterDecoding/medusa-vicuna-7b-v1.3",
    speculative_model="[medusa]",
    num_speculative_tokens=3,
    tensor_parallel_size=1,
)

outputs = llm.generate(
    ["Python'da decorator nasıl kullanılır?"],
    SamplingParams(max_tokens=256, temperature=0.0),
)
print(outputs[0].outputs[0].text)
YöntemMekanizmaSpeedupDoğruluk Kaybı
EagleAutoregressive draft head2-3×Yok (lossless)
MedusaParalel speculative heads1.5-2×Yok (lossless)
Draft modelAyrı küçük model2-4×Yok (lossless)
n-gramPrompt'tan n-gram tahmin1.3-2×Yok (lossless)

07 TensorRT-LLM

NVIDIA'nın optimize edilmiş LLM inference kütüphanesi — kernel fusion, in-flight batching ve quantization.

TensorRT-LLM (TRT-LLM), NVIDIA'nın CUDA çekirdeklerini model mimarisine özgü fuse eden, in-flight batching (continuous batching benzeri), multi-GPU desteği ve agresif quantization sunan bir inference framework'üdür. vLLM'den farklı olarak derleme adımı gerektirir; ancak NVIDIA donanımında genellikle daha yüksek throughput sunar.

bash — TRT-LLM kurulumu
# Docker önerilen yöntem
docker pull nvcr.io/nvidia/tritonserver:24.01-trtllm-python-py3

# pip install (CUDA 12.x)
pip install tensorrt-llm -U --extra-index-url https://pypi.nvidia.com

# Model derleme (Llama-3 örneği)
python examples/llama/convert_checkpoint.py \
  --model_dir /models/llama-3-8b-hf \
  --output_dir /models/llama-3-8b-trt-ckpt \
  --dtype bfloat16 \
  --tp_size 1

trtllm-build \
  --checkpoint_dir /models/llama-3-8b-trt-ckpt \
  --output_dir /models/llama-3-8b-trt-engine \
  --gemm_plugin bfloat16 \
  --max_batch_size 32 \
  --max_input_len 2048 \
  --max_seq_len 4096
python — TRT-LLM inference
import tensorrt_llm
from tensorrt_llm.runtime import ModelRunnerCpp

# Engine'i yükle
runner = ModelRunnerCpp.from_dir(
    engine_dir="/models/llama-3-8b-trt-engine",
    rank=tensorrt_llm.mpi_rank(),
)

# Batch inference
import torch
input_ids = [
    torch.tensor([1, 29871, 13, 450], dtype=torch.int32),
    torch.tensor([1, 29871, 13], dtype=torch.int32),
]

outputs = runner.generate(
    batch_input_ids=input_ids,
    max_new_tokens=256,
    temperature=0.7,
    end_id=2,
    pad_id=2,
)
print(outputs)
vLLM vs TRT-LLM

vLLM kurulumu daha kolay ve framework-agnostic'tir. TRT-LLM, NVIDIA GPU'larda daha yüksek throughput sağlar ama derleme süreci ve NVIDIA ekosistemi gerektirir. Production'da çoğu ekip vLLM ile başlar, ardından gerekirse TRT-LLM'e geçiş yapar.

08 Quantization: AWQ, GPTQ, INT4/INT8

Model ağırlıklarını sıkıştır — VRAM gereksinimini yarıya indir, throughput'u artır, kaliteyi koru.

bash — AWQ quantized model
# AWQ (Activation-Aware Weight Quantization) — önerilen INT4
pip install autoawq

# Hugging Face'den hazır AWQ model
python -m vllm.entrypoints.openai.api_server \
  --model TheBloke/Llama-3-8B-Instruct-AWQ \
  --quantization awq \
  --dtype half

# GPTQ model
python -m vllm.entrypoints.openai.api_server \
  --model TheBloke/Llama-3-8B-Instruct-GPTQ \
  --quantization gptq \
  --dtype half
python — AWQ modeli kendin quantize et
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_path = "meta-llama/Meta-Llama-3-8B-Instruct"
quant_path = "llama-3-8b-awq"

# Quantization konfigürasyonu
quant_config = {
    "zero_point": True,
    "q_group_size": 128,
    "w_bit": 4,           # INT4
    "version": "GEMM",
}

# Model ve tokenizer yükle
model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path)

# Kalibrasyon verisi ile quantize et
model.quantize(tokenizer, quant_config=quant_config)
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)
print(f"AWQ model kaydedildi: {quant_path}")
YöntemBitBellek (7B)Kalite KaybıHız
FP16 (baseline)1614 GB
GPTQ INT443.5 GBHafif2-3×
AWQ INT443.5 GBÇok hafif2-3×
INT8 (W8A8)87 GBMinimal1.5-2×
FP8 (Hopper+)87 GBNeredeyse yok

09 Throughput Benchmark

tokens/sec, TTFT ve latency percentile — LLM serving performansını doğru ölç.

bash — vLLM benchmark suite
# vLLM'in dahili benchmark aracı
python benchmarks/benchmark_throughput.py \
  --model meta-llama/Meta-Llama-3-8B-Instruct \
  --backend vllm \
  --num-prompts 1000 \
  --input-len 512 \
  --output-len 128 \
  --tensor-parallel-size 1

# Sonraki çıktıya örnek:
# Throughput: 1423.2 tokens/s
# Request Throughput: 11.1 req/s
python — özel benchmark istemcisi
import asyncio
import time
import aiohttp
import statistics

async def send_request(session, url, prompt, req_id):
    start = time.perf_counter()
    first_token_time = None
    tokens = 0

    async with session.post(url, json={
        "model": "llama-3-8b",
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": 256,
        "stream": True,
    }) as resp:
        async for line in resp.content:
            line = line.decode().strip()
            if line.startswith("data: ") and line != "data: [DONE]":
                if first_token_time is None:
                    first_token_time = time.perf_counter()
                tokens += 1

    total_time = time.perf_counter() - start
    ttft = first_token_time - start if first_token_time else total_time
    return {
        "req_id": req_id,
        "ttft_ms": ttft * 1000,
        "total_ms": total_time * 1000,
        "tokens": tokens,
        "tps": tokens / total_time,
    }

async def benchmark(concurrency=10, total_requests=100):
    url = "http://localhost:8000/v1/chat/completions"
    prompt = "Makine öğrenmesinde overfitting nedir ve nasıl önlenir?"

    async with aiohttp.ClientSession() as session:
        semaphore = asyncio.Semaphore(concurrency)

        async def bounded_req(i):
            async with semaphore:
                return await send_request(session, url, prompt, i)

        start = time.perf_counter()
        results = await asyncio.gather(*[bounded_req(i) for i in range(total_requests)])
        elapsed = time.perf_counter() - start

    ttfts = [r["ttft_ms"] for r in results]
    tpss  = [r["tps"] for r in results]
    print(f"Toplam süre:     {elapsed:.1f}s")
    print(f"Request/s:       {total_requests/elapsed:.1f}")
    print(f"TTFT p50:        {statistics.median(ttfts):.0f} ms")
    print(f"TTFT p95:        {sorted(ttfts)[int(0.95*len(ttfts))]:.0f} ms")
    print(f"Throughput p50:  {statistics.median(tpss):.0f} tok/s")

asyncio.run(benchmark(concurrency=20, total_requests=200))
MetrikAçıklamaİyi Değer (8B, A100)
TTFT (p50)İlk token süresi, medyan< 100 ms
TTFT (p99)İlk token süresi, %99 percentile< 500 ms
ThroughputSaniyede üretilen token> 1000 tok/s
Request/sSaniyede tamamlanan istek> 10 req/s
GPU UtilGPU kullanım oranı> 85%

10 Pratik: Llama-3 vLLM ile Deploy

Llama-3-8B-Instruct'ı vLLM ile production'a al, throughput benchmark yap ve streaming output entegre et.

bash — tam deploy scripti
# 1. Model indir (HuggingFace Hub)
huggingface-cli login
huggingface-cli download meta-llama/Meta-Llama-3-8B-Instruct \
  --local-dir /models/llama-3-8b

# 2. vLLM sunucusu başlat
python -m vllm.entrypoints.openai.api_server \
  --model /models/llama-3-8b \
  --served-model-name llama-3-8b \
  --host 0.0.0.0 \
  --port 8000 \
  --gpu-memory-utilization 0.90 \
  --max-model-len 8192 \
  --enable-prefix-caching \
  --disable-log-requests \
  --tensor-parallel-size 1 &

# 3. Sağlık kontrolü
curl http://localhost:8000/health

# 4. Hızlı throughput testi
python benchmarks/benchmark_throughput.py \
  --model llama-3-8b \
  --backend openai \
  --endpoint http://localhost:8000/v1 \
  --num-prompts 200 \
  --request-rate 10
python — streaming FastAPI proxy
import asyncio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI
from pydantic import BaseModel

app = FastAPI(title="LLM Proxy")
client = AsyncOpenAI(base_url="http://localhost:8000/v1", api_key="x")

class ChatRequest(BaseModel):
    message: str
    system: str = "Sen yardımcı bir asistansın."
    max_tokens: int = 512

@app.post("/chat/stream")
async def chat_stream(req: ChatRequest):
    async def event_generator():
        stream = await client.chat.completions.create(
            model="llama-3-8b",
            messages=[
                {"role": "system", "content": req.system},
                {"role": "user",   "content": req.message},
            ],
            max_tokens=req.max_tokens,
            stream=True,
        )
        async for chunk in stream:
            delta = chunk.choices[0].delta
            if delta.content:
                yield f"data: {delta.content}\n\n"
        yield "data: [DONE]\n\n"

    return StreamingResponse(event_generator(), media_type="text/event-stream")

# Çalıştır: uvicorn serve:app --host 0.0.0.0 --port 8080
SONUÇ

Llama-3-8B-Instruct + vLLM + tek A100 80GB GPU kombinasyonu tipik olarak 1200-1800 token/s throughput, <80ms TTFT (p50) sağlar. AWQ INT4 quantization ile bu değerler 2000+ token/s'ye ulaşabilir, TTFT ise benzer kalır.