SykoSLM commited on
Commit
dccdf0d
·
verified ·
1 Parent(s): 4df5974

Upload anime_t2i_tpu.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. 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)