Instructions to use SykoSLM/SykoDiffusion-V1.1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use SykoSLM/SykoDiffusion-V1.1 with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("SykoSLM/SykoDiffusion-V1.1", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
Upload anime_t2i_tpu.py with huggingface_hub
Browse files- anime_t2i_tpu.py +739 -0
anime_t2i_tpu.py
ADDED
|
@@ -0,0 +1,739 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""
|
| 3 |
+
Anime yüzü text-to-image (pixel-space diffusion) - Google TPU v5e-1 (PyTorch/XLA) sürümü.
|
| 4 |
+
|
| 5 |
+
Mimari : diffusers.UNet2DConditionModel (~17-18M parametre, sıfırdan eğitiliyor)
|
| 6 |
+
Metin : CLIP ViT-B/32 text encoder (DONDURULMUŞ; embedding'ler önceden hesaplanır)
|
| 7 |
+
Veri : huggan/anime-faces -> CLIP zero-shot ile saç rengi / saç uzunluğu / göz rengi / gülümseme
|
| 8 |
+
etiketleri -> "anime face, blue long hair, red eyes, smiling"
|
| 9 |
+
|
| 10 |
+
GPU sürümüne göre TPU için yapılan değişiklikler
|
| 11 |
+
* Accelerate / DDP / GradScaler / fp16 yok. TPU v5e-1 tek çip -> tek süreç.
|
| 12 |
+
Hesap bf16 (torch.autocast("xla")), master ağırlıklar + EMA + optimizer durumu FP32.
|
| 13 |
+
bf16'nın aralığı fp32 ile aynı olduğu için "FP32 attention" hack'ine ve loss-scaling'e gerek kalmadı.
|
| 14 |
+
* Tüm şekiller statik (batch, çözünürlük, token sayısı sabit) -> XLA grafiği 1 kez derlenir.
|
| 15 |
+
* Adım içinde HİÇ host<->device senkronizasyonu yok (.item(), python if, isfinite kontrolü vs. yok).
|
| 16 |
+
lr, warmup, cosine, Adam adım sayacı, EMA decay hepsi cihaz üzerinde TENSÖR olarak hesaplanır
|
| 17 |
+
(python float lr kullanmak XLA'da her adımda yeniden derlemeye yol açar).
|
| 18 |
+
* Özel AdamW + grad-clip + NaN/inf koruması (bozuk adım cihaz üzerinde atlanır, host'a hiç dönülmez).
|
| 19 |
+
* Bellek-verimli (query-chunked, flash-benzeri, özel backward) self-attention:
|
| 20 |
+
XLA'da SDPA tam (B,H,S,S) skor matrisini HBM'de tutar; 32x32 seviyesinde (S=1024) batch 128+ OOM olurdu.
|
| 21 |
+
* Veri seti (uint8, NCHW) tamamen HBM'de; batch örnekleme/flip/caption seçimi cihazda -> DataLoader yok.
|
| 22 |
+
* DDIM örnekleyici elle yazıldı: her adımın grafiği aynı (t ve alpha'lar girdi tensörü) -> 50 adım TEK derleme.
|
| 23 |
+
* Kalıcı XLA derleme önbelleği (xla_cache/): ikinci çalıştırmada / resume'da derleme süresi ~0.
|
| 24 |
+
* prepare: CLIP görsel encoder'ı da TPU'da (sabit batch=256, son batch padlenir).
|
| 25 |
+
* Checkpoint'ler CPU'ya alınıp yazılır; torchvision bağımlılığı kaldırıldı (grid'i PIL ile yazıyoruz).
|
| 26 |
+
|
| 27 |
+
KULLANIM (Colab: Runtime > TPU v5e-1)
|
| 28 |
+
!pip -q install diffusers transformers # torch_xla hazır değilse aşağıya bak
|
| 29 |
+
!python anime_t2i_tpu.py prepare
|
| 30 |
+
!python anime_t2i_tpu.py train --steps 40000 --batch 128 --lr 1e-4
|
| 31 |
+
!python anime_t2i_tpu.py sample --prompt "anime face, blue long hair, red eyes, smiling"
|
| 32 |
+
|
| 33 |
+
torch_xla yoksa: !pip -q install torch==2.8.0 'torch_xla[tpu]==2.8.0'
|
| 34 |
+
Kalıcı klasör : WORK_DIR=/content/drive/MyDrive/anime_t2i (checkpoint'ler Drive'a yazılır)
|
| 35 |
+
Devam etme : ... train --resume <WORK>/ckpt/train_state_4000.pt --lr 5e-5
|
| 36 |
+
(eski GPU checkpoint'leri de yüklenir; optimizer durumu sıfırdan başlar)
|
| 37 |
+
Ayar önerileri : --batch 256 (HBM izin veriyorsa; lr'yi 1.5e-4 - 2e-4 yap), --base_ch 128 (MXU dostu, ~4x param)
|
| 38 |
+
Test (TPU'suz) : FORCE_CPU=1 python anime_t2i_tpu.py train --steps 5 --batch 8 --base_ch 32 ...
|
| 39 |
+
"""
|
| 40 |
+
import os
|
| 41 |
+
import time
|
| 42 |
+
import math
|
| 43 |
+
import argparse
|
| 44 |
+
import zipfile
|
| 45 |
+
import contextlib
|
| 46 |
+
from pathlib import Path
|
| 47 |
+
|
| 48 |
+
import numpy as np
|
| 49 |
+
import torch
|
| 50 |
+
import torch.nn.functional as F
|
| 51 |
+
from PIL import Image
|
| 52 |
+
|
| 53 |
+
# ---------------------------------------------------------------- XLA (opsiyonel)
|
| 54 |
+
HAS_XLA = False
|
| 55 |
+
if os.environ.get("FORCE_CPU", "0") != "1":
|
| 56 |
+
try:
|
| 57 |
+
import torch_xla
|
| 58 |
+
import torch_xla.core.xla_model as xm
|
| 59 |
+
HAS_XLA = True
|
| 60 |
+
except Exception:
|
| 61 |
+
HAS_XLA = False
|
| 62 |
+
|
| 63 |
+
# ------------------------------------------------------------------ AYARLAR
|
| 64 |
+
RES = 64 # çözünürlük (4'e bölünebilmeli)
|
| 65 |
+
T_STEPS = 1000 # difüzyon adım sayısı
|
| 66 |
+
TOK_LEN = 24 # caption token uzunluğu
|
| 67 |
+
CLIP_ID = "openai/clip-vit-base-patch32"
|
| 68 |
+
P_UNCOND = 0.1 # classifier-free guidance için caption'ı düşürme olasılığı
|
| 69 |
+
MIN_SNR_GAMMA = 5.0 # Min-SNR loss ağırlığı
|
| 70 |
+
SEED = 42
|
| 71 |
+
WARMUP = 1000
|
| 72 |
+
LOG_EVERY = 100
|
| 73 |
+
ATTN_BUDGET = 2 ** 27 # chunked attention: bir chunk'taki skor elemanı sayısı üst sınırı (fp32 ~512MB)
|
| 74 |
+
|
| 75 |
+
if os.environ.get("WORK_DIR"):
|
| 76 |
+
WORK = Path(os.environ["WORK_DIR"])
|
| 77 |
+
elif Path("/kaggle/working").exists():
|
| 78 |
+
WORK = Path("/kaggle/working")
|
| 79 |
+
elif Path("/content").exists():
|
| 80 |
+
WORK = Path("/content/work")
|
| 81 |
+
else:
|
| 82 |
+
WORK = Path("./work")
|
| 83 |
+
DATA = WORK / "data"
|
| 84 |
+
CKPT = WORK / "ckpt"
|
| 85 |
+
OUT = WORK / "samples"
|
| 86 |
+
|
| 87 |
+
HAIR_COLORS = ["blonde", "brown", "black", "blue", "pink", "purple", "green", "red", "silver", "white", "orange"]
|
| 88 |
+
EYE_COLORS = ["blue", "red", "green", "brown", "purple", "yellow", "orange", "pink", "black"]
|
| 89 |
+
HAIR_LEN = ["long", "short"]
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# ------------------------------------------------------------------ CİHAZ YARDIMCILARI
|
| 93 |
+
def init_xla_cache():
|
| 94 |
+
"""Kalıcı derleme önbelleği: cihaz kullanılmadan ÖNCE çağrılmalı."""
|
| 95 |
+
if not HAS_XLA:
|
| 96 |
+
return
|
| 97 |
+
try:
|
| 98 |
+
import torch_xla.runtime as xr
|
| 99 |
+
(WORK / "xla_cache").mkdir(parents=True, exist_ok=True)
|
| 100 |
+
xr.initialize_cache(str(WORK / "xla_cache"), readonly=False)
|
| 101 |
+
except Exception as e:
|
| 102 |
+
print("XLA derleme önbelleği açılamadı (sorun değil):", e)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def get_device():
|
| 106 |
+
if HAS_XLA:
|
| 107 |
+
os.environ.setdefault("PJRT_DEVICE", "TPU")
|
| 108 |
+
try:
|
| 109 |
+
return torch_xla.device()
|
| 110 |
+
except AttributeError:
|
| 111 |
+
return xm.xla_device()
|
| 112 |
+
return torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def sync():
|
| 116 |
+
"""Lazy grafiği cihaza gönder (adım sınırı). XLA dışında no-op."""
|
| 117 |
+
if HAS_XLA:
|
| 118 |
+
if hasattr(torch_xla, "sync"):
|
| 119 |
+
torch_xla.sync()
|
| 120 |
+
else:
|
| 121 |
+
xm.mark_step()
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def amp(dev):
|
| 125 |
+
"""bf16 autocast (TPU'da doğal). CPU'da kapalı (test için fp32)."""
|
| 126 |
+
if dev.type == "xla":
|
| 127 |
+
try:
|
| 128 |
+
return torch.autocast("xla", dtype=torch.bfloat16)
|
| 129 |
+
except Exception:
|
| 130 |
+
from torch_xla.amp import autocast as xla_autocast
|
| 131 |
+
return xla_autocast(dev, dtype=torch.bfloat16)
|
| 132 |
+
if dev.type == "cuda" and torch.cuda.is_bf16_supported():
|
| 133 |
+
return torch.autocast("cuda", dtype=torch.bfloat16)
|
| 134 |
+
return contextlib.nullcontext()
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def seed_all(seed):
|
| 138 |
+
torch.manual_seed(seed)
|
| 139 |
+
np.random.seed(seed % (2 ** 32))
|
| 140 |
+
if HAS_XLA:
|
| 141 |
+
try:
|
| 142 |
+
xm.set_rng_state(seed)
|
| 143 |
+
except Exception:
|
| 144 |
+
pass
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
# ------------------------------------------------------------------ BELLEK-VERİMLİ ATTENTION
|
| 148 |
+
class _ChunkedAttnFn(torch.autograd.Function):
|
| 149 |
+
"""
|
| 150 |
+
softmax(QK^T * scale) V — query'leri parçalara bölerek hesaplar, (B,H,S,S) matrisi asla tamamen tutulmaz.
|
| 151 |
+
Backward'da skorlar yeniden hesaplanır (FlashAttention mantığı; yalnızca q,k,v,o,logsumexp saklanır).
|
| 152 |
+
softmax/exp fp32, matmul'lar girdi dtype'ında (bf16) yapılır.
|
| 153 |
+
"""
|
| 154 |
+
|
| 155 |
+
@staticmethod
|
| 156 |
+
def forward(ctx, q, k, v, scale, chunk):
|
| 157 |
+
outs, lses = [], []
|
| 158 |
+
kt = k.transpose(-1, -2)
|
| 159 |
+
for i in range(0, q.shape[2], chunk):
|
| 160 |
+
s = torch.matmul(q[:, :, i:i + chunk], kt).float() * scale
|
| 161 |
+
lse = torch.logsumexp(s, dim=-1, keepdim=True)
|
| 162 |
+
p = torch.exp(s - lse).to(v.dtype)
|
| 163 |
+
outs.append(torch.matmul(p, v))
|
| 164 |
+
lses.append(lse)
|
| 165 |
+
o = torch.cat(outs, dim=2)
|
| 166 |
+
lse = torch.cat(lses, dim=2)
|
| 167 |
+
ctx.save_for_backward(q, k, v, o, lse)
|
| 168 |
+
ctx.scale, ctx.chunk = scale, chunk
|
| 169 |
+
return o
|
| 170 |
+
|
| 171 |
+
@staticmethod
|
| 172 |
+
def backward(ctx, do):
|
| 173 |
+
q, k, v, o, lse = ctx.saved_tensors
|
| 174 |
+
scale, chunk = ctx.scale, ctx.chunk
|
| 175 |
+
do = do.to(q.dtype)
|
| 176 |
+
delta = (do.float() * o.float()).sum(-1, keepdim=True) # (B,H,S,1)
|
| 177 |
+
kt, vt = k.transpose(-1, -2), v.transpose(-1, -2)
|
| 178 |
+
dk = torch.zeros(k.shape, dtype=torch.float32, device=k.device)
|
| 179 |
+
dv = torch.zeros(v.shape, dtype=torch.float32, device=v.device)
|
| 180 |
+
dqs = []
|
| 181 |
+
for i in range(0, q.shape[2], chunk):
|
| 182 |
+
qc, doc = q[:, :, i:i + chunk], do[:, :, i:i + chunk]
|
| 183 |
+
s = torch.matmul(qc, kt).float() * scale
|
| 184 |
+
p = torch.exp(s - lse[:, :, i:i + chunk])
|
| 185 |
+
dp = torch.matmul(doc, vt).float()
|
| 186 |
+
ds = (p * (dp - delta[:, :, i:i + chunk]) * scale).to(q.dtype)
|
| 187 |
+
dqs.append(torch.matmul(ds, k))
|
| 188 |
+
dk = dk + torch.matmul(ds.transpose(-1, -2), qc).float()
|
| 189 |
+
dv = dv + torch.matmul(p.to(q.dtype).transpose(-1, -2), doc).float()
|
| 190 |
+
return torch.cat(dqs, dim=2), dk.to(k.dtype), dv.to(v.dtype), None, None
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def chunked_attention(q, k, v, scale, budget=ATTN_BUDGET):
|
| 194 |
+
"""q,k,v: (B,H,S,D)."""
|
| 195 |
+
B, H, S, _ = q.shape
|
| 196 |
+
c = max(16, min(S, budget // (B * H * k.shape[2])))
|
| 197 |
+
c = 1 << (int(c).bit_length() - 1) # 2'nin kuvveti (S de 2'nin kuvveti -> tam bölünür)
|
| 198 |
+
return _ChunkedAttnFn.apply(q, k, v, scale, min(c, S))
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
class ChunkedAttnProcessor:
|
| 202 |
+
"""diffusers Attention için processor: self-attn -> chunked, cross-attn (K uzunluğu 24) -> SDPA."""
|
| 203 |
+
|
| 204 |
+
def __call__(self, attn, hidden_states, encoder_hidden_states=None, attention_mask=None, temb=None,
|
| 205 |
+
*args, **kwargs):
|
| 206 |
+
if attention_mask is not None or getattr(attn, "group_norm", None) is not None \
|
| 207 |
+
or getattr(attn, "spatial_norm", None) is not None:
|
| 208 |
+
raise NotImplementedError("ChunkedAttnProcessor bu attention yapılandırmasını desteklemiyor; --attn sdpa kullan.")
|
| 209 |
+
is_cross = encoder_hidden_states is not None
|
| 210 |
+
ctx = encoder_hidden_states if is_cross else hidden_states
|
| 211 |
+
if is_cross and getattr(attn, "norm_cross", None):
|
| 212 |
+
ctx = attn.norm_encoder_hidden_states(ctx)
|
| 213 |
+
B = hidden_states.shape[0]
|
| 214 |
+
q, k, v = attn.to_q(hidden_states), attn.to_k(ctx), attn.to_v(ctx)
|
| 215 |
+
H = attn.heads
|
| 216 |
+
D = q.shape[-1] // H
|
| 217 |
+
q = q.reshape(B, -1, H, D).transpose(1, 2)
|
| 218 |
+
k = k.reshape(B, -1, H, D).transpose(1, 2)
|
| 219 |
+
v = v.reshape(B, -1, H, D).transpose(1, 2)
|
| 220 |
+
if is_cross:
|
| 221 |
+
o = F.scaled_dot_product_attention(q, k, v)
|
| 222 |
+
else:
|
| 223 |
+
o = chunked_attention(q, k, v, attn.scale)
|
| 224 |
+
o = o.transpose(1, 2).reshape(B, -1, H * D).to(q.dtype)
|
| 225 |
+
o = attn.to_out[0](o)
|
| 226 |
+
return attn.to_out[1](o)
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def set_attn(unet, mode):
|
| 230 |
+
if mode == "chunked":
|
| 231 |
+
unet.set_attn_processor(ChunkedAttnProcessor())
|
| 232 |
+
return unet
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
# ------------------------------------------------------------------ MODEL
|
| 236 |
+
def build_unet(base_ch=64, attn="chunked"):
|
| 237 |
+
"""Hazır diffusers UNet'i, küçük config ile (base_ch=64 -> ~17-18M param)."""
|
| 238 |
+
from diffusers import UNet2DConditionModel
|
| 239 |
+
unet = UNet2DConditionModel(
|
| 240 |
+
sample_size=RES,
|
| 241 |
+
in_channels=3,
|
| 242 |
+
out_channels=3,
|
| 243 |
+
down_block_types=("DownBlock2D", "CrossAttnDownBlock2D", "CrossAttnDownBlock2D"),
|
| 244 |
+
up_block_types=("CrossAttnUpBlock2D", "CrossAttnUpBlock2D", "UpBlock2D"),
|
| 245 |
+
block_out_channels=(96, 192, 384), # base_ch=128 -> MXU (128x128) dostu
|
| 246 |
+
layers_per_block=1,
|
| 247 |
+
cross_attention_dim=512, # CLIP ViT-B/32 text hidden size
|
| 248 |
+
attention_head_dim=8,
|
| 249 |
+
norm_num_groups=32,
|
| 250 |
+
)
|
| 251 |
+
torch.nn.init.zeros_(unet.conv_out.weight)
|
| 252 |
+
torch.nn.init.zeros_(unet.conv_out.bias)
|
| 253 |
+
return set_attn(unet, attn)
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
class EMA:
|
| 257 |
+
"""Cihaz üzerinde EMA. decay warmup'ı tensör olarak hesaplanır -> derleme tekrarı yok."""
|
| 258 |
+
|
| 259 |
+
def __init__(self, params, dev, decay=0.9995):
|
| 260 |
+
self.shadow = [p.detach().clone() for p in params]
|
| 261 |
+
self.decay = decay
|
| 262 |
+
self.n = torch.tensor(0.0, device=dev)
|
| 263 |
+
|
| 264 |
+
@torch.no_grad()
|
| 265 |
+
def update(self, params):
|
| 266 |
+
self.n += 1.0
|
| 267 |
+
d = torch.clamp((1.0 + self.n) / (10.0 + self.n), max=self.decay)
|
| 268 |
+
w = 1.0 - d
|
| 269 |
+
for s, p in zip(self.shadow, params):
|
| 270 |
+
s.add_((p.detach() - s) * w)
|
| 271 |
+
|
| 272 |
+
@torch.no_grad()
|
| 273 |
+
def copy_to(self, model):
|
| 274 |
+
for p, s in zip(model.parameters(), self.shadow):
|
| 275 |
+
p.copy_(s)
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
class DeviceAdamW:
|
| 279 |
+
"""
|
| 280 |
+
AdamW (decoupled wd). lr ve adım sayacı cihaz tensörüdür; `ok` False ise (grad NaN/inf)
|
| 281 |
+
m, v, p ve sayaç güncellenmez (adım tamamen atlanır) — host'a dönmeden.
|
| 282 |
+
"""
|
| 283 |
+
|
| 284 |
+
def __init__(self, params, dev, betas=(0.9, 0.99), eps=1e-6, weight_decay=0.01):
|
| 285 |
+
self.params = params
|
| 286 |
+
self.b1, self.b2 = betas
|
| 287 |
+
self.eps, self.wd = eps, weight_decay
|
| 288 |
+
self.m = [torch.zeros_like(p) for p in params]
|
| 289 |
+
self.v = [torch.zeros_like(p) for p in params]
|
| 290 |
+
self.t = torch.tensor(0.0, device=dev)
|
| 291 |
+
|
| 292 |
+
@torch.no_grad()
|
| 293 |
+
def step(self, lr, ok):
|
| 294 |
+
self.t += ok.float()
|
| 295 |
+
t = torch.clamp(self.t, min=1.0)
|
| 296 |
+
bc1 = 1.0 - self.b1 ** t
|
| 297 |
+
bc2 = 1.0 - self.b2 ** t
|
| 298 |
+
step_size = lr / bc1
|
| 299 |
+
inv_sqrt_bc2 = torch.rsqrt(bc2)
|
| 300 |
+
decay = 1.0 - lr * self.wd
|
| 301 |
+
for p, m, v in zip(self.params, self.m, self.v):
|
| 302 |
+
if p.grad is None:
|
| 303 |
+
continue
|
| 304 |
+
g = torch.where(ok, p.grad, torch.zeros_like(p.grad))
|
| 305 |
+
m_new = m + (g - m) * (1.0 - self.b1)
|
| 306 |
+
v_new = v + (g * g - v) * (1.0 - self.b2)
|
| 307 |
+
p_new = p * decay - step_size * m_new / (v_new.sqrt() * inv_sqrt_bc2 + self.eps)
|
| 308 |
+
p.copy_(torch.where(ok, p_new, p))
|
| 309 |
+
m.copy_(torch.where(ok, m_new, m))
|
| 310 |
+
v.copy_(torch.where(ok, v_new, v))
|
| 311 |
+
|
| 312 |
+
def zero_grad(self):
|
| 313 |
+
for p in self.params:
|
| 314 |
+
p.grad = None
|
| 315 |
+
|
| 316 |
+
def state_dict(self):
|
| 317 |
+
return {"m": [x.detach().cpu() for x in self.m], "v": [x.detach().cpu() for x in self.v],
|
| 318 |
+
"t": float(self.t.item())}
|
| 319 |
+
|
| 320 |
+
@torch.no_grad()
|
| 321 |
+
def load_state_dict(self, sd):
|
| 322 |
+
for a, b in zip(self.m, sd["m"]):
|
| 323 |
+
a.copy_(b.to(a.device))
|
| 324 |
+
for a, b in zip(self.v, sd["v"]):
|
| 325 |
+
a.copy_(b.to(a.device))
|
| 326 |
+
self.t.fill_(float(sd["t"]))
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def clip_grads(params, max_norm):
|
| 330 |
+
"""Global grad-norm clip, host senkronizasyonu yok. Clip ÖNCESİ normu döner."""
|
| 331 |
+
grads = [p.grad for p in params if p.grad is not None]
|
| 332 |
+
total = torch.sqrt(sum(g.float().pow(2).sum() for g in grads))
|
| 333 |
+
coef = torch.clamp(max_norm / (total + 1e-6), max=1.0)
|
| 334 |
+
for g in grads:
|
| 335 |
+
g.mul_(coef)
|
| 336 |
+
return total
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
def lr_tensor(step_t, base_lr, total_steps):
|
| 340 |
+
"""Warmup + cosine, tamamen cihazda (step_t: float tensör)."""
|
| 341 |
+
warm = base_lr * (step_t + 1.0) / WARMUP
|
| 342 |
+
prog = torch.clamp((step_t - WARMUP) / max(1, total_steps - WARMUP), min=0.0, max=1.0)
|
| 343 |
+
cos = base_lr * (0.05 + 0.95 * 0.5 * (1.0 + torch.cos(math.pi * prog)))
|
| 344 |
+
return torch.where(step_t < WARMUP, warm, cos)
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
# ------------------------------------------------------------------ CLIP YARDIMCILARI (CPU)
|
| 348 |
+
def load_clip_text():
|
| 349 |
+
from transformers import CLIPTokenizer, CLIPTextModelWithProjection
|
| 350 |
+
tok = CLIPTokenizer.from_pretrained(CLIP_ID)
|
| 351 |
+
txt = CLIPTextModelWithProjection.from_pretrained(CLIP_ID).eval()
|
| 352 |
+
return tok, txt
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
@torch.no_grad()
|
| 356 |
+
def encode_prompts(tok, txt, prompts):
|
| 357 |
+
t = tok(prompts, padding="max_length", max_length=TOK_LEN, truncation=True, return_tensors="pt")
|
| 358 |
+
return txt(**t).last_hidden_state # (B, TOK_LEN, 512)
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
# ------------------------------------------------------------------ ÖRNEK ÜRETİMİ (DDIM + CFG)
|
| 362 |
+
@torch.no_grad()
|
| 363 |
+
def generate(unet, cond, null, steps=50, cfg=4.0, seed=0):
|
| 364 |
+
"""
|
| 365 |
+
cond: (B,L,D), null: (1,L,D) -> [0,1] aralığında (B,3,H,W).
|
| 366 |
+
DDIM (eta=0, clip_sample) elle yazıldı: t / alpha değerleri her adımda GİRDİ tensörü,
|
| 367 |
+
böylece adım grafiği tek kez derlenir ve 50 adımda yeniden kullanılır.
|
| 368 |
+
"""
|
| 369 |
+
from diffusers import DDIMScheduler
|
| 370 |
+
dev = cond.device
|
| 371 |
+
B = cond.shape[0]
|
| 372 |
+
sched = DDIMScheduler(num_train_timesteps=T_STEPS, beta_schedule="squaredcos_cap_v2", clip_sample=True,
|
| 373 |
+
prediction_type="epsilon", timestep_spacing="trailing")
|
| 374 |
+
sched.set_timesteps(steps) # CPU
|
| 375 |
+
ts = sched.timesteps.long()
|
| 376 |
+
prev = ts - T_STEPS // steps
|
| 377 |
+
ac = sched.alphas_cumprod.float()
|
| 378 |
+
a_t = ac[ts]
|
| 379 |
+
a_p = torch.where(prev >= 0, ac[prev.clamp(min=0)], sched.final_alpha_cumprod.float())
|
| 380 |
+
table = torch.stack([ts.float(), a_t, a_p], dim=1) # (steps,3) CPU
|
| 381 |
+
|
| 382 |
+
g = torch.Generator().manual_seed(seed)
|
| 383 |
+
x = torch.randn(B, 3, RES, RES, generator=g).to(dev)
|
| 384 |
+
ctx = torch.cat([null.to(dev).float().expand(B, -1, -1), cond.float()])
|
| 385 |
+
for i in range(steps):
|
| 386 |
+
row = table[i].to(dev)
|
| 387 |
+
t = row[0].expand(2 * B)
|
| 388 |
+
with amp(dev):
|
| 389 |
+
eps = unet(torch.cat([x, x]), t, encoder_hidden_states=ctx).sample
|
| 390 |
+
e_u, e_c = eps.float().chunk(2)
|
| 391 |
+
eps = e_u + cfg * (e_c - e_u)
|
| 392 |
+
at, ap = row[1], row[2]
|
| 393 |
+
x0 = ((x - (1.0 - at).sqrt() * eps) / at.sqrt()).clamp(-1, 1)
|
| 394 |
+
x = ap.sqrt() * x0 + (1.0 - ap).sqrt() * eps
|
| 395 |
+
sync()
|
| 396 |
+
return (x.clamp(-1, 1) + 1) / 2
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def save_grid(x, path, nrow, pad=2):
|
| 400 |
+
x = (x.detach().float().cpu().clamp(0, 1) * 255 + 0.5).byte().permute(0, 2, 3, 1).numpy()
|
| 401 |
+
n, h, w, _ = x.shape
|
| 402 |
+
rows = (n + nrow - 1) // nrow
|
| 403 |
+
canvas = np.zeros((rows * (h + pad) + pad, nrow * (w + pad) + pad, 3), dtype=np.uint8)
|
| 404 |
+
for i in range(n):
|
| 405 |
+
r, c = divmod(i, nrow)
|
| 406 |
+
y0, x0 = pad + r * (h + pad), pad + c * (w + pad)
|
| 407 |
+
canvas[y0:y0 + h, x0:x0 + w] = x[i]
|
| 408 |
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
| 409 |
+
Image.fromarray(canvas).save(str(path))
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
# ------------------------------------------------------------------ 1) VERİ HAZIRLAMA
|
| 413 |
+
def collect_files(image_dir):
|
| 414 |
+
exts = {".jpg", ".jpeg", ".png", ".webp", ".bmp"}
|
| 415 |
+
if image_dir is None:
|
| 416 |
+
from huggingface_hub import hf_hub_download
|
| 417 |
+
z = hf_hub_download(repo_id="huggan/anime-faces", filename="data.zip", repo_type="dataset")
|
| 418 |
+
out = WORK / "raw"
|
| 419 |
+
if not out.exists():
|
| 420 |
+
print("data.zip açılıyor...")
|
| 421 |
+
with zipfile.ZipFile(z) as zf:
|
| 422 |
+
zf.extractall(out)
|
| 423 |
+
image_dir = out
|
| 424 |
+
files = [p for p in Path(image_dir).rglob("*") if p.suffix.lower() in exts and not p.name.startswith("._")]
|
| 425 |
+
return sorted(files)
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
def _load_square(p):
|
| 429 |
+
try:
|
| 430 |
+
im = Image.open(p).convert("RGB")
|
| 431 |
+
except Exception:
|
| 432 |
+
return None
|
| 433 |
+
w, h = im.size
|
| 434 |
+
s = min(w, h)
|
| 435 |
+
l, t = (w - s) // 2, (h - s) // 2
|
| 436 |
+
im = im.crop((l, t, l + s, t + s)).resize((RES, RES), Image.LANCZOS)
|
| 437 |
+
return np.asarray(im, dtype=np.uint8)
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
def cmd_prepare(args):
|
| 441 |
+
from tqdm import tqdm
|
| 442 |
+
from collections import Counter
|
| 443 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 444 |
+
from transformers import CLIPVisionModelWithProjection
|
| 445 |
+
|
| 446 |
+
DATA.mkdir(parents=True, exist_ok=True)
|
| 447 |
+
dev = get_device()
|
| 448 |
+
|
| 449 |
+
files = collect_files(args.image_dir)
|
| 450 |
+
print(f"{len(files)} dosya bulundu, {RES}x{RES} boyutuna getiriliyor...")
|
| 451 |
+
with ThreadPoolExecutor(max_workers=os.cpu_count() or 4) as ex:
|
| 452 |
+
arr = [a for a in tqdm(ex.map(_load_square, files), total=len(files)) if a is not None]
|
| 453 |
+
if not arr:
|
| 454 |
+
raise SystemExit("Hiç görsel yüklenemedi. --image_dir ile klasör ver.")
|
| 455 |
+
images = np.stack(arr) # (N,H,W,3) uint8
|
| 456 |
+
N = len(images)
|
| 457 |
+
print("Toplam görsel:", N)
|
| 458 |
+
|
| 459 |
+
# --- CLIP zero-shot ile pseudo-caption (görsel encoder TPU'da, sabit batch)
|
| 460 |
+
tok, txt = load_clip_text() # metin tarafı CPU'da (çok küçük iş)
|
| 461 |
+
vis = CLIPVisionModelWithProjection.from_pretrained(CLIP_ID).to(dev).eval()
|
| 462 |
+
mean = torch.tensor([0.48145466, 0.4578275, 0.40821073]).view(1, 3, 1, 1)
|
| 463 |
+
std = torch.tensor([0.26862954, 0.26130258, 0.27577711]).view(1, 3, 1, 1)
|
| 464 |
+
BS = 256
|
| 465 |
+
feats = []
|
| 466 |
+
with torch.no_grad():
|
| 467 |
+
for i in tqdm(range(0, N, BS), desc="CLIP"):
|
| 468 |
+
x = torch.from_numpy(images[i:i + BS]).permute(0, 3, 1, 2).float() / 255.0
|
| 469 |
+
n = x.shape[0]
|
| 470 |
+
x = F.interpolate(x, size=224, mode="bicubic", align_corners=False).clamp(0, 1)
|
| 471 |
+
x = (x - mean) / std
|
| 472 |
+
if n < BS: # statik şekil -> tek derleme
|
| 473 |
+
x = torch.cat([x, x.new_zeros(BS - n, *x.shape[1:])])
|
| 474 |
+
f = vis(pixel_values=x.to(dev)).image_embeds
|
| 475 |
+
feats.append(F.normalize(f.float(), dim=-1)[:n].cpu())
|
| 476 |
+
feats = torch.cat(feats) # (N,512) CPU
|
| 477 |
+
del vis
|
| 478 |
+
|
| 479 |
+
@torch.no_grad()
|
| 480 |
+
def classify(prompts):
|
| 481 |
+
t = tok(prompts, padding=True, return_tensors="pt")
|
| 482 |
+
te = F.normalize(txt(**t).text_embeds, dim=-1)
|
| 483 |
+
return (feats @ te.T).argmax(1).numpy()
|
| 484 |
+
|
| 485 |
+
hair_c = classify([f"an anime character with {c} hair" for c in HAIR_COLORS])
|
| 486 |
+
eye_c = classify([f"an anime character with {c} eyes" for c in EYE_COLORS])
|
| 487 |
+
hair_l = classify([f"an anime character with {c} hair" for c in HAIR_LEN])
|
| 488 |
+
smile = classify(["an anime face with a smile", "an anime face with a neutral expression"])
|
| 489 |
+
|
| 490 |
+
for name, a, names in [("saç rengi", hair_c, HAIR_COLORS), ("göz rengi", eye_c, EYE_COLORS),
|
| 491 |
+
("saç uzunluğu", hair_l, HAIR_LEN), ("gülümseme", smile, ["gülüyor", "nötr"])]:
|
| 492 |
+
cnt = Counter(a.tolist())
|
| 493 |
+
print(name, {names[k]: v for k, v in sorted(cnt.items())})
|
| 494 |
+
|
| 495 |
+
caps = []
|
| 496 |
+
for i in range(N):
|
| 497 |
+
c = f"anime face, {HAIR_COLORS[hair_c[i]]} {HAIR_LEN[hair_l[i]]} hair, {EYE_COLORS[eye_c[i]]} eyes"
|
| 498 |
+
if smile[i] == 0:
|
| 499 |
+
c += ", smiling"
|
| 500 |
+
caps.append(c)
|
| 501 |
+
|
| 502 |
+
uniq = sorted(set(caps))
|
| 503 |
+
captions = [""] + uniq # index 0 = boş caption (CFG için null)
|
| 504 |
+
cmap = {c: i for i, c in enumerate(captions)}
|
| 505 |
+
labels = np.array([cmap[c] for c in caps], dtype=np.int64)
|
| 506 |
+
|
| 507 |
+
emb = torch.cat([encode_prompts(tok, txt, captions[i:i + 128]).half()
|
| 508 |
+
for i in range(0, len(captions), 128)])
|
| 509 |
+
|
| 510 |
+
np.save(DATA / "images.npy", images)
|
| 511 |
+
np.save(DATA / "labels.npy", labels)
|
| 512 |
+
torch.save({"emb": emb, "captions": captions}, DATA / "text_emb.pt")
|
| 513 |
+
|
| 514 |
+
prev = torch.from_numpy(images[:32]).permute(0, 3, 1, 2).float() / 255.0
|
| 515 |
+
save_grid(prev, WORK / "preview_labels.png", nrow=8)
|
| 516 |
+
print(f"\n{len(captions) - 1} benzersiz caption. İlk 32 görselin captionları (8'erli satırlar, preview_labels.png ile karşılaştır):")
|
| 517 |
+
for i in range(32):
|
| 518 |
+
print(i, "|", caps[i])
|
| 519 |
+
print("\nHazır ->", DATA)
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
# ------------------------------------------------------------------ 2) EĞİTİM
|
| 523 |
+
def save_train_state(path, unet, ema, opt, step):
|
| 524 |
+
state = {"unet": {k: v.detach().cpu() for k, v in unet.state_dict().items()},
|
| 525 |
+
"ema": [s.detach().cpu() for s in ema.shadow], "ema_n": float(ema.n.item()),
|
| 526 |
+
"opt": opt.state_dict(), "step": step}
|
| 527 |
+
tmp = CKPT / "train_state.tmp"
|
| 528 |
+
torch.save(state, tmp)
|
| 529 |
+
os.replace(tmp, path)
|
| 530 |
+
|
| 531 |
+
|
| 532 |
+
def export_ema(ema, base_ch, path):
|
| 533 |
+
"""EMA ağırlıklarını CPU'da diffusers formatında (save_pretrained) yaz."""
|
| 534 |
+
m = build_unet(base_ch, attn="sdpa")
|
| 535 |
+
with torch.no_grad():
|
| 536 |
+
for p, s in zip(m.parameters(), ema.shadow):
|
| 537 |
+
p.copy_(s.detach().cpu())
|
| 538 |
+
m.save_pretrained(str(path))
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
def cmd_train(args):
|
| 542 |
+
from diffusers import DDPMScheduler
|
| 543 |
+
|
| 544 |
+
dev = get_device()
|
| 545 |
+
seed_all(SEED)
|
| 546 |
+
|
| 547 |
+
# tüm veri HBM'de (uint8 NCHW, ~260MB)
|
| 548 |
+
images = torch.from_numpy(np.load(DATA / "images.npy")).permute(0, 3, 1, 2).contiguous().to(dev)
|
| 549 |
+
labels = torch.from_numpy(np.load(DATA / "labels.npy")).long().to(dev)
|
| 550 |
+
te = torch.load(DATA / "text_emb.pt", weights_only=False)
|
| 551 |
+
text_emb = te["emb"].float().to(dev) # (K,L,512) fp32
|
| 552 |
+
captions = te["captions"]
|
| 553 |
+
null = text_emb[0] # (L,512) boş caption
|
| 554 |
+
N = images.shape[0]
|
| 555 |
+
|
| 556 |
+
unet = build_unet(args.base_ch, args.attn).to(dev)
|
| 557 |
+
params = [p for p in unet.parameters() if p.requires_grad]
|
| 558 |
+
nparam = sum(p.numel() for p in params)
|
| 559 |
+
print(f"cihaz: {dev} | UNet: {nparam / 1e6:.2f}M param | görsel: {N} | batch: {args.batch} "
|
| 560 |
+
f"| lr: {args.lr} | attn: {args.attn} | bf16 autocast")
|
| 561 |
+
|
| 562 |
+
ck, start_step = None, 0
|
| 563 |
+
if args.resume:
|
| 564 |
+
ck = torch.load(args.resume, map_location="cpu", weights_only=False)
|
| 565 |
+
unet.load_state_dict(ck["unet"])
|
| 566 |
+
start_step = ck["step"]
|
| 567 |
+
print("Devam ediliyor, adım:", start_step)
|
| 568 |
+
|
| 569 |
+
opt = DeviceAdamW(params, dev, betas=(0.9, 0.99), eps=1e-6, weight_decay=0.01)
|
| 570 |
+
if ck is not None:
|
| 571 |
+
if isinstance(ck.get("opt"), dict) and "m" in ck["opt"]:
|
| 572 |
+
opt.load_state_dict(ck["opt"])
|
| 573 |
+
else:
|
| 574 |
+
print("Uyarı: checkpoint eski (GPU) formatında, optimizer durumu sıfırdan başlıyor.")
|
| 575 |
+
|
| 576 |
+
ema = EMA(params, dev, decay=0.9995)
|
| 577 |
+
if ck is not None:
|
| 578 |
+
for s, p in zip(ema.shadow, ck["ema"]):
|
| 579 |
+
s.copy_(p.to(dev))
|
| 580 |
+
ema.n.fill_(float(ck.get("ema_n", 0)))
|
| 581 |
+
ema_model = build_unet(args.base_ch, args.attn).to(dev).eval()
|
| 582 |
+
|
| 583 |
+
ac = DDPMScheduler(num_train_timesteps=T_STEPS, beta_schedule="squaredcos_cap_v2").alphas_cumprod.float().to(dev)
|
| 584 |
+
snr = ac / (1.0 - ac)
|
| 585 |
+
minsnr_w = torch.clamp(snr, max=MIN_SNR_GAMMA) / snr
|
| 586 |
+
|
| 587 |
+
# periyodik örnekler için en sık geçen 6 caption
|
| 588 |
+
freq = np.bincount(labels.cpu().numpy(), minlength=len(captions))
|
| 589 |
+
top = [int(i) for i in np.argsort(-freq) if i != 0][:6]
|
| 590 |
+
sample_cond = text_emb[torch.tensor(top, device=dev)].repeat_interleave(6, dim=0) # 6 prompt x 6 örnek
|
| 591 |
+
OUT.mkdir(parents=True, exist_ok=True)
|
| 592 |
+
CKPT.mkdir(parents=True, exist_ok=True)
|
| 593 |
+
(OUT / "sample_prompts.txt").write_text("\n".join(captions[i] for i in top))
|
| 594 |
+
|
| 595 |
+
seed_all(SEED + start_step)
|
| 596 |
+
B = args.batch
|
| 597 |
+
step = start_step
|
| 598 |
+
step_t = torch.tensor(float(start_step), device=dev)
|
| 599 |
+
run = torch.zeros((), device=dev)
|
| 600 |
+
gn_sum = torch.zeros((), device=dev)
|
| 601 |
+
gn_max = torch.zeros((), device=dev)
|
| 602 |
+
n_bad = torch.zeros((), device=dev)
|
| 603 |
+
unet.train()
|
| 604 |
+
|
| 605 |
+
print("İlk adımda XLA derlemesi yapılır (birkaç dakika sürebilir; önbellekten sonraki çalıştırmalarda hızlıdır)...")
|
| 606 |
+
t_start = time.time()
|
| 607 |
+
t_log = time.time()
|
| 608 |
+
|
| 609 |
+
while step < args.steps:
|
| 610 |
+
# ---- batch (hepsi cihazda)
|
| 611 |
+
idx = torch.randint(0, N, (B,), device=dev)
|
| 612 |
+
x0 = images[idx].float().div_(127.5).sub_(1.0)
|
| 613 |
+
flip = torch.rand(B, device=dev) < 0.5
|
| 614 |
+
x0 = torch.where(flip[:, None, None, None], x0.flip(3), x0)
|
| 615 |
+
|
| 616 |
+
cond = text_emb[labels[idx]]
|
| 617 |
+
drop = torch.rand(B, device=dev) < P_UNCOND
|
| 618 |
+
cond = torch.where(drop[:, None, None], null[None], cond)
|
| 619 |
+
|
| 620 |
+
noise = torch.randn_like(x0)
|
| 621 |
+
t = torch.randint(0, T_STEPS, (B,), device=dev)
|
| 622 |
+
a_t = ac[t].sqrt()[:, None, None, None]
|
| 623 |
+
s_t = (1.0 - ac[t]).sqrt()[:, None, None, None]
|
| 624 |
+
xt = a_t * x0 + s_t * noise
|
| 625 |
+
|
| 626 |
+
# ---- ileri / geri
|
| 627 |
+
with amp(dev):
|
| 628 |
+
pred = unet(xt, t, encoder_hidden_states=cond).sample
|
| 629 |
+
loss = (((pred.float() - noise) ** 2).mean(dim=(1, 2, 3)) * minsnr_w[t]).mean()
|
| 630 |
+
loss.backward()
|
| 631 |
+
|
| 632 |
+
# ---- optimizer (cihazda, senkronizasyonsuz)
|
| 633 |
+
gn = clip_grads(params, 1.0)
|
| 634 |
+
ok = torch.isfinite(gn) & torch.isfinite(loss)
|
| 635 |
+
lr = lr_tensor(step_t, args.lr, args.steps)
|
| 636 |
+
opt.step(lr, ok)
|
| 637 |
+
opt.zero_grad()
|
| 638 |
+
ema.update(params)
|
| 639 |
+
|
| 640 |
+
# ---- istatistikler (cihazda birikir)
|
| 641 |
+
okf = ok.float()
|
| 642 |
+
run += torch.where(ok, loss.detach(), torch.zeros_like(loss))
|
| 643 |
+
gn_ok = torch.where(ok, gn, torch.zeros_like(gn))
|
| 644 |
+
gn_sum += gn_ok
|
| 645 |
+
gn_max = torch.maximum(gn_max, gn_ok)
|
| 646 |
+
n_bad += 1.0 - okf
|
| 647 |
+
step_t += 1.0
|
| 648 |
+
|
| 649 |
+
sync() # adım sınırı: grafiği gönder, host bir sonraki adımı izlemeye devam eder
|
| 650 |
+
step += 1
|
| 651 |
+
|
| 652 |
+
if step == start_step + 1:
|
| 653 |
+
loss.item() # ilk derlemenin bitmesini bekle, süreyi göster
|
| 654 |
+
print(f"ilk adım (derleme dahil): {time.time() - t_start:.1f} sn")
|
| 655 |
+
t_log = time.time()
|
| 656 |
+
|
| 657 |
+
if step % LOG_EVERY == 0:
|
| 658 |
+
bad = int(n_bad.item())
|
| 659 |
+
good = max(1, LOG_EVERY - bad)
|
| 660 |
+
avg = run.item() / good
|
| 661 |
+
gn_avg = gn_sum.item() / good
|
| 662 |
+
ips = LOG_EVERY / (time.time() - t_log)
|
| 663 |
+
t_log = time.time()
|
| 664 |
+
print(f"step {step}/{args.steps} | loss {avg:.4f} | lr {lr.item():.2e} | {ips:.2f} it/s "
|
| 665 |
+
f"| {(time.time() - t_start) / 60:.1f} dk | gn {gn_avg:.2f} (maks {gn_max.item():.1f}) "
|
| 666 |
+
f"| atlanan {bad}")
|
| 667 |
+
run.zero_(); gn_sum.zero_(); gn_max.zero_(); n_bad.zero_()
|
| 668 |
+
if bad >= LOG_EVERY:
|
| 669 |
+
print("Tüm adımlar NaN/inf! --lr düşür veya --resume ile son sağlam checkpoint'ten devam et.")
|
| 670 |
+
break
|
| 671 |
+
if step - start_step >= 1000 and avg > args.diverge:
|
| 672 |
+
print(f"Loss {avg:.3f} > {args.diverge}: model çöktü, duruyorum. "
|
| 673 |
+
f"Son sağlam checkpoint'ten daha düşük --lr ile --resume yap.")
|
| 674 |
+
break
|
| 675 |
+
|
| 676 |
+
if step % args.sample_every == 0 or step == args.steps:
|
| 677 |
+
ema.copy_to(ema_model)
|
| 678 |
+
export_ema(ema, args.base_ch, CKPT / "ema_unet")
|
| 679 |
+
if step % args.ckpt_every == 0 or step == args.steps:
|
| 680 |
+
save_train_state(CKPT / f"train_state_{step}.pt", unet, ema, opt, step)
|
| 681 |
+
imgs = generate(ema_model, sample_cond, null[None], steps=args.sample_steps, cfg=4.0, seed=0)
|
| 682 |
+
save_grid(imgs, OUT / f"step_{step:06d}.png", nrow=6)
|
| 683 |
+
print(f"[kaydedildi] step {step} -> {OUT / f'step_{step:06d}.png'}")
|
| 684 |
+
t_log = time.time()
|
| 685 |
+
if (time.time() - t_start) / 3600.0 > args.max_hours:
|
| 686 |
+
print(f"--max_hours ({args.max_hours} sa) doldu, durduruluyor. --resume ile devam edebilirsin.")
|
| 687 |
+
break
|
| 688 |
+
|
| 689 |
+
print("Bitti. Model:", CKPT / "ema_unet")
|
| 690 |
+
|
| 691 |
+
|
| 692 |
+
# ------------------------------------------------------------------ 3) ÖRNEK ÜRETME
|
| 693 |
+
def cmd_sample(args):
|
| 694 |
+
from diffusers import UNet2DConditionModel
|
| 695 |
+
dev = get_device()
|
| 696 |
+
unet = set_attn(UNet2DConditionModel.from_pretrained(str(CKPT / "ema_unet")), args.attn).to(dev).eval()
|
| 697 |
+
tok, txt = load_clip_text()
|
| 698 |
+
cond = encode_prompts(tok, txt, args.prompt).repeat_interleave(args.n, dim=0).to(dev)
|
| 699 |
+
null = encode_prompts(tok, txt, [""]).to(dev)
|
| 700 |
+
imgs = generate(unet, cond, null, steps=args.steps, cfg=args.cfg, seed=args.seed)
|
| 701 |
+
OUT.mkdir(parents=True, exist_ok=True)
|
| 702 |
+
path = OUT / f"sample_{int(time.time())}.png"
|
| 703 |
+
save_grid(imgs, path, nrow=args.n)
|
| 704 |
+
print("Kaydedildi:", path, "| satırlar =", args.prompt)
|
| 705 |
+
|
| 706 |
+
|
| 707 |
+
# ------------------------------------------------------------------ CLI
|
| 708 |
+
if __name__ == "__main__":
|
| 709 |
+
ap = argparse.ArgumentParser()
|
| 710 |
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
| 711 |
+
|
| 712 |
+
p1 = sub.add_parser("prepare")
|
| 713 |
+
p1.add_argument("--image_dir", default=None, help="Boşsa huggan/anime-faces indirilir")
|
| 714 |
+
|
| 715 |
+
p2 = sub.add_parser("train")
|
| 716 |
+
p2.add_argument("--steps", type=int, default=40000)
|
| 717 |
+
p2.add_argument("--batch", type=int, default=128, help="Batch (TPU v5e-1 tek çip). OOM olursa 64; rahatsa 256")
|
| 718 |
+
p2.add_argument("--lr", type=float, default=1e-4)
|
| 719 |
+
p2.add_argument("--base_ch", type=int, default=64, help="UNet taban kanal (64 -> ~17M, 128 -> MXU dostu ~4x param)")
|
| 720 |
+
p2.add_argument("--attn", choices=["chunked", "sdpa"], default="chunked",
|
| 721 |
+
help="chunked: bellek-verimli özel self-attention; sdpa: standart (büyük batch'te OOM riski)")
|
| 722 |
+
p2.add_argument("--ckpt_every", type=int, default=2000)
|
| 723 |
+
p2.add_argument("--sample_every", type=int, default=500)
|
| 724 |
+
p2.add_argument("--sample_steps", type=int, default=50, help="Periyodik örneklerde DDIM adımı")
|
| 725 |
+
p2.add_argument("--max_hours", type=float, default=11.0)
|
| 726 |
+
p2.add_argument("--diverge", type=float, default=0.4, help="100 adım ort. loss bunu aşarsa dur")
|
| 727 |
+
p2.add_argument("--resume", default=None)
|
| 728 |
+
|
| 729 |
+
p3 = sub.add_parser("sample")
|
| 730 |
+
p3.add_argument("--prompt", nargs="+", default=["anime face, blue long hair, red eyes, smiling"])
|
| 731 |
+
p3.add_argument("--n", type=int, default=6, help="prompt başına görsel sayısı")
|
| 732 |
+
p3.add_argument("--cfg", type=float, default=4.0)
|
| 733 |
+
p3.add_argument("--steps", type=int, default=50)
|
| 734 |
+
p3.add_argument("--seed", type=int, default=0)
|
| 735 |
+
p3.add_argument("--attn", choices=["chunked", "sdpa"], default="chunked")
|
| 736 |
+
|
| 737 |
+
args = ap.parse_args()
|
| 738 |
+
init_xla_cache()
|
| 739 |
+
{"prepare": cmd_prepare, "train": cmd_train, "sample": cmd_sample}[args.cmd](args)
|