
GPU kiralamadan önce elimdeki donanıma baktım: WSL2 içinde Oracle Linux, 4 GB VRAM’li bir RTX A2000 Laptop. “Bir akşamda kurarım” dedim. Kurdum kurmasına — ama araya bir başarısız kurulum ve beş başarısız çalıştırma girdi; altı ayrı neden, her biri bir öncekinin arkasından çıktı. Bu yazıda hem vLLM’i küçük kartta ayağa kaldırmayı hem de o altı hatanın her birinden çıkan dersi anlatacağım.
1. Neden Önce Laptop?
Çünkü vLLM’i öğrenmenin maliyetli kısmı GPU değil, zamanınız. Kiralık bir H100’ün başında hata mesajı debug etmek, sayaç işlerken park yerinde el freni çalışmak gibi. Bu yazıda çözeceğimiz altı sorunun — derleyici eksiği, bellek kapısı, header dosyası, sürüm uyuşmazlığı — hiçbiri güçlü GPU istemiyor.
Küçük kart bence daha iyi bir öğretmen. 80 GB’lık kartta bazı hatalar hemen karşına çıkmaz ve bunları öğrenemezsiniz. 4 GB’lık kartta vLLM’in bellek modeliyle yüzleşmek zorunda kalırsınız — ben kaldım, ve o yüzleşmenin notları aşağıda.
2. Sahne: WSL2’de GPU Kimin Malı?
WSL2’de NVIDIA driver’ı Linux’a kurmazsınız. Driver Windows tarafında yaşar; WSL onun Linux yüzünü (libcuda.so ve arkadaşlarını) /usr/lib/wsl/lib altından dağıtımınıza mount eder. GPU passthrough, mevcut Windows driver’ıyla kendiliğinden çalışır ve inference native Linux hızının %90-100’ünde koşar[3]. Oracle Linux terminalimde nvidia-smi ilk günden kartı görüyordu — driver kurulumu diye bir adım hiç olmadı.

Kartım hakkında bir not: vLLM, compute capability 7.0 altındaki kartlarla (GTX 10-serisi ve öncesi) çalışmıyor — topluluktan biri GTX 1080’de “hardware too old” duvarına toslamış[5]. A2000, Ampere mimarisiyle (sm_86) bu sınırın rahat üstünde. Sizin kartınız RTX 20-serisi ve üstüyse aynı akış geçerli.
3. Kurulum: Broşürdeki ve Gerçekteki
Dokümanlardaki kurulum iki satır: pip install vllm, vllm serve model-adı. Benim makinemde gerçek reçete şu oldu:
# Sistem paketleri — her birinin sebebi birazdan
sudo dnf install -y gcc gcc-c++ make rust cargo python3.12-devel
# Bağımlılıklar (uv ile; birkaç GB torch+CUDA wheel iner,
# üstüne bir de kaynaktan derleme var — kahvenizi alın)
uv sync
# Sunucu (defaults'ları birazdan anlatacağım serve.sh üzerinden)
./serve.sh Qwen/Qwen3-0.6B
O ilk satırdaki paket listesi gökten inmedi; her paketi bir hata mesajı söyletti. En öğreticisi şu: neden bir Python paketi kurarken Rust derleyicisi gerekiyor? Cevap, RHEL ailesinin bilinen bir gerçeğiyle wheel ekosisteminin kesişiminde. Oracle Linux 8.10’un glibc’si 2.28 — kurumsal dağıtımların “eskiyi uzun destekle” felsefesinin doğal sonucu. vLLM’in kendi wheel’i manylinux_2_28 hedeflediği için sorunsuz; ama bağımlılıklarından llguidance‘ın sürüm aralığındaki her yayın yalnızca manylinux_2_31 wheel’i (glibc ≥ 2.31 ister) sunuyor. Benim glibc’me uyan wheel yok; uv sessizce sdist’e düşüyor; sdist bir Rust/PyO3 extension’ı ve derlenmek istiyor. Makinede derleyici de yok — ilk hata: error: linker `cc` not found. EL8 tabanlı herhangi bir dağıtımda (Oracle, Rocky, Alma, RHEL) aynı duvar sizi de bekliyor.
python3.12-devel‘in hikayesi ise daha sinsi, çünkü kurulumda değil ilk istekte patlıyor: Triton, CUDA yardımcı C kodunu çalışma anında JIT-derliyor ve Python.h arıyor. venv’iniz sistem Python’u üzerine kuruluysa header’lar ayrı pakette. Bu ikiliyi baştan kurmazsanız elinizde “açılan ama ilk istekte ölen” bir sunucu kalıyor — ki bu ayrım (birazdan göreceğiz) bu yazının ana fikri.
4. Altı Hata, Altı Ders
Şimdi yazının kalbi. Kurulum bitti, vllm serve dedim ve beş kez üst üste farklı bir sebeple çöktü. Her düzeltme motoru bir sonraki hata noktasına ilerletti. Soğan gibi — her katmanın altından yenisi. Beşi de engine initialization sırasında, vLLM’in kernel derleyip KV cache boyutlandırdığı aşamada çıktı. İşte tam liste, her hatayı teşhis ettiren ipucuyla:
| # | Belirti | Kök neden | Çözüm |
|---|---|---|---|
| 0 | error: linker `cc` not found (kurulumda) | glibc 2.28’e uyan llguidance wheel’i yok → kaynaktan Rust derlemesi → makinede derleyici yok | dnf install gcc gcc-c++ make rust cargo |
| 1 | RuntimeError: UVA is not available | vLLM, WSL2’de pinned memory’yi varsayılan kapalı tutuyor | VLLM_WSL2_ENABLE_PIN_MEMORY=1 |
| 2 | Free memory on device (3.21/4.0 GiB) ... less than desired (3.4 GiB) | Windows, WSL altında VRAM’in ~0.8 GB’ını masaüstüne ayırıyor | --gpu-memory-utilization 0.75 |
| 3 | InductorError: ... Python.h: No such file or directory | Triton JIT’in header ihtiyacı; venv sistem python3.12’de | dnf install python3.12-devel |
| 4 | Could not find nvcc and default cuda_home='/usr/local/cuda' doesn't exist | nvcc aslında eksik değil — torch cu13 wheel’leri komple bir toolkit gömüyor, ama kimse ona işaret etmiyor | CUDA_HOME=.venv/.../nvidia/cu13 |
| 5 | #error "CUDA compiler and CUDA toolkit headers are incompatible" | Gömülü toolkit’in kendi içi kaymış: nvcc 13.3 vs cudart header 13.0 — NVIDIA wheel’lerinde packaging hatası, lokal bir yanlış ayar değil | VLLM_USE_FLASHINFER_SAMPLER=0 |
#1 — Kapalı tutulan kapı. “UVA is not available” hatasını görünce ilk düşünce “WSL bunu desteklemiyor demek ki” oluyor. Oysa kapı vLLM’in kendi kodunda: WSL2 tespit edilince pin_memory varsayılan olarak kapatılıyor. Halbuki 4.19.121 ve üstü WSL2 kernel’lerinde pinned memory çalışıyor — benim kernel 6.6.87, sınırın çok üstünde. Environment variable ile kapıyı açmak yetiyor; ben yine de flag’i körlemesine çevirmek yerine pin_memory=True allocation ve UVA device view ile yeteneğin gerçekten var olduğunu doğruladım. Ldığım ders: “platform desteklemiyor” ile “araç temkinli davranıp kapatmış” farklı şeyler.
#2 — Ölçülmüş gerçek: 4 GB aslında 3.2 GB. Kartım 4 GB ama nvidia-smi boşta bile ~0.8 GB’ın dolu olduğunu söylüyor — Windows masaüstü aynı karttan besleniyor. Sonuç: --gpu-memory-utilization için tavan pratik olarak 0.75; 0.85 daha açılışta patladı. İnternetteki rehberler bu flag için 0.8-0.9 önerir; laptop + WSL kombinasyonunda o rakamlar biraz hayal. Kendi kartınızın gerçek boş VRAM’ini görmeden bu flag’e değer vermeyin.
#4 ve #5 — Hata mesajının dediği ve gerçekte olan. Bu ikili, debug sanatının ders kitabı örneği. Mesaj “nvcc yok” diyor; halbuki nvcc var — torch’un cu13 wheel’leri venv’in içine bin/, include/, lib/, nvvm/ ile komple bir CUDA toolkit gömüyor, sadece hiçbir environment variable ona işaret etmiyor. CUDA_HOME‘u oraya çevirince bu hata çözüldü… ve altından #5 çıktı: gömülü toolkit’in kendi içi tutarsız. Venv’deki her cuda_runtime_api.h‘ı nvcc --version ile karşılaştırınca kanıt netti: header’lar 13.0 derken nvcc 13.3, ve CCCL birebir eşleşme talep ediyor. Yani NVIDIA’nın wheel paketlemesinde bir hata — benim makinemde düzeltebileceğim bir şey değil. Çözüm pragmatik: flashinfer sampler’ı kapatmak. Bu bir optimizasyon, zorunluluk değil; vLLM kendi PyTorch sampler’ına düşüyor ve 0.6B’lik modelde fark hissedilmiyor. Eşleşen bir CUDA 13.0 toolkit kurmak ise, bu donanımın ihtiyaç duymadığı bir kernel’i geri açmak için birkaç GB’lık bir dolambaç olurdu.
Zehirlenmiş cache uyarısı: #5 ile boğuşurken bir düzeltme deneyip yine aynı hatayı alırsanız, düzeltmeniz işe yaramamış olmayabilir — başarısız build’in kalıntısı sonucu maskeliyor olabilir. Önce
rm -rf ~/.cache/flashinfer/*/86/cached_ops/samplingile temizleyin, sonra yargılayın. Genel kural olarak da: #3-#5 derleme aşaması hataları olduğundan, sıcak bir~/.cache/vllm/torch_compile_cachehangi hatanın hangi koşuda yüzeye çıkacağını değiştiriyor — “düzeldi mi?” sorusunun güvenilir tek cevabı sıfırdan (soğuk cache ile) bir koşu.
Üç environment variable’lık çözüm setini bir .env dosyasında topladım (her satırın üstünde önlediği hatanın yorumu var) ve serve.sh diye ince bir script bunları uygulayıp, siz kendiniz vermediyseniz --max-model-len 2048 --gpu-memory-utilization 0.75 varsayılanlarını ekliyor. Script çalıştırmadan önce nihai komutu ekrana basıyor — sihirli kutu yok, ne koştuğunuzu her zaman görüyorsunuz.
5. “Çalışıyor”un Tanımı: Smoke Test
Altı hatanın belki de en değerli mirası şu fark: “process ayakta” ile “model token üretiyor” farklı iddialar — ve yalnızca ikincisinin değeri var. #3’ü hatırlayın: sunucu açılıyor, /health yeşil, ama ilk gerçek istekte Triton derlemesi patlıyor. Elle curl atıp “açıldı, tamamdır” deseydim bu hatayı demo sırasında yakalayacaktım. O yüzden bir smoke_test.sh yazdım; altı hatanın dördünü elle başlatma değil, bu script yakaladı.
Script sırayla şunları doğruluyor: /health, /v1/models, /v1/completions, /v1/chat/completions, SSE streaming ve usage muhasebesi. İçinde iki tasarım kararı var ki paylaşmaya değer:
Tutarlılık iddiası: Completions testi çıktının boş olmamasına değil, tutarlı olmasına bakıyor — “The capital of France is” prompt’una dönen metin “Paris” içeriyor mu? Çünkü bozuk ağırlıklar da makul uzunlukta çöp döndürür; “bir şeyler geldi” testi onları geçirir, “doğru şey geldi” testi geçirmez.
Kök nedeni göster, sarmalayıcıyı değil: Sunucu erken ölürse script log’dan engine’in asıl hatasını söküp gösteriyor. Bunun sebebi vLLM’in hata raporlama yapısı: APIServer‘ın size gösterdiği traceback ~30 satırlık bir “Engine core initialization failed. See root cause above” sarmalayıcısı — asıl hata onun birkaç yüz satır yukarısında. “See root cause above” diyen log’da yukarıya bakmayı otomatikleştirmek, altı hatalık bir maratonda saatler kazandırıyor.
6. Model Seçimi ve Qwen3’ün Düşünme Tuzağı
Donanım gerçeğim net: 4 GB etiketli, fiilen 3.2 GB boş kartta gerçekçi aralık 0.5B-1.5B parametre; kısa context’te sığan en büyük model Qwen3-1.7B. 7B, KV cache hesaba katılınca quantize edilmiş halde bile yüklenmiyor. Bu, genel rehberlerin 4 GB için çizdiği 0.5B-3B bandının[1] WSL vergisi düşülmüş hali. Ben denemelerimi Qwen3-0.6B ile yaptım ve burada beni bekleyen, bellekle ilgisi olmayan bir tuzak vardı.
Qwen3 ailesinde thinking (düşünme) modu varsayılan açık[11] ve model cevaba başlamadan önce <think> bloğunda kabaca 150+ token’lık akıl yürütme üretiyor. Küçük bir max_tokens verirseniz bütçenin tamamını düşünme yiyor ve elinize "finish_reason":"length" ile hiç cevap içermeyen bir yanıt geçiyor. İlk gördüğümde sunucum bozuk sanmıştım — değilmiş, model resmen “düşünürken süre bitti” diyormuş. Üç çıkış yolu:
# 1) İstek bazında düşünmeyi kapat — 50 token'a sığar (ama uyarıyı okuyun):
"chat_template_kwargs": {"enable_thinking": false}
# 2) Alan tanı: "max_tokens": 600 verdim, 165 token'da tamamladı.
# 3) Sunucu tarafında ayrıştır — vllm serve'e şu flag'i ekleyin:
# --reasoning-parser qwen3
# içeriği message.reasoning alanına, cevap message.content'e
# düşer; istemcide string ameliyatı yapmazsınız.
Şimdi 1. yolun uyarısı — çünkü bu tuzağın bir de ikinci katı var. Modele “Where is the capital of Japan?” diye sordum: düşünme açıkken <think> bloğunda kendi kendine “Tokyo… başka aday var mı… yok” diye sağlamasını yapıp doğru cevabı verdi. Aynı soruyu enable_thinking: false ile sorunca cevap: “Osaka City.” Yani bu boyuttaki model düşünme token’larına süs olsun diye değil, doğruluk için ihtiyaç duyuyor — kapatmak token bütçesinden tasarruf ettirirken kaliteden çalıyor. Ders: thinking’i kapatmak bir optimizasyon değil, bir takas; küçük modellerde takasın bedeli büyük. Token bütçesi dertse doğru çözüm 1 değil, 2 veya 3.
Bir de beklenti kalibrasyonu: 0.6B’lik model, küçük olduğunu her fırsatta hatırlatıyor. “Merhaba, ayakta mısın?” diye sordum; reasoning’inde soruyu yanlış çevirdi (“sit down?”) ve cevap yerine selamımı bana geri yankıladı. Bu bir konfigürasyon sorunu değil, model kapasitesi — mekaniği öğrenmek için harika, sohbet arkadaşı olarak umutsuz. Zaten serinin varlık sebebi de bu: mekaniği burada bedavaya öğrenip zekayı kiralık kartta aramak.
7. Sunucu Şart Değil: Python API ile Offline Inference
Buraya kadar vLLM hep sunucu olarak çalıştı — biz elle tek istek bile atmadık gerçi: sunucuyu serve.sh açtı, endpoint’leri de bizim yerimize smoke test yokladı. Ama vLLM’in ikinci bir kullanım şekli daha var ve toplu işler (batch) için aslında daha doğal: offline inference — sunucu yok, port yok, curl yok; LLM sınıfını doğrudan Python’dan çağırıyorsunuz[13]. Bütün .env çözümlerimiz burada da aynen gerekiyor, o yüzden çalıştırma şekli uv run --env-file .env python main.py:
import os
from vllm import LLM, SamplingParams
MODEL = os.getenv("MODEL", "Qwen/Qwen3-0.6B")
llm = LLM(
model=MODEL,
gpu_memory_utilization=0.75, # oran TOPLAM VRAM'e göre: 4.0'ın 3.0 GiB'ı
max_model_len=4096, # Qwen3 varsayılanı 40K context ister —
# o boyda KV cache bu karta sığmaz
enforce_eager=True, # CUDA graph'ları atla: birkaç yüz MB kâr
)
params = SamplingParams(temperature=0.3, top_p=0.9, max_tokens=512)
outs = llm.chat([{"role": "user", "content": "Where is the capital of Japan?"}], params)
print(outs[0].outputs[0].text)
Buradaki üç seçim de bilinçli. max_model_len=4096: Qwen3’ün config’i 40K context ilan eder ve vLLM buna inanıp KV cache’i ona göre boyutlandırmaya kalkar — 4 GB’lık kartta daha başlamadan biter; context’i elle sınırlamak şart. enforce_eager=True: CUDA graph derlemesini atlar, biraz hızdan feragat edip VRAM kazanırsınız — log da bunu açıkça söylüyor: “Enforce eager set, disabling torch.compile and CUDAGraphs”. (Kazancın ölçüsü ne kadar? Birazdan iki koşuyu yan yana koyunca göreceğiz: tam 0.27 GiB.) Ve llm.chat() ile llm.generate() farkı: chat() mesajlara chat template uygular, model bir konuşma sırası cevaplar ve <|im_end|>‘de durur; generate() ise çıplak completion’dır — metni sadece devam ettirir. Instruct modele generate() ile soru sorup “neden saçmalıyor” demek, klasik başlangıç hatasıdır.
Bu modun bana asıl öğrettiği şeyse log okuma oldu. vLLM açılışta bellek muhasebesini kalem kalem döküyor; benim koşumdan gerçek satırlar:
Model loading took 1.12 GiB
Available KV cache memory: 1.41 GiB
GPU KV cache size: 13,184 tokens
Maximum concurrency for 4,096 tokens per request: 3.22x
Free memory on device (3.21/4.0 GiB) on startup.
Desired GPU memory utilization is (0.75, 3.0 GiB).
Actual usage is 1.39 GiB for consumed memory (weights + non-torch),
0.2 GiB for peak activation, and 0.0 GiB for CUDAGraph memory.
Replace gpu_memory_utilization config with `--kv-cache-memory=1356459520`
(1.26 GiB) to fit into requested memory ...
Yazının başından beri anlattığım bellek matematiğinin — etikette 4.0, gerçekte 3.21; ağırlıklar 1.12; kalan KV cache — vLLM’in kendi ağzından dökümü bu. Üstelik iki pratik cevher var içinde: “13,184 token KV cache, 4096’lık isteklerde 3.22x eşzamanlılık” satırı, kartınızın gerçek kapasite raporu — kaç isteği aynı anda taşıyabileceğinizi tahmin etmiyorsunuz, okuyorsunuz. Ve vLLM, oran yerine mutlak değer vermek isterseniz kullanmanız gereken --kv-cache-memory değerini byte’ına kadar kendisi öneriyor. “Log’da yukarı bak” dersinin tatlı simetrisi: hata ararken yukarı bakıyorduk, kapasite planlarken de yukarı bakıyoruz.
Küçük bir WSL selamı da logdan eksik olmamış: “WSL is detected and NVML is not compatible with fork” deyip multiprocessing yöntemini kendiliğinden spawn‘a çeviriyor. Platform tespitlerinin iyi yapılmış hali böyle görünüyor — #1’deki pin memory kapısının aksine, burada vLLM sorunu görüp sessizce doğru olanı yapıyor.
Ve log okumanın finali: elimde artık aynı makinede, aynı modelle, farklı ayarlarla alınmış iki açılış raporu var — offline koşu (enforce_eager=True, context 4096) ile serve.sh koşusu (CUDA graph’lar açık, context 2048). Yan yana koyunca bir kontrollü deneye dönüşüyorlar:
| Offline: eager, ctx 4096 | Serve: graph’lı, ctx 2048 | Ders | |
|---|---|---|---|
| CUDAGraph belleği | 0.0 GiB | 0.27 GiB (+ 6 sn capture) | “Birkaç yüz MB” lafı değil, ölçüm: eager’ın kazandırdığı tam bu |
| KV cache | 1.41 GiB = 13,184 token | 1.52 GiB = 14,192 token | Peak activation küçülünce (0.2 → 0.08 GiB) artan pay cache’e gitti |
| Eşzamanlılık | 4096 token/istek → 3.22x | 2048 token/istek → 6.93x | Context’i yarıya indirmek eşzamanlılığı ikiye katlıyor — KV matematiği iş başında |
| torch.compile | — (kapalı) | 0.29 sn (AOT cache’ten) | Sıcak torch_compile_cache derlemeyi diskten yüklüyor; ilk koşudaki dakikalar sonrakilerde saniyeye iniyor |
Aynı 3.0 GiB bütçe, iki farklı harcama planı: eager modu belleği KV cache’e yatırıyor (daha çok istek sığar, her token biraz yavaş), graph modu hıza yatırıyor (0.27 GiB’ı kalıba bağlar, inference hızlanır). 4 GB’lık kartta ikisi de savunulabilir — önemli olan takasın farkında seçim yapmak. Bir de tabloda görünmeyen bir sürpriz: sıcak compile cache sayesinde graph’lı sunucu bile komuttan “HTTP server started” satırına ~40 saniyede geldi. Soğuk cache’le bu sürenin katlanacağını 4. bölümden biliyorsunuz — demolardan önce sunucuyu bir kez ısındırın.
8. Bugün localhost, Yarın Kiralık H100
Bütün bu maceranın sonunda elimde ne var? OpenAI uyumlu, streaming yapan, usage sayan bir endpoint[9] — ve ona karşı yazılmış istemci kodu:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="bos")
resp = client.chat.completions.create(
model="Qwen/Qwen3-0.6B",
messages=[{"role": "user", "content": "Tek cümleyle: KV cache nedir?"}],
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)
print(resp.choices[0].message.content)
Bu kodun kıymeti şu: serinin sonraki yazılarında kiralık GPU’lara değişmeden bağlanacak — değişen tek satır base_url. Smoke test de aynen taşınacak; BASE_URL environment variable’ı zaten bunun için var.
Sunucunun açılış log’unda bastığı rota listesi de es geçilmeyecek kadar cömert. /v1/chat/completions ve /v1/completions‘ı biliyorduk; ama listede /docs da var — tarayıcıdan localhost:8000/docs açın, bütün API’yi kurcalayabileceğiniz interaktif bir OpenAPI arayüzü sizi karşılıyor; curl yazmadan endpoint denemek için birebir. /metrics, Prometheus formatında sunucu metrikleri döküyor (ikinci yazıda kiralık GPU’yu izlerken işimize yarayacak), /tokenize ile bir metnin kaç token ettiğini sunucuya sorabiliyorsunuz, ve sürpriz konuk: /v1/messages — vLLM, OpenAI’ın yanında Anthropic’in Messages API formatını da konuşuyor; Claude için yazılmış istemci kodunuz varsa onu da doğrudan bağlayabilirsiniz.
Log’dan son bir uyarı da sampling tarafından geldi: “Default vLLM sampling parameters have been overridden by the model’s generation_config.json: {‘temperature’: 0.6, ‘top_k’: 20, ‘top_p’: 0.95}”. Yani modeller Hugging Face’ten kendi örnekleme varsayılanlarıyla geliyor ve vLLM bunları sessizce devralıyor — “temperature vermezsem 1.0’dır” varsayımı bu sunucuda yanlış. İstekte açıkça belirtmediğiniz her parametre modelin tercihiyle dolduruluyor; vLLM’in kendi varsayılanlarına dönmek isterseniz sunucuyu --generation-config vllm ile açın. Reprodüksiyon derdi olan herkesin bilmesi gereken bir ayrıntı.
Son iki WSL notu, ikisi de klasik: model dosyalarını WSL dosya sisteminde tutun, /mnt/c üzerinden erişim 3-5 kat yavaş[3]; ve WSL2 varsayılan olarak sistem RAM’inin yarısını kullandığından[3], model yükleme sırasında darlanırsanız Windows tarafındaki .wslconfig ile tahsisi artırın[4].
9. LangChain ile Aynı Sunucu
8. bölümdeki iddianın — “OpenAI uyumlu API’ye karşı yazılmış her şey buraya bağlanır” — asıl sınavı, ekosistemin üst katmanları. Denedim: LangChain 1.x, hiçbir vLLM’e özel paket kurmadan, standart ChatOpenAI entegrasyonuyla lokal sunucuya bağlanıyor[14]. Değişen yine tek şey base_url; api_key="EMPTY" ise istemcinin şart koştuğu ama sunucunun (biz --api-key vermediğimiz sürece) umursamadığı bir formalite:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="Qwen/Qwen3-0.6B",
base_url="http://localhost:8000/v1",
api_key="EMPTY", # istemci istiyor, sunucu yok sayıyor
temperature=0.3,
max_tokens=600, # Qwen3 cevaba başlamadan ~150 token düşünüyor
)
# Tek tur:
reply = llm.invoke([
("system", "You are terse. Answer in one sentence."),
("human", "Where is the capital of Japan?"),
])
print(reply.text)
print(reply.usage_metadata) # sunucunun usage muhasebesinden dolar
# Standart LCEL zinciri de aynen çalışıyor:
chain = prompt | llm | StrOutputParser()
# Streaming de: for chunk in llm.stream(...): ...
invoke, LCEL zinciri (prompt | llm | StrOutputParser()), streaming — üçü de ilk denemede çalıştı. usage_metadata‘nın dolu gelmesi ayrıca hoş bir detay, çünkü içinde thinking vergisinin makbuzu var: tek cümlelik “Tokyo, Japan’s capital.” cevabı için 29 input, 111 output token — o 111’in aslan payı <think> bloğu. Bölüm 6’da anlattığım maliyet artık soyut değil, faturada kalem kalem.
Ve işte tam burada, thinking meselesinin pipeline boyutu ortaya çıkıyor — sohbette katlanılabilir olan şey zincirde bug’a dönüşüyor. Çeviri zincirim şunu döndürdü:
<think>
Okay, the user wants me to translate "The server is running." to Turkish.
... "running" is "kalan." So the translation should be "Server çalışıyor." ...
</think>
Server çalışıyor.
StrOutputParser‘dan çıkan string’in içinde koca bir <think> bloğu var — bu çıktıyı bir veritabanına yazan, bir UI’da gösteren ya da bir sonraki zincire besleyen herkes için sorun. (Bu arada modelin reasoning’de “running = kalan” deyip çeviriyi yine de doğru toparlaması, 0.6B izlemenin ayrı bir keyfi.) Çözüm derseniz, bölüm 6’daki iki yolun burada gerçek bir takası var:
Yol A — thinking’i kapat: llm.bind(extra_body={"chat_template_kwargs": {"enable_thinking": False}}). En ucuz çözüm ve string tertemiz. Ama Osaka hadisesi burada da birebir tekrarlandı: düşünmeyen model başkent sorusuna yine “Osaka City” dedi. Rastlantı değil, deterministik bir yanlış — bu model doğruluk için o token’lara muhtaç.
Yol B — sunucuda ayrıştır: --reasoning-parser qwen3 ile content yalnızca cevabı taşır, zincirler temiz string alır. Ama yeni bir gerçekle tanıştım: vLLM düşünceyi standart dışı bir reasoning alanında döndürüyor ve ChatOpenAI, resmî OpenAI şemasını hedeflediği için vendor alanlarını ayıklamıyor — reasoning LangChain katmanında sessizce düşüyor (langchain-openai 1.5.1’de doğruladım; additional_kwargs sadece {'refusal': None}). Zincir için mükemmel, ama düşünce metninin kendisi lazımsa ham endpoint’ten okumak zorundasınız.
Yani üçüncü kez aynı ders, bu kez katmanlar arası: uyumluluk “ortak şema” kadardır. OpenAI şeması her katmandan sorunsuz geçti; şemanın dışına taşan ilk alan (vLLM’in reasoning‘i) ilk arakatmanda kayboldu. Kiralık GPU’da kurumsal bir pipeline kurarken bu cümleyi hatırlayacağız.
Kapanış
Bir başarısız kurulum, beş başarısız başlatma — ve karşılığında altı kalıcı ders: eski glibc’nin wheel ekosistemiyle imtihanı, “platform desteklemiyor” ile “araç kapatmış” farkı, etiketteki VRAM ile gerçek VRAM farkı, JIT’in gizli bağımlılıkları, hata mesajının dediği ile gerçekte olanın farkı, ve vendor paketlerinin de hatalı olabileceği gerçeği. Bunların hiçbirini kiralık H100’de öğrenmek istemezdim — orada her hata dakika başı para yazar, burada sadece inat ister.
Şu üçünü yapabiliyorsanız bu dershaneden mezunsunuz: bir modelin VRAM ihtiyacını hesapla söyleyebiliyorsunuz; hata gördüğünüzde sarmalayıcı traceback’i değil kök nedeni arıyorsunuz; ve “sunucu ayakta” demek yerine smoke test koşturuyorsunuz. Bundan sonrası donanım meselesi — 7B ve üstü bu karta hiçbir numarayla sığmayacak. Kolay gelsin!
Kaynaklar
- Kumar Shivam (Medium) — Run vLLM Locally on Low-VRAM Budget Laptop (4GB GPU): Full Guide, Errors & Ollama Alternatives
- mobiarch — Install vLLM in WSL
- InsiderLLM — WSL2 Local AI on Windows: GPU Passthrough, Fixed (2026)
- Ilir Ivezaj — WSL2 + CUDA GPU Development Setup: Complete Guide
- Emilien Lancelot (DEV Community) — Making vLLM Work on WSL2
- Tech Insider — How to Set Up vLLM: 12 Steps, 90 Min (2026)
- DigitalOcean — How to Choose the Right GPU for vLLM Inference
- Will It Run AI — vLLM Tensor Parallel Setup: Copy-Paste Configs + Exact VRAM Math
- vLLM Docs — OpenAI-Compatible Server
- NVIDIA — CUDA on WSL User Guide
- Hugging Face — Qwen3-0.6B Model Card (Thinking Mode)
- vLLM Docs — Reasoning Outputs (–reasoning-parser)
- vLLM Docs — Quickstart (Offline Inference: LLM Class)
- LangChain Docs — ChatOpenAI Integration