Gönümüzün Pain-Point’i ve Sık Sorulan Sorusu..
50B/70B LLM’leri Düşük Bütçeli Altyapılarda ve Donanımlarda Çalıştırmanın bir Yolu var mı ?
Evet, Pek mümkün tabiki.. (:
Şartlar doğru sağlandığında, 70B parametreli bir modelin ağırlık matrisleri ortalama 140 GB yer kaplıyor. 4 GB VRAM bunun %3’ünü bile tutamaz. Bu basit bir aritmetik meselesi, tartışılacak bir tarafı da pek yok, başka bir makalede detaylıca tartışırız bunu..
Ama şu da bir gerçek: Türkiye’deki üretim sahalarının büyük kısmında, kontrol odalarındaki iş istasyonlarında 8 GB VRAM’li ekran kartları duruyor. RTX 3070, bazen daha eskisi. Ve bu tesisler, hat üzerindeki kalite raporlarını, bakım loglarını, operatör notlarını işleyecek bir dil modeli istiyor. Veri tesisten çıkmayacak. Bulut seçeneği masada yok. Yeni donanım bütçesi de yok.
Bu cümleyi son bir yılda 50’den fazla farklı üretim tesis’inden duydum. Bunun bir çözümü bazı limitasyonlar dahilinde var. Malasef sektörlerde ve kurumlarda ki uzmanlık bu alanda henüz yeni geliştiği için süreçler tıkanabiliyor.
AirLLM bu tıkanan Düşük Bütçeli çözüm uzayında ve arayışında ilginç bir noktada duruyor. Ekiplerimiz düşük bütçeli (Startup) Savunma Sanayi POC’imiz de Masterlabs olarak bu problemi biraz ezber bozan ve farklı şekilde çözmeyi bir müşterimiz için başardı.
AirLLM ne yapıyor ?
Modelinizi komple VRAM’e almak yerine, her seferinde tek bir katmanı yüklüyor, ardından forward pass’i yapıyorsun ve ara aktivasyonu host belleğe yazıyorsun. Böylece ne oluyor..? Sen bu arada kalan katmanı boşaltıyorsun ve öbürünü getirip, rahatlıkla düşük bütçe ile çalıştırıyorsun. Güzel değil mi ? :)
Aslında bu, operating system’lerde 1960’lardan beri olan (Eski Gurme’ler bilir) page swapping mantığı. Yeni bir fikir falan da değil. Ama bunu transformer inference’a uyarlamak demek, duyduğunuzu tahmin ettiğim CUDA stream yönetimi, pinned memory tahsisi, PCIe transfer zamanlaması gibi farklı katman serialization formatı ile çalışmak da demek oluyor..
Yani ben bu “Fikri Biliyorum” ile “Çalışan Kod Yazıyorum” arasında hani ciddi bir mesafe de artık var..
Pek tabii, AirLLM o mesafeyi de kapatmış. API tarafında transformers ile aynı şeyi kolaylıkla yazabiliyorsun:
import time
from airllm import AutoModel
model = AutoModel.from_pretrained("Qwen/Qwen3-32B")
# Hat 3 operatör terminalinden gelen ham bakım notu
ham_not = "CNC-07 spindle titreşim 0.8 mm/s RMS, devir 12000 rpm, normal değer 0.3. Bakım formu oluştur."
tokenlar = model.tokenizer([ham_not], return_tensors="pt", max_length=128, truncation=True)
t0 = time.perf_counter()
cikti = model.generate(tokenlar["input_ids"].cuda(), max_new_tokens=20)
sure = time.perf_counter() - t0
sonuc = model.tokenizer.decode(cikti.sequences[0], skip_special_tokens=True)
print(f"[{sure:.1f} sn] {sonuc}")
Yeni artık bir soyutlama sistemini öğrenmene de gerek kalmadı. Bu bizce çok doğru bir tasarım kararı olmuş. Artık kimse zaten bu dönemde hadi kalk 45gün bootcamp’e zaman harcayalım, kafayı kıralım, öğrenelim demeyecektir. Herkes “Hızlı Üretim” yolunun peşinde sürükleniyor.
Son günlerin samanyolu yıldızı, Qwen3-235B, 3 GB VRAM ile rahatlıkla çalışıyor. Ayrıca test ettiğimiz Llama 3.1 405B modeli ile 8 GB’de sorunsuz çalışmaktadır.
Sessiz, yavaş ama sesi daima tok gelen model olan, DeepSeek-V3’ün 671B’lik modeli olan 12 GB’de sorunsuz işlem ve testler de gerçekleştirebildik.
İlk çalıştırmada model katmanlara bölünüp diske yazılıyor, bu bir kere oluyor. 70B bir model yaklaşık ortalama 140 GB ek disk alanı istiyor. Disk alanı yetmezse “MetadataIncompleteBuffer” hatası alırsınız ve baştan başlarsınız. Buna dikkat edin, çok Sinir bozucu bir durum ama disk alanını önceden kontrol ederseniz yaşamazsınız.
Bir de “bitsandbytes” ile “opsiyonel blok quantization” var. Buda şu anlama geliyor; model’in Hassasiyet kaybı ölçülebilir seviyenin altında kalırsa, hızınızı yaklaşık 3 kat artıyor. Buda diğer ince önemli bir bilgi..
OpenSource olması sebebiyle, varsayılan Limitasyonlar nedir ?
MasterLabs olarak, POC’miz de Limitasyonlarını test ettiğimiz ve bizlere sağlanan platform ve altyapı kaynağı aşağıdaki gibidir;
- 8 GB VRAM
- NVMe SSD
- 32 GB RAM
- 3xLinux (RT/CoreOS/Ubuntu)
AirLLM tek kullanıcı ve tek sequence üstünde çalışır.
Batch inference henüz yok.
Concurrent request geliştirme seviyedinde.
Production serving istiyorsanız, bunun üzerine birde core mühendislik koymanız gerekir.
Py-Scheduler yazacaksınız, I/O pipeline’ını optimize etmelisiniz, quantization stratejisini kendi use case’inize göre ayarlanmalı.
Özetle, Açık Kaynak bir dağıtılımı olan AirLLM Kütüphanesi size mantıklı ve çözüm odaklı bir iskelet veriyor. Geri kalanını siz inşa ediyorsunuz.
Latency de limitasyon var.
Disk alanı ve Storage algoritması ile bunlar çözülebiliyor.
Bunlar Açık Kaynak (OpenSource) dağıtımlar da tasarımın doğal bir sonucu. Bunu bilerek giriyorsanız, beklentiniz doğruysa, daima işe yarıyor.
Beklentiniz; Skoda Superb ile yolda BMW 5.20 performansı almak ise çok yanlış yerdesiniz demektir.
POC’de nasıl bir mimari ve pattern oluşturduk ?
MasterLabs yaklaşımında Live-Prod ve POC-Test ortamları birbirinden tamamen farklı mimari prensiplerle ele alınır.
Projelerin temeli örneğin çoğunlukla DDD ve EDA Pattern’leri ile oluştuysa, müşterinin teknik ve iş gereksinimlerine bağlı olarak çözümü Hexagonal Architecture veya Clean Architecture ile şekillendiririz. POC seviyesinde, ham (RAW) veya kontrolsüz dağıtık (Mass-Distributed) bir yapıda bırakmayız; yönetilebilir, devamlı ölçeklenebilir, gözlemlenebilir ve üretim ortamına uygun kurumsal bir sürdürülebilir mimariye dönüştürürüz.
Hata yönetimi, metrik toplama, katman optimizasyonu ve yapılandırılmış çıktı üretimi ekliyoruz. Aynı inference sonucunu veriyor ama güvenilir şekilde çalışıyor:
import time
import logging
from pathlib import Path
from dataclasses import dataclass
import torch
from airllm import AutoModel
from airllm.utils import split_and_save_layers
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("inference_worker")
@dataclass
class InferenceConfig:
model_id: str = "Qwen/Qwen3-32B"
cache_dir: str = "/opt/model_cache/split_layers"
compression: str = "4bit"
profiling: bool = True
max_input_len: int = 512
max_output_len: int = 256
@dataclass
class InferenceResult:
text: str = ""
first_token_ms: float = 0.0
total_ms: float = 0.0
token_count: int = 0
tokens_per_sec: float = 0.0
class LayeredInferenceEngine:
def __init__(self, cfg: InferenceConfig):
self.cfg = cfg
self.model = None
self._ensure_split()
def _ensure_split(self):
split_path = Path(self.cfg.cache_dir) / self.cfg.model_id.replace("/", "--")
if not split_path.exists() or not (split_path / "metadata.json").exists():
logger.info("Katman bölme işlemi başlıyor: %s", self.cfg.model_id)
split_and_save_layers(
self.cfg.model_id,
output_dir=str(split_path),
compression=self.cfg.compression,
)
logger.info("Bölme tamamlandı: %s", split_path)
else:
logger.info("Mevcut bölünmüş katmanlar kullanılıyor: %s", split_path)
def load(self):
logger.info("Model yükleniyor: %s (compression=%s)", self.cfg.model_id, self.cfg.compression)
t0 = time.perf_counter()
self.model = AutoModel.from_pretrained(
self.cfg.model_id,
compression=self.cfg.compression,
profiling=self.cfg.profiling,
)
elapsed = time.perf_counter() - t0
logger.info("Model hazır. Yükleme süresi: %.1f sn", elapsed)
def generate(self, prompt: str) -> InferenceResult:
if self.model is None:
self.load()
result = InferenceResult()
t_start = time.perf_counter()
input_tokens = self.model.tokenizer(
[prompt],
return_tensors="pt",
max_length=self.cfg.max_input_len,
truncation=True,
)
input_ids = input_tokens["input_ids"].cuda()
t_first = time.perf_counter()
output = self.model.generate(
input_ids,
max_new_tokens=self.cfg.max_output_len,
do_sample=False,
temperature=1.0,
)
t_end = time.perf_counter()
generated_ids = output.sequences[0][input_ids.shape[1]:]
result.text = self.model.tokenizer.decode(generated_ids, skip_special_tokens=True)
result.token_count = len(generated_ids)
result.first_token_ms = (t_first - t_start) * 1000
result.total_ms = (t_end - t_start) * 1000
result.tokens_per_sec = result.token_count / ((t_end - t_start) or 1e-6)
logger.info(
"Üretim tamamlandı | token=%d | ilk_token=%.0fms | toplam=%.0fms | hız=%.1f tok/s",
result.token_count, result.first_token_ms, result.total_ms, result.tokens_per_sec,
)
return result
def main():
cfg = InferenceConfig(
model_id="Qwen/Qwen3-32B",
cache_dir="/opt/model_cache/split_layers",
compression="4bit",
)
engine = LayeredInferenceEngine(cfg)
engine.load()
operator_note = (
"Hat 3'teki CNC tezgahı son 2 saattir titreşim yapıyor. "
"Spindle hızı 12000 rpm'de iken 0.8 mm/s RMS ölçüldü. "
"Normal değer 0.3 mm/s. Bakım formu oluştur."
)
result = engine.generate(operator_note)
print("\n--- Yapılandırılmış Bakım Formu ---")
print(result.text)
print(f"\n[Metrik] {result.token_count} token, {result.total_ms:.0f} ms, {result.tokens_per_sec:.1f} tok/s")
if __name__ == "__main__":
main()
Yukarıda paylaştığım code’u ayrıca test ettiğim de AirLLM ile nerdeyse aynı sonucu üretti. Fakat bunun katman bölüm kontrolü, hata yönetimi, metrik toplama ve yapılandırılmış çıktı başlıklarını sağlamayı ve hazırlamayı unutmamak gerekiyor.
Üretim sahasında “Çalışıyor” ile “Güvenilir ve Sürdürülebilir şekilde Çalışıyor” arasındaki farkı görmek istiyorsanız. Bu işin “Gizli Formülü” de bu katmanlarda saklı olduğunu unutmayın..
Sonraki 2.Bölüm de..
Bir sonraki 2.Bölüm’de ki yazıda, bu inference engine’i gerçek bir üretim sahasına nasıl taşıdığımızı anlatacağım.
Türkiye’de büyük bir otomotiv yan sanayi üretim tesisinde yaptığımız POC’un teknik detayları kısıtlı donanım, izole private AI Model ortamı (air-gapped) tasarımı ile yeni bir donanım almadan ve production-grade bir ortamda nasıl verim alınır paylaşmış olacağız.
Kerem Çeliker
Founder, Chief Technology & Product Officer
MasterLabs Teknoloji A.Ş.
•masterlabs.com.tr ||•info@masterlabs.com.tr




Top comments (0)