ra / src /ae_sdxs.py
recoilme's picture
Ra fp16 (step 652000)
8096951 verified
Raw History Blame Contribute Delete
3.54 kB
"""Адаптер на сторонний VAE (AsymmetricAutoencoderKL из sdxs-micro) под API нашего AutoEncoder.
Что адаптер прячет от остального кода:
* нормировка латента своя — `latents_mean`/`latents_std` из vae_sdxs/config.json (≈ -0.04 / 1.58),
а не наши ≈ -4.1 / 1.03; снаружи всё так же: encode(x) → нормированный латент,
`to_raw()` в модели возвращает сырой;
* декодер асимметричный: энкодер f8, декодер f16, то есть латент 32×32 отдаёт картинку 512²
(вдвое больше входа) — это и есть смысл этого VAE;
* нет «хвоста» — поточного пиксельного декодера, поэтому пиксельный лосс при обучении не считается
(`--pix-weight 0`), а сэмплы декодируются один раз встроенным декодером VAE.
API повторяет `vae/ae/model.py::AutoEncoder` в той части, которую использует train.py/sample.py:
`encode`, `decode_raw`, `latent_mean`, `latent_std`, `config.latent_channels`, `config.patch_size`,
`compute_dtype`, `.eval()`, `.to(device)`.
"""
from __future__ import annotations
import torch
import torch.nn as nn
PATCH = 8 # энкодер сжимает в 8 раз; декодер потом растягивает в 16 → 2× на выходе
class SdxsAE(nn.Module):
def __init__(self, vae, device: str = "cuda"):
super().__init__()
self.vae = vae
self.device_name = device
self.compute_dtype = torch.float32
cfg = vae.config
mean = torch.tensor(getattr(cfg, "latents_mean"), dtype=torch.float32)
std = torch.tensor(getattr(cfg, "latents_std"), dtype=torch.float32)
self.register_buffer("latent_mean", mean.view(1, -1, 1, 1))
self.register_buffer("latent_std", std.view(1, -1, 1, 1))
self.config = type("Cfg", (), {"latent_channels": int(cfg.latent_channels),
"patch_size": PATCH})()
@classmethod
def from_pretrained(cls, path, device: str = "cuda"):
from diffusers import AsymmetricAutoencoderKL
vae = AsymmetricAutoencoderKL.from_pretrained(str(path), torch_dtype=torch.float32)
vae = vae.to(device).eval()
vae.requires_grad_(False)
return cls(vae, device).to(device).eval()
@property
def device(self):
return self.latent_mean.device
def train(self, mode: bool = True):
super().train(mode)
self.vae.eval() # VAE заморожен всегда
return self
def encode(self, x: torch.Tensor) -> torch.Tensor:
"""x в -1..1 → нормированный латент (B, 32, H/8, W/8)."""
with torch.no_grad():
raw = self.vae.encode(x.to(self.compute_dtype)).latent_dist.mode()
return ((raw - self.latent_mean) / self.latent_std).to(torch.float32)
def decode_raw(self, z_raw: torch.Tensor, steps: int = 0, generator=None) -> torch.Tensor:
"""Сырой латент → картинка в -1..1. `steps`/`generator` — для совместимости с нашим API."""
with torch.no_grad():
return self.vae.decode(z_raw.to(self.compute_dtype), return_dict=False)[0].clamp(-1, 1)