Text-to-Image
Diffusers
Safetensors
flow-matching
pyramidal-unet
256x256
ettin-150m
from-scratch
ra
udit
Instructions to use AiArtLab/Ra with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use AiArtLab/Ra with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("AiArtLab/Ra", 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
Download src/export.py from AiArtLab/Ra: direct link, hf CLI and curl.
- Browser
- Download file 6.25 kB
-
https://huggingface.co/AiArtLab/Ra/resolve/main/src/export.py
- Command line
-
hf download hf://AiArtLab/Ra/src/export.py
-
curl -L -o export.py https://huggingface.co/AiArtLab/Ra/resolve/main/src/export.py
6.25 kB
| """Экспорт обученного UDM в `transformer/`: safetensors + config.json. | |
| Три части в одном файле, по префиксам: | |
| body.* — тело (8 блоков из энкодера AE + 4 новых), поток в латентном пространстве | |
| text_mixer.* — обучаемый выбор слоёв Ettin (SliceMixer) | |
| tail.* — декодер AE (рендер латента в пиксели), дообученный как обычный flow | |
| python3 src/export.py --ckpt tmp/run256/last.pt --out transformer | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| import torch | |
| from safetensors.torch import save_file | |
| ROOT = Path(__file__).resolve().parent.parent | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--ckpt", default=str(ROOT / "tmp" / "run256" / "last.pt")) | |
| ap.add_argument("--out", default=str(ROOT / "transformer")) | |
| ap.add_argument("--metrics", default="", help="дописать метрики в config.json (строка)") | |
| ap.add_argument("--half", action="store_true", help="хранить веса в fp16 (вдвое меньше, для публикации)") | |
| args = ap.parse_args() | |
| ck = torch.load(args.ckpt, map_location="cpu") | |
| tensors = {} | |
| for name, sd in (("body", ck["body"]), ("text_mixer", ck["mixer"]), ("tail", ck.get("decoder") or {})): | |
| for k, v in sd.items(): | |
| tensors[f"{name}.{k}"] = (v.half() if args.half else v).contiguous() | |
| out = Path(args.out) | |
| out.mkdir(parents=True, exist_ok=True) | |
| save_file(tensors, str(out / "diffusion_pytorch_model.safetensors")) | |
| ae_cfg = json.loads((ROOT / "vae" / "config.json").read_text()) | |
| ta = ck.get("args") or {} # чекпойнт помнит свои аргументы — не сочиняем заново | |
| training = { | |
| "dataset": ta.get("hf_dataset") or ta.get("data") or "~/dataset/butterflies_256x64", | |
| "parts": ta.get("parts"), | |
| "size": ta.get("size"), | |
| "steps": int(ck["step"]), | |
| "batch": ta.get("batch", 8), | |
| "accum": ta.get("accum", 1), | |
| "lr": ta.get("lr", 1e-4), | |
| "lr_tail": ta.get("lr_tail", 3e-5), | |
| "drop_text": ta.get("drop_text", 0.1), | |
| "init": ta.get("init"), | |
| "metrics": args.metrics, | |
| } | |
| cfg = { | |
| "_class_name": "UDiT", | |
| "name": "Ra", | |
| "format": "udm/v1", | |
| "step": int(ck["step"]), | |
| "latent": { | |
| "channels": ae_cfg["latent_channels"], | |
| "spatial_factor": ae_cfg["patch_size"], | |
| "mean": ae_cfg["latent_mean"], | |
| "std": ae_cfg["latent_std"], | |
| "source": "vae/ (Muinez/f8c32-ae)", | |
| }, | |
| "text": { | |
| "encoder": "text_encoder/ (jhu-clsp/ettin-encoder-150m, 768-dim, 22 слоя)", | |
| "max_len": 192, | |
| "slices": [2, 5, 8, 11, 14, 17, 20], | |
| "slice_blocks": 2, | |
| "trainable": "text_mixer.*", | |
| }, | |
| "body": { | |
| "kind": "пирамидальный U: ширина растёт к низу", | |
| "levels": [ | |
| {"level": 0, "tokens": "÷1", "width": 192, "blocks": "conv 6 enc / 4 dec + 1 self-attn на сторону", | |
| "text": "кросс-внимание в каждом conv-блоке и в self-attn"}, | |
| {"level": 1, "tokens": "÷2", "width": 512, "blocks": "attn 4 enc / 3 dec", "text": "кросс-внимание"}, | |
| {"level": 2, "tokens": "÷4", "width": 1024, "blocks": "attn 3 enc / 2 dec", "text": "кросс-внимание"}, | |
| {"level": 3, "tokens": "÷8", "width": 1536, "blocks": "attn 2 (боттлнек)", "text": "кросс-внимание"}, | |
| ], | |
| "skips": "аддитивные (SD-стиль): e0/e1/e2 прибавляются в декодере", | |
| "down": "mean-pool 2× + Linear", | |
| "up": "Linear + nearest 2×", | |
| "rope": "2D RoPE, base 100, фаза в латентных пикселях на каждом уровне", | |
| "parametrization": "x0 латента", | |
| }, | |
| "tail": { | |
| "source": "декодер AE, 6 блоков @512, PixelShuffle ×3", | |
| "trained_as": "flow по пикселям, условие z0 + (ошибка тела).detach()", | |
| "sample_steps": 4, | |
| }, | |
| "params": { | |
| "body": round(sum(v.numel() for k, v in tensors.items() if k.startswith("body.")) / 1e6, 2), | |
| "text_mixer": round(sum(v.numel() for k, v in tensors.items() | |
| if k.startswith("text_mixer.")) / 1e6, 2), | |
| "tail": round(sum(v.numel() for k, v in tensors.items() if k.startswith("tail.")) / 1e6, 2), | |
| }, | |
| "training": {k: v for k, v in training.items() if v is not None}, | |
| "storage_dtype": "float16" if args.half else "float32", | |
| } | |
| cfg["params"]["total"] = round(sum(cfg["params"].values()), 2) | |
| (out / "config.json").write_text(json.dumps(cfg, indent=2, ensure_ascii=False)) | |
| print(f"сохранено: {out}/diffusion_pytorch_model.safetensors " | |
| f"({sum(v.numel() for v in tensors.values())/1e6:.1f}M параметров, " | |
| f"{(out / 'diffusion_pytorch_model.safetensors').stat().st_size/1e6:.0f} МБ)") | |
| print(f"части: {cfg['params']}") | |
| def load_exported(model, enc, ae, path, device=None): | |
| """Загрузить из `transformer/` (config.json + safetensors с префиксами body./text_mixer./tail.).""" | |
| import json | |
| from safetensors.torch import load_file | |
| path = Path(path) | |
| sd = load_file(str(path / "diffusion_pytorch_model.safetensors"), device=str(device) if device else None) | |
| def sub(prefix): | |
| return {k[len(prefix) + 1:]: v for k, v in sd.items() if k.startswith(prefix + ".")} | |
| model.body.load_state_dict(sub("body"), strict=False) | |
| enc.mixer.load_state_dict(sub("text_mixer")) | |
| ae.decoder.load_state_dict(sub("tail")) | |
| cfg_path = path / "config.json" | |
| return json.loads(cfg_path.read_text()) if cfg_path.exists() else {} | |
| if __name__ == "__main__": | |
| main() | |