Otomatik Görsel Üretim Pipeline’ı: Stable Diffusion API Kullanımı
Görsel üretimi otomatikleştirmek istediğinizde, ilk başta “bir API çağırırsın, görsel gelir” gibi basit görünüyor. Sonra gerçekle yüzleşiyorsunuz: batch işler yarım kalıyor, VRAM yetersizliğinden process çöküyor, üretilen görsellerin kalitesi tutarsız, pipeline’ı izleyecek hiçbir mekanizma yok. Bu yazıda sıfırdan bir production-grade Stable Diffusion API pipeline’ı kurmayı ele alacağız. Sadece “çalışıyor” seviyesinde değil, gerçekten güvenilir bir şekilde.
Mimariye Karar Vermek
Production ortamında SD kullanmak için üç farklı yaklaşım var:
- AUTOMATIC1111 WebUI API modu: En yaygın, en geniş extension desteği, ama tek process ve yönetimi zahmetli
- ComfyUI API: Node-based pipeline, karmaşık workflow’lar için mükemmel, JSON tabanlı workflow tanımları
- Standalone inference: Diffusers kütüphanesi ile doğrudan, en fazla kontrol ama en fazla kod
Biz AUTOMATIC1111’in API modunu temel alacağız çünkü mevcut çoğu ekibin zaten aşina olduğu interface bu. Sonrasında ComfyUI entegrasyonuna da değineceğiz.
Fiziksel kurulum için de bazı kararlar vermek gerekiyor. NVIDIA GPU’su olan bir sunucu mu kullanacaksınız, yoksa birden fazla worker mı? Tek GPU’lu basit bir setup mı, yoksa load balancer arkasında birden fazla instance mı? Bu yazıda tek GPU’lu bir sunucu üzerinde kurulum yapacağız ama multi-instance için de notlar ekleyeceğiz.
AUTOMATIC1111’i API Modunda Ayağa Kaldırmak
Önce gereksinimleri kontrol edelim:
# CUDA versiyonunu kontrol et
nvidia-smi
nvcc --version
# Python versiyonu (3.10 önerilir)
python3 --version
# Disk alanı - modeller için en az 20GB boş alan şart
df -h /opt
Kurulum için temiz bir virtual environment ile başlamak hayat kurtarır:
# Kurulum dizini
sudo mkdir -p /opt/stable-diffusion
sudo chown $USER:$USER /opt/stable-diffusion
cd /opt/stable-diffusion
# SD WebUI klonla
git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
cd stable-diffusion-webui
# Python virtual environment
python3 -m venv venv
source venv/bin/activate
# Gerekli temel paketler
pip install --upgrade pip
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
Şimdi kritik kısma geliyoruz: WebUI’yi sadece API modu olarak çalıştıracak bir launch scripti. Bu scriptı hazırlamak ileride pek çok sorunun önüne geçiyor:
cat > /opt/stable-diffusion/launch_api.sh << 'EOF'
#!/bin/bash
# Ortam değişkenleri
export CUDA_VISIBLE_DEVICES=0
export PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:512
# Log dizini
LOG_DIR="/var/log/stable-diffusion"
mkdir -p $LOG_DIR
cd /opt/stable-diffusion/stable-diffusion-webui
source venv/bin/activate
# API modu: nowebui, api, dinleme adresi
exec python launch.py
--nowebui
--api
--api-log
--listen
--port 7860
--no-half-vae
--xformers
--medvram
--skip-torch-cuda-test
2>&1 | tee -a $LOG_DIR/webui.log
EOF
chmod +x /opt/stable-diffusion/launch_api.sh
Önemli parametrelerin ne işe yaradığını bilmek gerekiyor:
- –nowebui: Gradio arayüzünü açmaz, sadece API çalışır, kaynak tüketimi azalır
- –api: REST API endpoint’lerini aktif eder
- –api-log: Her API isteğini loglar, debug için çok değerli
- –medvram: 6-8GB VRAM’li kartlar için bellek optimizasyonu
- –xformers: Dikkat mekanizması optimizasyonu, hem hız hem bellek için kritik
- –no-half-vae: VAE’yi float32 ile çalıştırır, NaN artifact sorunlarını önler
Systemd service olarak tanımlamak production için şart:
cat > /etc/systemd/system/stable-diffusion-api.service << 'EOF'
[Unit]
Description=Stable Diffusion API Server
After=network.target
Wants=network.target
[Service]
Type=simple
User=sduser
Group=sduser
WorkingDirectory=/opt/stable-diffusion/stable-diffusion-webui
ExecStart=/opt/stable-diffusion/launch_api.sh
Restart=on-failure
RestartSec=10
StandardOutput=append:/var/log/stable-diffusion/service.log
StandardError=append:/var/log/stable-diffusion/error.log
# GPU erişimi için gerekli
Environment="PATH=/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
Environment="LD_LIBRARY_PATH=/usr/local/cuda/lib64"
# OOM durumunda restart
OOMScoreAdjust=500
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable stable-diffusion-api
systemctl start stable-diffusion-api
API ile İlk Bağlantı Testleri
Servis ayağa kalktıktan sonra API’nin gerçekten çalışıp çalışmadığını doğrulamak için birkaç kontrol:
# Servis durumu
curl -s http://localhost:7860/sdapi/v1/progress | python3 -m json.tool
# Yüklü modeller listesi
curl -s http://localhost:7860/sdapi/v1/sd-models | python3 -m json.tool | grep "model_name"
# Sistem bilgisi
curl -s http://localhost:7860/sdapi/v1/memory | python3 -m json.tool
Şimdi asıl işe koyulalım ve gerçek bir görsel üretelim. Önce basit bir test:
curl -X POST http://localhost:7860/sdapi/v1/txt2img
-H "Content-Type: application/json"
-d '{
"prompt": "a photorealistic mountain landscape, golden hour, 8k, detailed",
"negative_prompt": "blurry, low quality, watermark, text",
"steps": 25,
"cfg_scale": 7,
"width": 512,
"height": 512,
"sampler_name": "DPM++ 2M Karras",
"seed": -1,
"save_images": true
}'
| python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
with open('/tmp/test_output.png', 'wb') as f:
f.write(base64.b64decode(data['images'][0]))
print('Görsel kaydedildi: /tmp/test_output.png')
print('Seed:', json.loads(data['info'])['seed'])
"
Production Pipeline: Python Client Kütüphanesi
Curl ile test tamam, ama gerçek pipeline için düzgün bir Python client yazmak gerekiyor. Aşağıdaki sınıf production’da kullandığım versiyonun sadeleştirilmiş hali:
#!/usr/bin/env python3
"""
Stable Diffusion API Production Client
Retry mekanizması, rate limiting ve health check dahil
"""
import time
import base64
import logging
import requests
from pathlib import Path
from dataclasses import dataclass, field
from typing import Optional
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s'
)
logger = logging.getLogger('sd_client')
@dataclass
class GenerationConfig:
prompt: str
negative_prompt: str = "blurry, low quality, deformed, watermark"
steps: int = 25
cfg_scale: float = 7.0
width: int = 512
height: int = 512
sampler_name: str = "DPM++ 2M Karras"
seed: int = -1
batch_size: int = 1
restore_faces: bool = False
hr_scale: float = 1.0 # >1 ise hires.fix aktif
hr_upscaler: str = "Latent"
denoising_strength: float = 0.7
class SDApiClient:
def __init__(
self,
base_url: str = "http://localhost:7860",
timeout: int = 300,
max_retries: int = 3,
retry_delay: float = 5.0
):
self.base_url = base_url.rstrip('/')
self.timeout = timeout
self.max_retries = max_retries
self.retry_delay = retry_delay
self.session = requests.Session()
def health_check(self) -> bool:
try:
resp = self.session.get(
f"{self.base_url}/sdapi/v1/progress",
timeout=10
)
return resp.status_code == 200
except Exception as e:
logger.error(f"Health check hatası: {e}")
return False
def wait_for_ready(self, max_wait: int = 120) -> bool:
logger.info("API hazır olana kadar bekleniyor...")
for i in range(max_wait // 5):
if self.health_check():
logger.info("API hazır")
return True
time.sleep(5)
logger.error(f"{max_wait}s sonra API hala hazır değil")
return False
def generate(
self,
config: GenerationConfig,
output_dir: Optional[Path] = None
) -> list[Path]:
payload = {
"prompt": config.prompt,
"negative_prompt": config.negative_prompt,
"steps": config.steps,
"cfg_scale": config.cfg_scale,
"width": config.width,
"height": config.height,
"sampler_name": config.sampler_name,
"seed": config.seed,
"batch_size": config.batch_size,
"restore_faces": config.restore_faces,
}
if config.hr_scale > 1.0:
payload.update({
"enable_hr": True,
"hr_scale": config.hr_scale,
"hr_upscaler": config.hr_upscaler,
"denoising_strength": config.denoising_strength,
})
for attempt in range(1, self.max_retries + 1):
try:
logger.info(
f"Görsel üretimi başlatıldı "
f"(deneme {attempt}/{self.max_retries})"
)
resp = self.session.post(
f"{self.base_url}/sdapi/v1/txt2img",
json=payload,
timeout=self.timeout
)
resp.raise_for_status()
data = resp.json()
import json as _json
info = _json.loads(data.get('info', '{}'))
seed = info.get('seed', 'unknown')
logger.info(f"Üretim tamamlandı. Seed: {seed}")
if output_dir:
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
saved_paths = []
for idx, img_b64 in enumerate(data['images']):
ts = int(time.time())
filename = f"sd_{ts}_{seed}_{idx}.png"
if output_dir:
path = output_dir / filename
with open(path, 'wb') as f:
f.write(base64.b64decode(img_b64))
saved_paths.append(path)
logger.info(f"Kaydedildi: {path}")
return saved_paths
except requests.exceptions.Timeout:
logger.warning(
f"Timeout (deneme {attempt}). "
f"{self.retry_delay}s sonra tekrar denenecek."
)
except requests.exceptions.HTTPError as e:
logger.error(f"HTTP hatası: {e}")
if e.response.status_code in (400, 422):
raise # Retry yapmanın anlamı yok
except Exception as e:
logger.error(f"Beklenmedik hata: {e}")
if attempt < self.max_retries:
time.sleep(self.retry_delay * attempt)
raise RuntimeError(
f"{self.max_retries} denemeden sonra görsel üretilemedi"
)
Batch İşlem Pipeline’ı
Gerçek dünya senaryosu: bir e-ticaret sitesi için 500 ürün görseli üretmek, her ürünün farklı prompt’u var, bazıları hires.fix gerektiriyor, hataları loglamak gerekiyor.
#!/usr/bin/env python3
"""
Batch görsel üretim pipeline'ı
CSV'den prompt okur, görselleri üretir, rapor oluşturur
"""
import csv
import json
import time
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
# Yukarıda tanımladığımız client ve config sınıflarını import ediyoruz
OUTPUT_BASE = Path("/data/generated-images")
REPORT_FILE = OUTPUT_BASE / "batch_report.jsonl"
FAILED_FILE = OUTPUT_BASE / "failed_prompts.csv"
def process_single(row: dict, client: SDApiClient) -> dict:
"""Tek bir satırı işle, sonucu döndür"""
start = time.time()
product_id = row['product_id']
try:
config = GenerationConfig(
prompt=row['prompt'],
negative_prompt=row.get(
'negative_prompt',
"blurry, low quality, watermark"
),
width=int(row.get('width', 512)),
height=int(row.get('height', 512)),
steps=int(row.get('steps', 25)),
hr_scale=float(row.get('hr_scale', 1.0)),
)
output_dir = OUTPUT_BASE / product_id
paths = client.generate(config, output_dir=output_dir)
duration = time.time() - start
return {
"product_id": product_id,
"status": "success",
"files": [str(p) for p in paths],
"duration_seconds": round(duration, 2),
"timestamp": datetime.utcnow().isoformat()
}
except Exception as e:
duration = time.time() - start
return {
"product_id": product_id,
"status": "failed",
"error": str(e),
"duration_seconds": round(duration, 2),
"timestamp": datetime.utcnow().isoformat()
}
def run_batch(csv_path: str, workers: int = 1):
"""
workers=1 önerilir çünkü tek GPU paylaşımı
birden fazla worker'la VRAM patlamasına yol açar
"""
client = SDApiClient(max_retries=3)
if not client.wait_for_ready(max_wait=180):
raise RuntimeError("API başlatılamadı")
with open(csv_path, 'r', encoding='utf-8') as f:
rows = list(csv.DictReader(f))
print(f"Toplam {len(rows)} görsel üretilecek")
OUTPUT_BASE.mkdir(parents=True, exist_ok=True)
success_count = 0
failed_rows = []
with open(REPORT_FILE, 'a', encoding='utf-8') as report_f:
for i, row in enumerate(rows, 1):
print(f"[{i}/{len(rows)}] İşleniyor: {row['product_id']}")
result = process_single(row, client)
report_f.write(json.dumps(result, ensure_ascii=False) + 'n')
report_f.flush()
if result['status'] == 'success':
success_count += 1
else:
failed_rows.append(row)
print(f" HATA: {result['error']}")
# Başarısız olanları tekrar denemek için kaydet
if failed_rows:
with open(FAILED_FILE, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=failed_rows[0].keys())
writer.writeheader()
writer.writerows(failed_rows)
print(f"nTamamlandı: {success_count}/{len(rows)} başarılı")
print(f"Rapor: {REPORT_FILE}")
if failed_rows:
print(f"Başarısız liste: {FAILED_FILE}")
if __name__ == "__main__":
import sys
run_batch(sys.argv[1])
İzleme ve VRAM Yönetimi
Production’da en büyük sorun GPU belleği. Stable Diffusion uzun süre çalışırken VRAM fragmentasyonu yaşanabilir ve performans düşer. Periyodik model unload/reload yapmak sorunu azaltıyor:
#!/bin/bash
# /opt/scripts/sd_health_monitor.sh
# Crontab: */5 * * * * /opt/scripts/sd_health_monitor.sh
LOG="/var/log/stable-diffusion/monitor.log"
API="http://localhost:7860"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> $LOG
}
# API erişilebilirlik kontrolü
if ! curl -sf "$API/sdapi/v1/progress" > /dev/null; then
log "KRITIK: API yanıt vermiyor, servis yeniden başlatılıyor"
systemctl restart stable-diffusion-api
sleep 30
exit 1
fi
# VRAM kullanımını kontrol et
VRAM_USED=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits | head -1)
VRAM_TOTAL=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | head -1)
VRAM_PCT=$((VRAM_USED * 100 / VRAM_TOTAL))
log "VRAM kullanımı: ${VRAM_USED}MB / ${VRAM_TOTAL}MB (%${VRAM_PCT})"
# %95 üzerinde VRAM kullanımında model unload
if [ $VRAM_PCT -gt 95 ]; then
log "UYARI: VRAM kritik seviyede, model cache temizleniyor"
curl -sf -X POST "$API/sdapi/v1/unload-checkpoint" > /dev/null
sleep 5
curl -sf -X POST "$API/sdapi/v1/reload-checkpoint" > /dev/null
log "Model yeniden yüklendi"
fi
# GPU sıcaklık kontrolü
GPU_TEMP=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader | head -1)
if [ "$GPU_TEMP" -gt 85 ]; then
log "UYARI: GPU sıcaklığı yüksek: ${GPU_TEMP}C"
fi
Model Yönetimi ve ControlNet Entegrasyonu
Birden fazla model kullanılacaksa model değiştirme API üzerinden yapılabilir:
# Mevcut aktif modeli öğren
curl -s http://localhost:7860/sdapi/v1/options
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('sd_model_checkpoint'))"
# Model değiştir (ağır işlem, ~30s sürebilir)
curl -X POST http://localhost:7860/sdapi/v1/options
-H "Content-Type: application/json"
-d '{
"sd_model_checkpoint": "realisticVisionV60B1_v60B1VAE.safetensors"
}'
# ControlNet ile görsel üretme (ControlNet extension yüklüyse)
curl -X POST http://localhost:7860/sdapi/v1/txt2img
-H "Content-Type: application/json"
-d '{
"prompt": "a woman standing, professional photo",
"steps": 30,
"width": 512,
"height": 768,
"alwayson_scripts": {
"controlnet": {
"args": [{
"enabled": true,
"module": "openpose",
"model": "control_v11p_sd15_openpose",
"weight": 0.8,
"image": "<BASE64_POSE_IMAGE>",
"resize_mode": 1,
"control_mode": 0
}]
}
}
}' | python3 -c "
import sys,json,base64
d=json.load(sys.stdin)
open('controlnet_output.png','wb').write(base64.b64decode(d['images'][0]))
print('Tamamlandı')
"
Nginx ile Reverse Proxy ve Temel Güvenlik
API’yi dışarıya açmanız gerekiyorsa Nginx arkasına almak ve en azından basit bir API key kontrolü eklemek şart. Stable Diffusion API’si kendi başına hiçbir authentication mekanizması sunmuyor:
# /etc/nginx/sites-available/stable-diffusion
server {
listen 443 ssl;
server_name sd-api.sirketiniz.com;
ssl_certificate /etc/letsencrypt/live/sd-api.sirketiniz.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sd-api.sirketiniz.com/privkey.pem;
# Basit API key kontrolü
# Gerçek bir auth için ayrı bir proxy servisi yazmanız önerilir
location /sdapi/ {
# Sadece belirli IP'lere izin ver (VPN IP aralığı)
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;
proxy_pass http://127.0.0.1:7860;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Görsel üretimi uzun sürebilir
proxy_read_timeout 600s;
proxy_send_timeout 600s;
# Büyük base64 response'lar için
proxy_buffer_size 128k;
proxy_buffers 8 256k;
proxy_busy_buffers_size 512k;
client_max_body_size 50m;
}
}
Sık Karşılaşılan Sorunlar
CUDA out of memory hatası: En sık karşılaşılan sorun. --medvram veya --lowvram ile başlatın. Hala oluyorsa batch_size’ı 1’e düşürün ve hires.fix’i devre dışı bırakın. Uzun çalışmalar sonrası yaşanıyorsa periyodik model reload scriptinizi çalıştırın.
“Model failed to load” hatası: Model dosyası corrupt olabilir. sha256sum ile hash kontrolü yapın ve modeli tekrar indirin. .safetensors formatı .ckpt‘ye kıyasla çok daha güvenilir.
API çok yavaş, timeout alıyorum: İlk istek her zaman yavaştır çünkü model GPU’ya yüklenir. Timeout değerlerinizi en az 300 saniyeye çekin. Steps sayısını düşürün, DPM++ 2M Karras sampler en iyi hız/kalite dengesini sunar.
Görseller tutarsız kalitede çıkıyor: Seed’i sabitleyerek test edin. Sorun devam ediyorsa --no-half-vae flag’ini ekleyin. CFG scale değeri çok yüksekse (>12) renk dağılımları bozulabilir.
Process çöküyor ama loglarda hata yok: OOM killer devreye girmiş olabilir. dmesg | grep -i "oom|killed" ile kontrol edin. Sistemd servis tanımındaki OOMScoreAdjust değerini düşürün (negatif değer = öldürülme önceliği düşük).
Sonuç
Stable Diffusion API’sini production’a taşımak, modeli çalıştırmaktan çok daha fazlasını gerektiriyor. VRAM yönetimi, retry mekanizmaları, izleme, güvenlik katmanı ve batch işlem koordinasyonu hepsinin bir arada düşünülmesi gerekiyor.
Burada anlattığım yapıyı küçük bir e-ticaret müşterisinin görsel üretim hattında yaklaşık altı aydır kullanıyoruz. Günde ortalama 800-1200 görsel üretiliyor, çalışma süresi %99’un üzerinde. En büyük sorun başta VRAM fragmentasyonu idi, periyodik model reload scriptiyle bunu büyük ölçüde çözdük.
ComfyUI’ye geçmeyi düşünüyorsanız, özellikle karmaşık workflow’larınız varsa (img2img zincirleme, LoRA karıştırma, ControlNet stack’leri) zaman ayırın. Öğrenme eğrisi var ama API üzerinden JSON workflow gönderme mekanizması çok daha deterministik ve yönetilebilir. Bunu da ayrı bir yazının konusu yapalım.
