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
Ra fp16 (step 212000)
Browse files- .gitattributes +5 -31
- .gitignore +25 -0
- docs/ARCH.md +79 -0
- docs/README.ru.md +90 -0
- docs/STATUS.md +46 -0
- docs/img/samples.png +3 -0
- docs/img/samples_butterflies_20k.png +3 -0
- docs/img/strip.png +3 -0
- docs/presentation.pdf +3 -0
- docs/presentation.src.html +618 -0
- src/export.py +127 -0
- src/make_dataset.py +94 -0
- src/make_dataset_people.py +174 -0
- src/model.py +270 -0
- src/publish.py +98 -0
- src/sample.py +75 -0
- src/text.py +132 -0
- src/train.py +524 -0
- text_encoder/.gitattributes +35 -0
- text_encoder/README.md +971 -0
- text_encoder/config.json +46 -0
- text_encoder/model.safetensors +3 -0
- text_encoder/special_tokens_map.json +37 -0
- text_encoder/tokenizer.json +0 -0
- text_encoder/tokenizer_config.json +944 -0
- transformer/config.json +157 -0
- transformer/diffusion_pytorch_model.safetensors +3 -0
- vae/.gitattributes +35 -0
- vae/LICENSE +202 -0
- vae/README.md +104 -0
- vae/ae/__init__.py +3 -0
- vae/ae/model.py +469 -0
- vae/ae/window_attention.py +129 -0
- vae/config.json +86 -0
- vae/example.py +29 -0
- vae/model.safetensors +3 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,9 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
|
| 27 |
-
*.
|
| 28 |
-
*.
|
| 29 |
-
*.
|
| 30 |
-
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
| 1 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 4 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
docs/presentation.pdf filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
|
| 5 |
+
# прогоны обучения, чекпойнты, сэмплы
|
| 6 |
+
tmp/
|
| 7 |
+
runs/
|
| 8 |
+
*.pt
|
| 9 |
+
nohup.out
|
| 10 |
+
*.log
|
| 11 |
+
|
| 12 |
+
# локальная постановка задачи (не для публикации)
|
| 13 |
+
task.md
|
| 14 |
+
|
| 15 |
+
# jupyter
|
| 16 |
+
.ipynb_checkpoints/
|
| 17 |
+
|
| 18 |
+
# кэши моделей и окружение
|
| 19 |
+
.hf_home/
|
| 20 |
+
.cache/
|
| 21 |
+
text_encoder/.cache/
|
| 22 |
+
.env
|
| 23 |
+
|
| 24 |
+
# системное
|
| 25 |
+
.DS_Store
|
docs/ARCH.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Архитектура
|
| 2 |
+
|
| 3 |
+
Три части, каждая со своей задачей. Всё, что ниже, проверено запуском.
|
| 4 |
+
|
| 5 |
+
## 1. Тело: пирамидальный U-трансформер
|
| 6 |
+
|
| 7 |
+
Принцип: **ширина растёт там, где токенов мало.** Стоимость слоя ~ `токены × C²`, поэтому на
|
| 8 |
+
высоком разрешении держим узкие свёрточные блоки, а ширину и полное внимание — внизу.
|
| 9 |
+
|
| 10 |
+
| уровень | токенов при 512² | ширина | блоки | механика |
|
| 11 |
+
|---|---|---|---|---|
|
| 12 |
+
| L0 ÷1 | 4096 (512²) / 1024 (256²) | **192** | 6 enc / 4 dec + 1 self-attn с каждой стороны | depthwise 3×3 + expand 4× (ConvNeXt-стиль), кросс-внимание к тексту в каждом блоке |
|
| 13 |
+
| L1 ÷2 | 1024 / 256 | **512** | 4 enc / 3 dec | трансформер: self-attn + cross-attn к тексту + FFN |
|
| 14 |
+
| L2 ÷4 | 256 / 64 | **1024** | 3 enc / 2 dec | то же, полное внимание дёшево |
|
| 15 |
+
| L3 ÷8 | 64 / 16 | **1536** | 2 (боттлнек) | то же; широкий низ = глобальные ограничения (симметрия, композиция) |
|
| 16 |
+
|
| 17 |
+
Переходы и связи:
|
| 18 |
+
|
| 19 |
+
- **вниз**: mean-pool 2×2 + `Linear`, вверх: `Linear` + nearest 2×;
|
| 20 |
+
- **скипы аддитивные** (SD-стиль): выходы `e0/e1/e2` прибавляются к декодеру того же уровня;
|
| 21 |
+
ширина на входе и выходе уровня совпадает, поэтому сложение без проекций;
|
| 22 |
+
- **RoPE на каждом уровне**, фаза в **латентных пикселях** (L1 сдвинут вдвое, L2 вчетверо), поэтому
|
| 23 |
+
одна фаза означает одно расстояние независимо от уровня;
|
| 24 |
+
- **модуляция**: общий вектор `t + pooled-текст` → `(shift, scale, gate)` на уровень; в conv-блоках
|
| 25 |
+
L0 пять чанков (три на conv-путь, два на вход кросс-внимания);
|
| 26 |
+
- **zero-init**: выход conv-блоков и FFN обнулён, поэтому новые ветки не ломают старт.
|
| 27 |
+
|
| 28 |
+
Размеры: тело **143.3M**. Шаг обучения 97 мс при bs8 на 256² (8.1 ГБ). Для сравнения, прежний
|
| 29 |
+
плоский стек ширины 1024 на всех уровнях: 196 мс и hf 0.18 против 0.54 у пирамиды (архитектура
|
| 30 |
+
удалена в `tmp/archive`).
|
| 31 |
+
|
| 32 |
+
## 2. Текст
|
| 33 |
+
|
| 34 |
+
- **Ettin-150M** (`text_encoder/`): ModernBERT, 768, 22 слоя, byte-level BPE, MIT, заморожен.
|
| 35 |
+
- **SliceMixer** (`src/text.py`, 10.04M обучаемых): 7 срезов (выходы после глобальных слоёв
|
| 36 |
+
2, 5, 8, 11, 14, 17, 20) → пер-срезный LayerNorm (нормы растут 52 → 548, без нормировки ранние
|
| 37 |
+
слои не влияют) → 2 блока attention **по оси срезов** → `Linear(7→1)` → zero-init добавка к
|
| 38 |
+
среднему по срезам. Идея из `moss/adapter`, где 0.8B + 158M голова заменили 9B-энкодер.
|
| 39 |
+
- Вход в тело: кросс-внимание на **всех** уровнях (общие ключи/значения на уровень) плюс
|
| 40 |
+
pooled-текст в модуляции.
|
| 41 |
+
|
| 42 |
+
## 3. Хвост
|
| 43 |
+
|
| 44 |
+
Декодер AE без изменений архитектуры: патчи 8×8, 6 блоков @512, adaLN с низкоранговыми дельтами,
|
| 45 |
+
long skip из блока n−2, финальный `PixelShuffle + conv3×3` ×3. 23.87M.
|
| 46 |
+
|
| 47 |
+
Обучается как обычный flow по пикселям: случайные `t`, случайный шум, цель — картинка, **условие —
|
| 48 |
+
`z0 + (ошибка тела).detach()`**. Условие без градиента: рендерер видит реальную ошибку тела, но не
|
| 49 |
+
может «научиться исправлять мусор» (именно это раньше увело его с 29.6 на 21.3 дБ).
|
| 50 |
+
|
| 51 |
+
## 4. Сэмплинг
|
| 52 |
+
|
| 53 |
+
```
|
| 54 |
+
z ~ N(0,1)
|
| 55 |
+
4–8 шагов Эйлера по телу: x0 = тело(z, t, текст); z ← z − (z − x0)/(t·N)
|
| 56 |
+
CFG 1.5 (выше 2 при недообученной безусловной ветви ухудшает латент)
|
| 57 |
+
1 + 3 шага декодера AE: изображение
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
При 256² сэмпл стоит ~35 мс на 4800-й карте; при 512² — примерно вчетверо дороже.
|
| 61 |
+
|
| 62 |
+
## 5. Бюджет
|
| 63 |
+
|
| 64 |
+
| часть | параметры |
|
| 65 |
+
|---|---|
|
| 66 |
+
| тело | 143.3M |
|
| 67 |
+
| выбор слоёв текста | 10.04M |
|
| 68 |
+
| хвост | 23.87M |
|
| 69 |
+
| **итого** | **177.3M** |
|
| 70 |
+
|
| 71 |
+
## 6. Что зафиксировано и почему
|
| 72 |
+
|
| 73 |
+
- **x0, а не velocity.** Смена параметризации тела не затрагивает декодер (это отдельный flow со
|
| 74 |
+
своей целью), но требует переучивания тела с нуля — поэтому оставлено как есть.
|
| 75 |
+
- **Текст не экономим.** Кросс-внимание на L0 добавило 2.5M параметров и 9 мс, `switch` вырос
|
| 76 |
+
0.43 → 0.78 при том же уровне деталей.
|
| 77 |
+
- **Никаких фиксированных шумов и клипов в обучении** — только стандартный flow-matching.
|
| 78 |
+
- **Удалено как тупиковое:** плоский 1024-стек, резидуальная U-ветка, Mix-FFN поверх плоского
|
| 79 |
+
стека, velocity-переключение посреди обучения, LPIPS в лоссе, кропы одной картинки вместо набора.
|
docs/README.ru.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ra (UDiT) — пирамидальный U-трансформер для text-to-image
|
| 2 |
+
|
| 3 |
+
Название модели — **Ra** (бог солнца, в честь пирамиды ширин 192 → 512 → 1024 → 1536);
|
| 4 |
+
`UDiT` — имя архитектуры в коде.
|
| 5 |
+
|
| 6 |
+
Одна модель из трёх частей: **тело** (пирамидальный U-трансформер, flow в латентном пространстве
|
| 7 |
+
AE), **хвост** (декодер AE, flow по пикселям) и **текст** (замороженный Ettin-150M плюс обучаемый
|
| 8 |
+
выбор слоёв). 177.3M параметров, обучение на 16 ГБ.
|
| 9 |
+
|
| 10 |
+
```
|
| 11 |
+
текст: Ettin-150M (768, 22 слоя, заморожен)
|
| 12 |
+
└─ SliceMixer: 7 слоёв → attention по оси срезов → Linear(7→1) [10.04M]
|
| 13 |
+
|
| 14 |
+
шум (32, h, w) ─► ТЕЛО, пирамида ширины 192 → 512 → 1024 → 1536 [143.3M]
|
| 15 |
+
L0 ÷1 conv + текст 6 enc / 4 dec 4096 токенов при 512², 1024 при 256²
|
| 16 |
+
L1 ÷2 трансформер 4 / 3 1024 при 512², 256 при 256²
|
| 17 |
+
L2 ÷4 трансформер 3 / 2 256 / 64
|
| 18 |
+
L3 ÷8 трансформер 2 64 / 16 ← широкий низ, полное внимание
|
| 19 |
+
скипы аддитивные, вниз mean-pool 2×+Linear, вверх Linear+nearest 2×
|
| 20 |
+
─► латент z (32, h, w) ─► ХВОСТ: декодер AE (6 блоков @512, PixelShuffle ×3) [23.87M]
|
| 21 |
+
flow по пикселям, условие z0 + (ошибка тела).detach()
|
| 22 |
+
─► картинка
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
## Измерено (RTX 4080 16 ГБ, 256², bs8)
|
| 26 |
+
|
| 27 |
+
| | |
|
| 28 |
+
|---|---|
|
| 29 |
+
| шаг обучения | **97 мс** (8.1 ГБ), 121 обновление на эпоху при 970 картинках |
|
| 30 |
+
| параметры | тело 143.3M + текст 10.04M + хвост 23.87M = **177.3M** |
|
| 31 |
+
| бабочки, 20k шагов | `mse_lat` 0.107 (low 0.022, high 0.187), `mse_pix` 0.0105, `switch` 0.78, `tail(z0)` 24.7 дБ |
|
| 32 |
+
| детали (hf, Paper Kite) | **0.54** против AE 0.859 (63%); у прежней плоской 1024-архитектуры было 0.18 |
|
| 33 |
+
| потолок AE (256², бабочки) | hf 0.859 при GT 0.913 |
|
| 34 |
+
|
| 35 |
+
Подробности архитектуры — `docs/ARCH.md`, состояние и планы — `docs/STATUS.md`.
|
| 36 |
+
|
| 37 |
+
## Состав
|
| 38 |
+
|
| 39 |
+
```
|
| 40 |
+
src/model.py UDiT: тело-пирамида + хвост + sample()
|
| 41 |
+
src/text.py Ettin + SliceMixer (выбор слоёв)
|
| 42 |
+
src/train.py обучение на папке «картинка + .txt»
|
| 43 |
+
src/make_dataset.py сборка набора из плоской папки (бакеты кратны 64)
|
| 44 |
+
src/make_dataset_people.py сборка набора по классам из allphoto_train (woman/man/cat)
|
| 45 |
+
src/export.py экспорт в transformer/ (safetensors + config.json)
|
| 46 |
+
vae/ AE f8c32-ae: энкодер 68.4M + декодер 23.9M, f8, 32 канала
|
| 47 |
+
text_encoder/ Ettin-150M (768, 22 слоя, MIT)
|
| 48 |
+
transformer/ текущие веса модели + config.json
|
| 49 |
+
tmp/ прогоны (samples, log.csv, last.pt); tmp/archive — старое
|
| 50 |
+
docs/ARCH.md, docs/STATUS.md
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
## Запуск
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
# данные: бакеты кратны 64 (тело трижды делит сетку на 2)
|
| 57 |
+
python3 src/make_dataset.py --src ~/dataset/butterflies/butterfly \
|
| 58 |
+
--out ~/dataset/butterflies_256x64 --long-side 256 --eval-n 30
|
| 59 |
+
|
| 60 |
+
# обучение (256², реальный батч 8, ~16 минут на 10k шагов)
|
| 61 |
+
python3 src/train.py --data ~/dataset/butterflies_256x64/train \
|
| 62 |
+
--eval ~/dataset/butterflies_256x64/eval --bs 8 --steps 20000 --every 500 --cfg 1.5
|
| 63 |
+
|
| 64 |
+
# продолжить с чекпойнта
|
| 65 |
+
python3 src/train.py ... --resume --out tmp/pyr2
|
| 66 |
+
|
| 67 |
+
# экспорт весов
|
| 68 |
+
python3 src/export.py --ckpt tmp/pyr2/last.pt --out transformer
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
Смотреть прогресс: `tmp/<run>/samples/latest.png` (панели по промптам), `tmp/<run>/log.csv`.
|
| 72 |
+
|
| 73 |
+
## Правила, выведенные из замеров
|
| 74 |
+
|
| 75 |
+
1. **Ширина растёт к низу** (192 / 512 / 1024 / 1536). Плоский стек ширины 1024 на всех уровнях
|
| 76 |
+
стоил 196 мс/шаг и дал hf 0.18; пирамида при 97 мс даёт 0.54.
|
| 77 |
+
2. **Текст — на всех уровнях**, включая самый мелкий: кросс-внимание в каждом conv-блоке L0 и
|
| 78 |
+
отдельный self-attn блок с текстом на L0. Стоило +2.5M параметров и +9 мс, `switch` вырос
|
| 79 |
+
0.43 → 0.78.
|
| 80 |
+
3. **Хвост учится на латенте `z0` плюс ошибка тела без градиента.** Обучение на предсказаниях тела
|
| 81 |
+
деградировало рендерер с 29.6 до 21.3 дБ; на чистом `z0` — рендерер не видит реальных ошибок.
|
| 82 |
+
4. **Бакеты кратны 64**: три даунсэмпла требуют латента, кратного 8 (`h % 8 == 0`, `w % 8 == 0`).
|
| 83 |
+
Аспект — центр-кроп, не растяжение; в `allphoto_train` чёрные поля резать (`trim_black`).
|
| 84 |
+
5. **Замер, а не впечатление.** В каждом логе: `mse_lat` (общий, low t<0.3, high t>0.7), `mse_pix`,
|
| 85 |
+
`switch` (один шум × два капшена), `tail(z0)` (сторож деградации хвоста), `skip` (не-конечные шаги).
|
| 86 |
+
Детали меряются долей высоких частот `hf(gen)/hf(AE.recon)`.
|
| 87 |
+
6. **Цель — `x0`**, не velocity: обосновано только тем, что так наследуются веса декодера AE; смена
|
| 88 |
+
параметризации тела на декодер не влияет.
|
| 89 |
+
7. **Никаких костылей в обучении** (фиксированный шум хвоста, клип латента, «mix» латентов):
|
| 90 |
+
всё, что влияет на результат, должно быть частью стандартного flow-matching.
|
docs/STATUS.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Состояние и планы
|
| 2 |
+
|
| 3 |
+
## Что работает (проверено)
|
| 4 |
+
|
| 5 |
+
- **Пайплайн целиком**: текст → латент → пиксели, обучение и сэмплинг. Baseline-метрики в каждом
|
| 6 |
+
логе: `mse_lat` (общий, low t<0.3, high t>0.7), `mse_pix`, `switch`, `tail(z0)`, `skip`.
|
| 7 |
+
- **Бабочки, 20 000 шагов** (`butterflies_256x64`, 970 train / 30 eval, 97 мс/шаг):
|
| 8 |
+
|
| 9 |
+
| шаги | hf (доля от AE 0.859) | mse_lat | low | high | switch |
|
| 10 |
+
|---|---|---|---|---|---|
|
| 11 |
+
| 4 000 | 0.446 (52%) | 0.21 | 0.031 | 0.42 | 0.43 |
|
| 12 |
+
| 10 000 | 0.436 (51%) | 0.164 | 0.024 | 0.322 | 0.68 |
|
| 13 |
+
| 20 000 | **0.540 (63%)** | 0.107 | 0.022 | 0.187 | 0.78 |
|
| 14 |
+
|
| 15 |
+
Глазами: узнаваемая бабочка с жилками и рисунком, два сида дают две разные особи.
|
| 16 |
+
`tail(z0)` 24.7 дБ (потолок AE на 256² — 0.859 по hf), `skip` = 0.
|
| 17 |
+
- **Потолок AE не мешает**: на 256² AE сохраняет 0.859 из 0.913 высоких частот GT.
|
| 18 |
+
|
| 19 |
+
## Чего ещё не сделано
|
| 20 |
+
|
| 21 |
+
1. **Плато на бабочках не найдено.** hf 0.436 → 0.540 за 10k шагов, лоссы падают; 30k шагов
|
| 22 |
+
покажут, где насыщение.
|
| 23 |
+
2. **Люди не обучены на этой архитектуре.** Готов билдер `src/make_dataset_people.py`
|
| 24 |
+
(5749 фото в `~/dataset/allphoto_train`, классы woman/man/cat, бакеты кратны 64).
|
| 25 |
+
Это главный тест: там капшены длинные и детальными, то есть деталь *определена условием*.
|
| 26 |
+
3. **512² не запускался** на пирамиде. Нужен набор с бакетами кратны 64 (512×384, 512×256, 384×512,
|
| 27 |
+
512×512) — `make_dataset.py --long-side 512 --multiple 64`. Ожидание: чешуйки/лица физически
|
| 28 |
+
разрешимы (бабочка 400 px вместо 200).
|
| 29 |
+
4. **`high` = 0.187** — «выбор особи» на высоком шуме. Падает с обучением (0.42 → 0.19), поэтому
|
| 30 |
+
причина — недообучение, а не только бедность капшена.
|
| 31 |
+
|
| 32 |
+
## Известные ограничения
|
| 33 |
+
|
| 34 |
+
- Латент AE — **f8** (64×64 при 512²). Всё мельче 8×8 px додумывает декодер, а не тело.
|
| 35 |
+
- Бакеты обязаны быть кратны **64** по каждой стороне (три даунсэмпла). При других размерах
|
| 36 |
+
даунсэмпл нецелый.
|
| 37 |
+
- Сравнение с прежними архитектурами корректно только на одинаковых наборах и шагах: старые
|
| 38 |
+
чекпойнты в новую архитектуру не переносятся (уровни и ширины другие).
|
| 39 |
+
|
| 40 |
+
## Порядок работ
|
| 41 |
+
|
| 42 |
+
1. Дожать бабочек до плато (30k) — 16 минут на 10k шагов.
|
| 43 |
+
2. Перейти на людей (5749) при 256²: 30k шагов ≈ 50 минут.
|
| 44 |
+
3. 512² на людях — после того, как на 256² будут лица/руки.
|
| 45 |
+
4. Ёмкость (ширина боттлнека 1536 → 2048, больше блоков L0) — только если данные и разрешение
|
| 46 |
+
уже не дают прироста.
|
docs/img/samples.png
ADDED
|
Git LFS Details
|
docs/img/samples_butterflies_20k.png
ADDED
|
Git LFS Details
|
docs/img/strip.png
ADDED
|
Git LFS Details
|
docs/presentation.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e25cf92a5db610c2ff0ed13334eef2d22df88ae2d0378a3642afb4ace9fe65f6
|
| 3 |
+
size 953712
|
docs/presentation.src.html
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="ru" data-register="technical">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<title>UDiT — пирамидальный U-трансформер</title>
|
| 6 |
+
<style>
|
| 7 |
+
@page { size: 1280px 720px; margin: 0; }
|
| 8 |
+
:root {
|
| 9 |
+
--paper: #f6f2ea; --ink: #14120f; --muted: #6d675c; --rule: #d9d2c3;
|
| 10 |
+
--copper: #a8551f; --teal: #1f6b66; --sand: #efe8da; --white: #fffdf8;
|
| 11 |
+
}
|
| 12 |
+
* { box-sizing: border-box; }
|
| 13 |
+
html, body { margin: 0; padding: 0; background: var(--paper); }
|
| 14 |
+
body { font-family: "DejaVu Sans", sans-serif; color: var(--ink); font-size: 15px; line-height: 1.45; }
|
| 15 |
+
.slide {
|
| 16 |
+
width: 1280px; height: 720px; padding: 54px 64px 44px; position: relative;
|
| 17 |
+
background: var(--paper); page-break-after: always; overflow: hidden;
|
| 18 |
+
}
|
| 19 |
+
.slide:last-child { page-break-after: auto; }
|
| 20 |
+
.eyebrow { font-family: "DejaVu Sans Mono", monospace; font-size: 11px; letter-spacing: .16em;
|
| 21 |
+
text-transform: uppercase; color: var(--copper); margin: 0 0 10px; }
|
| 22 |
+
h1 { font-family: 'P052', 'URW Bookman', serif; font-size: 46px; line-height: 1.08; margin: 0 0 14px; font-weight: 600; }
|
| 23 |
+
h2 { font-family: 'P052', 'URW Bookman', serif; font-size: 28px; line-height: 1.18; margin: 2px 0 8px; font-weight: 500; }
|
| 24 |
+
h3 { font-size: 14px; margin: 0 0 4px; letter-spacing: .02em; }
|
| 25 |
+
p { margin: 0 0 8px; }
|
| 26 |
+
.lede { font-size: 17px; color: #2c2822; max-width: 62ch; }
|
| 27 |
+
.muted { color: var(--muted); }
|
| 28 |
+
.mono { font-family: "DejaVu Sans Mono", monospace; font-variant-numeric: tabular-nums; }
|
| 29 |
+
.num { font-family: "DejaVu Sans Mono", monospace; font-weight: 700; }
|
| 30 |
+
.rule { height: 1px; background: var(--rule); margin: 18px 0 22px; }
|
| 31 |
+
.foot { position: absolute; left: 64px; right: 64px; bottom: 20px; display: flex;
|
| 32 |
+
justify-content: space-between; font-family: "DejaVu Sans Mono", monospace; font-size: 10.5px; color: var(--muted); }
|
| 33 |
+
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; }
|
| 34 |
+
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
|
| 35 |
+
.grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; }
|
| 36 |
+
.card { background: var(--white); border: 1px solid var(--rule); padding: 16px 18px; }
|
| 37 |
+
.kpi { display: flex; gap: 26px; flex-wrap: wrap; }
|
| 38 |
+
.kpi div { border-left: 2px solid var(--copper); padding-left: 12px; }
|
| 39 |
+
.kpi b { display: block; font-family: "DejaVu Sans Mono", monospace; font-size: 23px; font-weight: 700; }
|
| 40 |
+
.kpi span { font-size: 12.5px; color: var(--muted); }
|
| 41 |
+
table { border-collapse: collapse; width: 100%; font-size: 14px; }
|
| 42 |
+
th, td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--rule); }
|
| 43 |
+
th { font-family: "DejaVu Sans Mono", monospace; font-size: 10.5px; letter-spacing: .1em;
|
| 44 |
+
text-transform: uppercase; color: var(--muted); font-weight: 400; }
|
| 45 |
+
td.n, th.n { text-align: right; font-family: "DejaVu Sans Mono", monospace; font-variant-numeric: tabular-nums; }
|
| 46 |
+
.tag { display: inline-block; font-family: "DejaVu Sans Mono", monospace; font-size: 11px;
|
| 47 |
+
padding: 1px 7px; border: 1px solid var(--rule); color: var(--muted); background: var(--sand); }
|
| 48 |
+
img.strip { width: 100%; display: block; border: 1px solid var(--rule); }
|
| 49 |
+
.caption { font-size: 12.5px; color: var(--muted); margin-top: 7px; }
|
| 50 |
+
.big { font-family: 'P052', serif; font-size: 21px; line-height: 1.3; }
|
| 51 |
+
ul { margin: 0; padding-left: 18px; }
|
| 52 |
+
li { margin-bottom: 5px; }
|
| 53 |
+
.accent { color: var(--copper); }
|
| 54 |
+
.teal { color: var(--teal); }
|
| 55 |
+
svg text { font-family: "DejaVu Sans", sans-serif; }
|
| 56 |
+
.mono-label { font-family: "DejaVu Sans Mono", monospace; font-size: 12px; }
|
| 57 |
+
</style>
|
| 58 |
+
</head>
|
| 59 |
+
<body>
|
| 60 |
+
|
| 61 |
+
<!-- 1 ---------------------------------------------------------------- -->
|
| 62 |
+
<section class="slide">
|
| 63 |
+
<p class="eyebrow">text-to-image · 177.3M параметров · одна RTX 4080</p>
|
| 64 |
+
<h1>UDiT — пирамидальный U-трансформер</h1>
|
| 65 |
+
<p class="lede" style="max-width:88ch">Ширина растёт к низу: 192 → 512 → 1024 → 1536. Свёртки на мелком уровне,
|
| 66 |
+
внимание на грубом, текст на всех уровнях. Декодер готового автоэнкодера работает хвостом-рендерером,
|
| 67 |
+
поэтому тело считает 32 числа на клетку 8×8, а не пиксели.</p>
|
| 68 |
+
|
| 69 |
+
<div class="rule" style="margin:16px 0 14px"></div>
|
| 70 |
+
<div class="kpi">
|
| 71 |
+
<div><b>177.3M</b><span>параметров всего</span></div>
|
| 72 |
+
<div><b>97 мс</b><span>шаг обучения · bs8 · 256²</span></div>
|
| 73 |
+
<div><b>20.4</b><span>GFLOP forward у тела</span></div>
|
| 74 |
+
<div><b>0.647</b><span>детали (hf) против AE 0.859</span></div>
|
| 75 |
+
<div><b>8.1 ГБ</b><span>пик памяти из 16</span></div>
|
| 76 |
+
</div>
|
| 77 |
+
|
| 78 |
+
<img class="strip" src="img/strip.png" alt="оригинал, реконструкция AE и две генерации" style="margin-top:22px">
|
| 79 |
+
<p class="caption">слева направо: оригинал · реконструкция AE (потолок 0.859 hf) · две генерации модели на 30k шагов с разными сидами</p>
|
| 80 |
+
|
| 81 |
+
<div class="rule" style="margin:16px 0 10px"></div>
|
| 82 |
+
<div class="grid3">
|
| 83 |
+
<div><h3>тело · 143.3M</h3><p class="muted">пирамида 192/512/1024/1536, поток в латентном пространстве</p></div>
|
| 84 |
+
<div><h3>текст · 10.0M</h3><p class="muted">Ettin-150M заморожен, обучается только выбор слоёв</p></div>
|
| 85 |
+
<div><h3>хвост · 23.9M</h3><p class="muted">декодер AE, дообучается как flow по пикселям</p></div>
|
| 86 |
+
</div>
|
| 87 |
+
|
| 88 |
+
<div class="foot"><span>UDiT · архитектура</span><span>1 / 12</span></div>
|
| 89 |
+
</section>
|
| 90 |
+
|
| 91 |
+
<!-- 2 ---------------------------------------------------------------- -->
|
| 92 |
+
<section class="slide">
|
| 93 |
+
<p class="eyebrow">три части</p>
|
| 94 |
+
<h2>Модель не считает пиксели — только 32 числа на клетку 8×8</h2>
|
| 95 |
+
|
| 96 |
+
<svg width="1150" height="250" viewBox="0 0 1150 250" style="margin-top:16px">
|
| 97 |
+
<defs>
|
| 98 |
+
<marker id="a1" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto">
|
| 99 |
+
<path d="M0,0 L10,5 L0,10 z" fill="#14120f"/></marker>
|
| 100 |
+
<marker id="a2" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto">
|
| 101 |
+
<path d="M0,0 L10,5 L0,10 z" fill="#1f6b66"/></marker>
|
| 102 |
+
</defs>
|
| 103 |
+
|
| 104 |
+
<rect x="0" y="60" width="140" height="92" fill="#fffdf8" stroke="#d9d2c3"/>
|
| 105 |
+
<text x="14" y="90" font-size="15">шум</text>
|
| 106 |
+
<text x="14" y="114" class="mono-label" fill="#6d675c">32 × h/8 × w/8</text>
|
| 107 |
+
<text x="14" y="134" class="mono-label" fill="#6d675c">при 256²: 32×32</text>
|
| 108 |
+
|
| 109 |
+
<rect x="196" y="24" width="330" height="164" fill="#f2ece0" stroke="#a8551f"/>
|
| 110 |
+
<text x="212" y="54" font-size="16" font-weight="bold">ТЕЛО · пирамида</text>
|
| 111 |
+
<text x="212" y="80" class="mono-label" fill="#6d675c">143.3M · 20.4 GFLOP</text>
|
| 112 |
+
<text x="212" y="102" class="mono-label" fill="#6d675c">4–8 шагов Эйлера · CFG 1.5</text>
|
| 113 |
+
<rect x="212" y="116" width="298" height="12" fill="#c98a5f"/><text x="212" y="146" class="mono-label" fill="#7c3d14">÷1 · 192</text>
|
| 114 |
+
<rect x="212" y="150" width="220" height="12" fill="#a8551f"/><text x="440" y="161" class="mono-label" fill="#7c3d14">÷2 · 512</text>
|
| 115 |
+
<rect x="212" y="168" width="140" height="12" fill="#7c3d14"/><text x="360" y="179" class="mono-label" fill="#7c3d14">÷4 · 1024 · ÷8 · 1536</text>
|
| 116 |
+
|
| 117 |
+
<rect x="582" y="60" width="150" height="92" fill="#fffdf8" stroke="#d9d2c3"/>
|
| 118 |
+
<text x="596" y="90" font-size="15">латент</text>
|
| 119 |
+
<text x="596" y="114" class="mono-label" fill="#6d675c">32 × h/8 × w/8</text>
|
| 120 |
+
<text x="596" y="134" class="mono-label" fill="#6d675c">x0-предсказание</text>
|
| 121 |
+
|
| 122 |
+
<rect x="788" y="24" width="230" height="164" fill="#e8f0ee" stroke="#1f6b66"/>
|
| 123 |
+
<text x="802" y="54" font-size="16" font-weight="bold">ХВОСТ · декодер AE</text>
|
| 124 |
+
<text x="802" y="80" class="mono-label" fill="#6d675c">23.9M · 4 шага flow</text>
|
| 125 |
+
<text x="802" y="102" class="mono-label" fill="#6d675c">PixelShuffle ×3 → 3 канала</text>
|
| 126 |
+
<text x="802" y="124" class="mono-label" fill="#6d675c">условие: z0 + ошибка тела</text>
|
| 127 |
+
<text x="802" y="146" class="mono-label" fill="#6d675c">обучен заранее, как декодер AE</text>
|
| 128 |
+
<text x="802" y="168" class="mono-label" fill="#6d675c">дообучается вместе с телом</text>
|
| 129 |
+
|
| 130 |
+
<rect x="1074" y="60" width="76" height="92" fill="#fffdf8" stroke="#d9d2c3"/>
|
| 131 |
+
<text x="1084" y="90" font-size="15">px</text>
|
| 132 |
+
<text x="1084" y="114" class="mono-label" fill="#6d675c">3×H×W</text>
|
| 133 |
+
|
| 134 |
+
<line x1="144" y1="106" x2="192" y2="106" stroke="#14120f" stroke-width="1.8" marker-end="url(#a1)"/>
|
| 135 |
+
<line x1="530" y1="106" x2="578" y2="106" stroke="#14120f" stroke-width="1.8" marker-end="url(#a1)"/>
|
| 136 |
+
<line x1="736" y1="106" x2="784" y2="106" stroke="#14120f" stroke-width="1.8" marker-end="url(#a1)"/>
|
| 137 |
+
<line x1="1022" y1="106" x2="1070" y2="106" stroke="#14120f" stroke-width="1.8" marker-end="url(#a1)"/>
|
| 138 |
+
|
| 139 |
+
<rect x="196" y="210" width="822" height="38" fill="#fffdf8" stroke="#1f6b66"/>
|
| 140 |
+
<text x="210" y="234" font-size="13.5">ТЕКСТ · Ettin-150M (768, заморожен) + SliceMixer 10.04M · кросс-внимание в теле на всех четырёх уровнях</text>
|
| 141 |
+
<line x1="300" y1="208" x2="300" y2="192" stroke="#1f6b66" stroke-width="1.8" marker-end="url(#a2)"/>
|
| 142 |
+
</svg>
|
| 143 |
+
|
| 144 |
+
<div class="grid3" style="margin-top:14px">
|
| 145 |
+
<div><h3>Латент вместо пикселей</h3><p class="muted">Один токен = 32 числа на клетку 8×8. При 256² это 1024 токена против 196 608 пикселей.</p></div>
|
| 146 |
+
<div><h3>Декодер уже обучен</h3><p class="muted">Хвост наследует декодер AE: телу не нужно учиться рисовать текстуру с нуля.</p></div>
|
| 147 |
+
<div><h3>Текст не экономим</h3><p class="muted">Кросс-внимание в каждом блоке до самого мелкого уровня: +2.5M, «switch» 0.43 → 0.78.</p></div>
|
| 148 |
+
</div>
|
| 149 |
+
|
| 150 |
+
<div class="foot"><span>UDiT · три части</span><span>2 / 12</span></div>
|
| 151 |
+
</section>
|
| 152 |
+
|
| 153 |
+
<!-- 3 ---------------------------------------------------------------- -->
|
| 154 |
+
<section class="slide">
|
| 155 |
+
<p class="eyebrow">почему пирамида</p>
|
| 156 |
+
<h2>Плоский стек ширины 1024 платит за каждый токен по полной</h2>
|
| 157 |
+
|
| 158 |
+
<div class="grid2" style="margin-top:18px; gap:44px">
|
| 159 |
+
<div>
|
| 160 |
+
<svg width="530" height="212" viewBox="0 0 530 212">
|
| 161 |
+
<text x="0" y="12" font-size="13.5" font-weight="bold" fill="#a8551f">было: 12 блоков × ширина 1024</text>
|
| 162 |
+
<g fill="#e0b39a">
|
| 163 |
+
<rect x="0" y="26" width="360" height="9"/><rect x="0" y="39" width="360" height="9"/>
|
| 164 |
+
<rect x="0" y="52" width="360" height="9"/><rect x="0" y="65" width="360" height="9"/>
|
| 165 |
+
<rect x="0" y="78" width="360" height="9"/><rect x="0" y="91" width="360" height="9"/>
|
| 166 |
+
<rect x="0" y="104" width="360" height="9"/><rect x="0" y="117" width="360" height="9"/>
|
| 167 |
+
<rect x="0" y="130" width="360" height="9"/><rect x="0" y="143" width="360" height="9"/>
|
| 168 |
+
</g>
|
| 169 |
+
<line x1="372" y1="26" x2="372" y2="152" stroke="#a8551f"/>
|
| 170 |
+
<text x="380" y="70" class="mono-label" fill="#7c3d14">все блоки</text>
|
| 171 |
+
<text x="380" y="88" class="mono-label" fill="#7c3d14">на одном</text>
|
| 172 |
+
<text x="380" y="106" class="mono-label" fill="#7c3d14">разрешении</text>
|
| 173 |
+
<text x="0" y="176" class="mono-label" fill="#6d675c">токенов на блоке: 1024 · ширина: 1024 · уровней: 1</text>
|
| 174 |
+
<text x="0" y="196" class="mono-label" fill="#a8551f">232 GFLOP · 196 мс/шаг · hf 0.18</text>
|
| 175 |
+
<text x="0" y="212" class="mono-label" fill="#6d675c">нет уровня, где вся картинка — это 16 токенов</text>
|
| 176 |
+
</svg>
|
| 177 |
+
</div>
|
| 178 |
+
<div>
|
| 179 |
+
<svg width="530" height="212" viewBox="0 0 530 212">
|
| 180 |
+
<text x="0" y="12" font-size="13.5" font-weight="bold" fill="#1f6b66">стало: 192 → 512 → 1024 → 1536</text>
|
| 181 |
+
<rect x="0" y="26" width="400" height="26" fill="#c98a5f"/>
|
| 182 |
+
<text x="10" y="43" class="mono-label" fill="#fffdf8">÷1 · 1024 токена · ширина 192 · свёртки</text>
|
| 183 |
+
<rect x="60" y="58" width="310" height="26" fill="#b06f3f"/>
|
| 184 |
+
<text x="70" y="75" class="mono-label" fill="#fffdf8">÷2 · 256 токенов · 512</text>
|
| 185 |
+
<rect x="120" y="90" width="220" height="26" fill="#a8551f"/>
|
| 186 |
+
<text x="130" y="107" class="mono-label" fill="#fffdf8">÷4 · 64 · 1024</text>
|
| 187 |
+
<rect x="180" y="122" width="130" height="26" fill="#7c3d14"/>
|
| 188 |
+
<text x="190" y="139" class="mono-label" fill="#fffdf8">÷8 · 16 · 1536</text>
|
| 189 |
+
<text x="0" y="176" class="mono-label" fill="#6d675c">энкодер вверх, декодер вниз, аддитивный скип на каждом уровне</text>
|
| 190 |
+
<text x="0" y="196" class="mono-label" fill="#1f6b66">20.4 GFLOP · 97 мс/шаг · hf 0.54 → 0.65</text>
|
| 191 |
+
<text x="0" y="212" class="mono-label" fill="#6d675c">широкий низ: 16 токенов видят всю картинку целиком</text>
|
| 192 |
+
</svg>
|
| 193 |
+
</div>
|
| 194 |
+
</div>
|
| 195 |
+
|
| 196 |
+
<div class="rule" style="margin:6px 0 10px"></div>
|
| 197 |
+
<table>
|
| 198 |
+
<tr><th style="width:32%">метрика</th><th class="n" style="width:13%">плоский 1024</th><th class="n" style="width:12%">пирамида</th><th>что это значит</th></tr>
|
| 199 |
+
<tr><td>GFLOP на forward (тело)</td><td class="n">232</td><td class="n">20.4</td><td>в 11 раз меньше арифметики при том же числе параметров</td></tr>
|
| 200 |
+
<tr><td>шаг обучения, bs8 · 256²</td><td class="n">196 мс</td><td class="n">97 мс</td><td>вдвое больше шагов за то же время</td></tr>
|
| 201 |
+
<tr><td>детали: hf(gen)/hf(AE)</td><td class="n">21%</td><td class="n">51–75%</td><td>и это при 3–6 кратном меньшем числе шагов обучения</td></tr>
|
| 202 |
+
</table>
|
| 203 |
+
|
| 204 |
+
<div class="rule" style="margin:10px 0 12px"></div>
|
| 205 |
+
<p class="big">Стоимость слоя ≈ токены × ширина². Мы держим ширину там, где токенов мало, и снимаем
|
| 206 |
+
с мелкого уровня 28-кратный перерасход — эти же деньги уходят в глубину и разрешение.</p>
|
| 207 |
+
|
| 208 |
+
<div class="foot"><span>UDiT · сравнение</span><span>3 / 12</span></div>
|
| 209 |
+
</section>
|
| 210 |
+
|
| 211 |
+
<!-- 4 ---------------------------------------------------------------- -->
|
| 212 |
+
<section class="slide">
|
| 213 |
+
<p class="eyebrow">��естница</p>
|
| 214 |
+
<h2>Ширина растёт там, где токенов мало — стоимость уровней сбалансирована</h2>
|
| 215 |
+
|
| 216 |
+
<div class="grid2" style="margin-top:12px; grid-template-columns: 1.25fr 1fr;">
|
| 217 |
+
<div>
|
| 218 |
+
<table>
|
| 219 |
+
<tr><th>уровень</th><th class="n">токенов 256²</th><th class="n">токенов 512²</th><th class="n">ширина</th><th>блоки</th><th>механика</th></tr>
|
| 220 |
+
<tr><td>L0 ÷1</td><td class="n">1024</td><td class="n">4096</td><td class="n">192</td><td>6 enc / 4 dec + self-attn</td><td>свёртки + текст</td></tr>
|
| 221 |
+
<tr><td>L1 ÷2</td><td class="n">256</td><td class="n">1024</td><td class="n">512</td><td>4 / 3</td><td>трансформер</td></tr>
|
| 222 |
+
<tr><td>L2 ÷4</td><td class="n">64</td><td class="n">256</td><td class="n">1024</td><td>3 / 2</td><td>трансформер</td></tr>
|
| 223 |
+
<tr><td>L3 ÷8</td><td class="n">16</td><td class="n">64</td><td class="n">1536</td><td>2 (боттлнек)</td><td>трансформер</td></tr>
|
| 224 |
+
</table>
|
| 225 |
+
<p class="caption">энкодер поднимается по лестнице, декодер спускается; на каждом уровне прибавляется скип</p>
|
| 226 |
+
</div>
|
| 227 |
+
<div>
|
| 228 |
+
<svg width="470" height="290" viewBox="0 0 470 290">
|
| 229 |
+
<text x="0" y="14" font-size="12" fill="#6d675c">стоимость уровня ≈ токены × ширина²</text>
|
| 230 |
+
<g>
|
| 231 |
+
<rect x="0" y="30" width="240" height="26" fill="#c98a5f"/><text x="250" y="48" class="mono-label">37.7 · L0</text>
|
| 232 |
+
<rect x="0" y="66" width="427" height="26" fill="#b06f3f"/><text x="437" y="84" class="mono-label">67.1 · L1</text>
|
| 233 |
+
<rect x="0" y="102" width="427" height="26" fill="#a8551f"/><text x="437" y="120" class="mono-label">67.1 · L2</text>
|
| 234 |
+
<rect x="0" y="138" width="240" height="26" fill="#7c3d14"/><text x="250" y="156" class="mono-label">37.7 · L3</text>
|
| 235 |
+
</g>
|
| 236 |
+
<line x1="0" y1="182" x2="430" y2="182" stroke="#d9d2c3"/>
|
| 237 |
+
<text x="0" y="204" font-size="12" fill="#a8551f">для сравнения: 1024 на всех уровнях</text>
|
| 238 |
+
<rect x="0" y="214" width="470" height="26" fill="#e7c8b2"/>
|
| 239 |
+
<text x="8" y="232" class="mono-label" fill="#7c3d14">1073 · L0 при ширине 1024 (в 28 раз дороже)</text>
|
| 240 |
+
<text x="0" y="272" class="mono-label" fill="#6d675c">параметры уезжают вниз, где токенов 16, а не 1024</text>
|
| 241 |
+
</svg>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
|
| 245 |
+
<div class="foot"><span>UDiT · бюджет уровней</span><span>4 / 12</span></div>
|
| 246 |
+
</section>
|
| 247 |
+
|
| 248 |
+
<!-- 5 ---------------------------------------------------------------- -->
|
| 249 |
+
<section class="slide">
|
| 250 |
+
<p class="eyebrow">роли уровней</p>
|
| 251 |
+
<h2>Каждый уровень отвечает за свой масштаб — как у художника</h2>
|
| 252 |
+
|
| 253 |
+
<div class="grid4" style="margin-top:18px">
|
| 254 |
+
<div class="card">
|
| 255 |
+
<svg width="100%" height="96" viewBox="0 0 220 96">
|
| 256 |
+
<rect x="18" y="14" width="184" height="68" fill="none" stroke="#d9d2c3"/>
|
| 257 |
+
<circle cx="110" cy="48" r="26" fill="#a8551f" opacity="0.22"/>
|
| 258 |
+
<circle cx="110" cy="48" r="12" fill="#a8551f" opacity="0.55"/>
|
| 259 |
+
<text x="70" y="92" class="mono-label" fill="#6d675c">16 токенов</text>
|
| 260 |
+
</svg>
|
| 261 |
+
<h3>L3 · «прищур»</h3>
|
| 262 |
+
<p class="muted">Вся картинка — 16 токенов. Полное внимание: все видят всех. Здесь решается композиция и симметрия.</p>
|
| 263 |
+
</div>
|
| 264 |
+
<div class="card">
|
| 265 |
+
<svg width="100%" height="96" viewBox="0 0 220 96">
|
| 266 |
+
<rect x="18" y="14" width="184" height="68" fill="none" stroke="#d9d2c3"/>
|
| 267 |
+
<path d="M60,70 Q110,10 160,70" fill="none" stroke="#1f6b66" stroke-width="3"/>
|
| 268 |
+
<text x="60" y="92" class="mono-label" fill="#6d675c">64 токена</text>
|
| 269 |
+
</svg>
|
| 270 |
+
<h3>L2 · крупные формы</h3>
|
| 271 |
+
<p class="muted">Тело, крылья, общая раскраска. Ширина 1024 — запас «смысла» на грубое описание.</p>
|
| 272 |
+
</div>
|
| 273 |
+
<div class="card">
|
| 274 |
+
<svg width="100%" height="96" viewBox="0 0 220 96">
|
| 275 |
+
<rect x="18" y="14" width="184" height="68" fill="none" stroke="#d9d2c3"/>
|
| 276 |
+
<path d="M40,60 C70,20 150,20 180,60" fill="none" stroke="#1f6b66" stroke-width="2"/>
|
| 277 |
+
<path d="M40,70 C70,34 150,34 180,70" fill="none" stroke="#1f6b66" stroke-width="2" opacity="0.6"/>
|
| 278 |
+
<text x="60" y="92" class="mono-label" fill="#6d675c">256 токенов</text>
|
| 279 |
+
</svg>
|
| 280 |
+
<h3>L1 · средние детали</h3>
|
| 281 |
+
<p class="muted">Форма крыла, крупный рисунок, где тёмное и где светлое. Ширина 512.</p>
|
| 282 |
+
</div>
|
| 283 |
+
<div class="card">
|
| 284 |
+
<svg width="100%" height="96" viewBox="0 0 220 96">
|
| 285 |
+
<rect x="18" y="14" width="184" height="68" fill="none" stroke="#d9d2c3"/>
|
| 286 |
+
<g stroke="#a8551f" stroke-width="1" opacity="0.8">
|
| 287 |
+
<line x1="30" y1="30" x2="46" y2="46"/><line x1="70" y1="26" x2="86" y2="42"/>
|
| 288 |
+
<line x1="110" y1="30" x2="126" y2="46"/><line x1="150" y1="26" x2="166" y2="42"/>
|
| 289 |
+
<line x1="40" y1="60" x2="56" y2="76"/><line x1="90" y1="58" x2="106" y2="74"/>
|
| 290 |
+
<line x1="140" y1="60" x2="156" y2="76"/>
|
| 291 |
+
</g>
|
| 292 |
+
<text x="56" y="92" class="mono-label" fill="#6d675c">1024 токена</text>
|
| 293 |
+
</svg>
|
| 294 |
+
<h3>L0 · текстура</h3>
|
| 295 |
+
<p class="muted">Жилки, кромки, крапинки. Здесь свёртка 3×3, а не внимание: текстура локальна, а свёртка в 20 раз дешевле.</p>
|
| 296 |
+
</div>
|
| 297 |
+
</div>
|
| 298 |
+
|
| 299 |
+
<div class="rule"></div>
|
| 300 |
+
<p class="big">Внимание на грубом уровне стоит копейки: 16 токенов × 16 = 256 пар. Внимание на мелком — 1024 × 1024 = миллион пар. Поэтому «прищур» мы делаем вниманием, а текстуру — свёрткой.</p>
|
| 301 |
+
|
| 302 |
+
<div class="foot"><span>UDiT · роли уровней</span><span>5 / 12</span></div>
|
| 303 |
+
</section>
|
| 304 |
+
|
| 305 |
+
<!-- 6 ---------------------------------------------------------------- -->
|
| 306 |
+
<section class="slide">
|
| 307 |
+
<p class="eyebrow">путь и скипы</p>
|
| 308 |
+
<h2>Энкодер поднимается к смыслу, декодер возвращается к деталям — помня эскиз</h2>
|
| 309 |
+
|
| 310 |
+
<svg width="1150" height="300" viewBox="0 0 1150 300" style="margin-top:14px">
|
| 311 |
+
<defs>
|
| 312 |
+
<marker id="up" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto">
|
| 313 |
+
<path d="M0,0 L10,5 L0,10 z" fill="#a8551f"/></marker>
|
| 314 |
+
<marker id="dn" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto">
|
| 315 |
+
<path d="M0,0 L10,5 L0,10 z" fill="#1f6b66"/></marker>
|
| 316 |
+
</defs>
|
| 317 |
+
|
| 318 |
+
<g>
|
| 319 |
+
<rect x="0" y="196" width="210" height="66" fill="#fffdf8" stroke="#d9d2c3"/>
|
| 320 |
+
<text x="14" y="222" font-size="13.5">L0 · 1024 токена · 192</text>
|
| 321 |
+
<text x="14" y="244" class="mono-label" fill="#6d675c">6 блоков enc · 4 dec</text>
|
| 322 |
+
|
| 323 |
+
<rect x="272" y="140" width="210" height="66" fill="#fffdf8" stroke="#d9d2c3"/>
|
| 324 |
+
<text x="286" y="166" font-size="13.5">L1 · 256 · 512</text>
|
| 325 |
+
<text x="286" y="188" class="mono-label" fill="#6d675c">4 enc · 3 dec</text>
|
| 326 |
+
|
| 327 |
+
<rect x="544" y="84" width="210" height="66" fill="#fffdf8" stroke="#d9d2c3"/>
|
| 328 |
+
<text x="558" y="110" font-size="13.5">L2 · 64 · 1024</text>
|
| 329 |
+
<text x="558" y="132" class="mono-label" fill="#6d675c">3 enc · 2 dec</text>
|
| 330 |
+
|
| 331 |
+
<rect x="816" y="28" width="210" height="66" fill="#fffdf8" stroke="#d9d2c3"/>
|
| 332 |
+
<text x="830" y="54" font-size="13.5">L3 · 16 · 1536</text>
|
| 333 |
+
<text x="830" y="76" class="mono-label" fill="#6d675c">2 блока · полное внимание</text>
|
| 334 |
+
</g>
|
| 335 |
+
|
| 336 |
+
<g stroke="#a8551f" stroke-width="2" fill="none">
|
| 337 |
+
<line x1="212" y1="228" x2="266" y2="180" marker-end="url(#up)"/>
|
| 338 |
+
<line x1="484" y1="172" x2="538" y2="124" marker-end="url(#up)"/>
|
| 339 |
+
<line x1="756" y1="116" x2="810" y2="68" marker-end="url(#up)"/>
|
| 340 |
+
</g>
|
| 341 |
+
<text x="206" y="212" class="mono-label" fill="#a8551f">pool 2×</text>
|
| 342 |
+
<text x="478" y="156" class="mono-label" fill="#a8551f">pool 2×</text>
|
| 343 |
+
<text x="750" y="100" class="mono-label" fill="#a8551f">pool 2×</text>
|
| 344 |
+
|
| 345 |
+
<g stroke="#1f6b66" stroke-width="2" fill="none">
|
| 346 |
+
<line x1="866" y1="98" x2="792" y2="146" marker-end="url(#dn)"/>
|
| 347 |
+
<line x1="594" y1="154" x2="520" y2="202" marker-end="url(#dn)"/>
|
| 348 |
+
<line x1="322" y1="210" x2="248" y2="258" marker-end="url(#dn)"/>
|
| 349 |
+
</g>
|
| 350 |
+
<text x="880" y="120" class="mono-label" fill="#1f6b66">up 2×</text>
|
| 351 |
+
<text x="608" y="176" class="mono-label" fill="#1f6b66">up 2×</text>
|
| 352 |
+
<text x="336" y="232" class="mono-label" fill="#1f6b66">up 2×</text>
|
| 353 |
+
|
| 354 |
+
<rect x="0" y="286" width="1026" height="0" fill="none"/>
|
| 355 |
+
<text x="0" y="292" class="mono-label" fill="#a8551f">скип: на каждом уровне выход энкодера прибавляется ко входу декодера того же уровня (аддитивно, без проекций)</text>
|
| 356 |
+
</svg>
|
| 357 |
+
|
| 358 |
+
<div class="grid3" style="margin-top:10px">
|
| 359 |
+
<div><h3>Почему аддитивные</h3><p class="muted">Ширина входа и выхода уровня совпадает, проекции не нужны — так делают U-Net’ы SD.</p></div>
|
| 360 |
+
<div><h3>Почему pool, а не stride</h3><p class="muted">Среднее 2×2 плюс линейный слой: сжатие учится и не ломает фазу позиционного кодирования.</p></div>
|
| 361 |
+
<div><h3>Что это даёт</h3><p class="muted">Тонкая структура строится на своём уровне, а не вытягивается ��ниманием с одного масштаба.</p></div>
|
| 362 |
+
</div>
|
| 363 |
+
|
| 364 |
+
<div class="foot"><span>UDiT · путь и скипы</span><span>6 / 12</span></div>
|
| 365 |
+
</section>
|
| 366 |
+
|
| 367 |
+
<!-- 7 ---------------------------------------------------------------- -->
|
| 368 |
+
<section class="slide">
|
| 369 |
+
<p class="eyebrow">текст</p>
|
| 370 |
+
<h2>Подпись не «берётся из последнего слоя» — модель сама выбирает, какие слои читать</h2>
|
| 371 |
+
|
| 372 |
+
<div class="grid2" style="margin-top:14px; grid-template-columns: 1.15fr 1fr;">
|
| 373 |
+
<div>
|
| 374 |
+
<svg width="560" height="252" viewBox="0 0 560 252">
|
| 375 |
+
<text x="0" y="12" font-size="11.5" fill="#6d675c">норма растёт с глубиной в 10 раз — без пер-слойной нормировки ранние слои не влияют</text>
|
| 376 |
+
<g class="mono-label" fill="#6d675c">
|
| 377 |
+
<text x="0" y="42">слой 2</text><text x="0" y="66">слой 5</text><text x="0" y="90">слой 8</text>
|
| 378 |
+
<text x="0" y="114">слой 11</text><text x="0" y="138">слой 14</text><text x="0" y="162">слой 17</text>
|
| 379 |
+
<text x="0" y="186">слой 20</text>
|
| 380 |
+
</g>
|
| 381 |
+
<g fill="#c98a5f">
|
| 382 |
+
<rect x="70" y="30" width="44" height="12"/><rect x="70" y="54" width="47" height="12"/>
|
| 383 |
+
<rect x="70" y="78" width="58" height="12"/><rect x="70" y="102" width="84" height="12"/>
|
| 384 |
+
<rect x="70" y="126" width="187" height="12"/><rect x="70" y="150" width="346" height="12"/>
|
| 385 |
+
</g>
|
| 386 |
+
<rect x="70" y="174" width="403" height="12" fill="#a8551f"/>
|
| 387 |
+
<text x="120" y="26" class="mono-label" fill="#7c3d14">52</text>
|
| 388 |
+
<text x="480" y="170" class="mono-label" fill="#7c3d14">548</text>
|
| 389 |
+
<rect x="0" y="206" width="560" height="24" fill="#e8f0ee" stroke="#1f6b66"/>
|
| 390 |
+
<text x="10" y="223" class="mono-label" fill="#1f6b66">7 срезов → LN на каждый → внимание по срезам → Linear(7→1) → + среднее</text>
|
| 391 |
+
<text x="0" y="248" class="mono-label" fill="#6d675c">«проектор слоёв»: какие слои читать, решает обучение, а не догадка</text>
|
| 392 |
+
</svg>
|
| 393 |
+
<p class="caption">SliceMixer · 10.04M обучаемых · идея из moss/adapter (0.8B + 158M голова вместо 9B-энкодера)</p>
|
| 394 |
+
</div>
|
| 395 |
+
<div>
|
| 396 |
+
<div class="card" style="margin-bottom:14px">
|
| 397 |
+
<h3>Два входа текста в каждый блок</h3>
|
| 398 |
+
<ul class="muted">
|
| 399 |
+
<li>кросс-внимание: каждый токен-клетка спрашивает «что в подписи про моё место?»</li>
|
| 400 |
+
<li>модуляция: общий вектор «t + усреднённый текст» настраивает shift/scale/gate фильтров</li>
|
| 401 |
+
</ul>
|
| 402 |
+
</div>
|
| 403 |
+
<table>
|
| 404 |
+
<tr><th>что сделали</th><th class="n">цена</th><th class="n">эффект</th></tr>
|
| 405 |
+
<tr><td>кросс-внимание и на L0</td><td class="n">+2.5M</td><td class="n">+9 мс</td></tr>
|
| 406 |
+
<tr><td>«switch» (один шум, два капшена)</td><td class="n">—</td><td class="n">0.43 → 0.78</td></tr>
|
| 407 |
+
<tr><td>детали (hf) не пострадали</td><td class="n">—</td><td class="n">0.44 → 0.65</td></tr>
|
| 408 |
+
</table>
|
| 409 |
+
<p class="caption">«switch» — насколько латент меняется от смены подписи при том же шуме</p>
|
| 410 |
+
</div>
|
| 411 |
+
</div>
|
| 412 |
+
|
| 413 |
+
<div class="foot"><span>UDiT · текст</span><span>7 / 12</span></div>
|
| 414 |
+
</section>
|
| 415 |
+
|
| 416 |
+
<!-- 8 ---------------------------------------------------------------- -->
|
| 417 |
+
<section class="slide">
|
| 418 |
+
<p class="eyebrow">хвост и сэмплинг</p>
|
| 419 |
+
<h2>Тело ведёт латент, готовый декодер рисует пиксели</h2>
|
| 420 |
+
|
| 421 |
+
<svg width="1140" height="240" viewBox="0 0 1140 240" style="margin-top:12px">
|
| 422 |
+
<defs>
|
| 423 |
+
<marker id="a3" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">
|
| 424 |
+
<path d="M0,0 L9,4.5 L0,9 z" fill="#14120f"/></marker>
|
| 425 |
+
</defs>
|
| 426 |
+
<g>
|
| 427 |
+
<circle cx="40" cy="70" r="18" fill="#fffdf8" stroke="#d9d2c3"/><text x="34" y="75" class="mono-label">шум</text>
|
| 428 |
+
<line x1="60" y1="70" x2="120" y2="70" stroke="#14120f" marker-end="url(#a3)"/>
|
| 429 |
+
<rect x="126" y="44" width="180" height="52" fill="#f2ece0" stroke="#a8551f"/>
|
| 430 |
+
<text x="138" y="66" font-size="12">тело: x0-предсказание</text>
|
| 431 |
+
<text x="138" y="84" class="mono-label" fill="#6d675c">8 шагов · CFG 1.5</text>
|
| 432 |
+
<line x1="310" y1="70" x2="370" y2="70" stroke="#14120f" marker-end="url(#a3)"/>
|
| 433 |
+
<rect x="376" y="44" width="150" height="52" fill="#fffdf8" stroke="#d9d2c3"/>
|
| 434 |
+
<text x="388" y="66" font-size="12">латент z</text>
|
| 435 |
+
<text x="388" y="84" class="mono-label" fill="#6d675c">32 × h/8 × w/8</text>
|
| 436 |
+
<line x1="530" y1="70" x2="590" y2="70" stroke="#14120f" marker-end="url(#a3)"/>
|
| 437 |
+
<rect x="596" y="44" width="190" height="52" fill="#e8f0ee" stroke="#1f6b66"/>
|
| 438 |
+
<text x="608" y="66" font-size="12">хвост: декодер AE</text>
|
| 439 |
+
<text x="608" y="84" class="mono-label" fill="#6d675c">4 шага flow</text>
|
| 440 |
+
<line x1="790" y1="70" x2="850" y2="70" stroke="#14120f" marker-end="url(#a3)"/>
|
| 441 |
+
<rect x="856" y="44" width="130" height="52" fill="#fffdf8" stroke="#d9d2c3"/>
|
| 442 |
+
<text x="868" y="66" font-size="12">пиксели</text>
|
| 443 |
+
<text x="868" y="84" class="mono-label" fill="#6d675c">3 × H × W</text>
|
| 444 |
+
</g>
|
| 445 |
+
</svg>
|
| 446 |
+
|
| 447 |
+
<div class="rule" style="margin:6px 0 10px"></div>
|
| 448 |
+
<table style="table-layout:fixed">
|
| 449 |
+
<tr><th style="width:30%">на каком латенте учить хвост</th><th style="width:18%">что видит рендерер</th><th style="width:52%">что происходит</th></tr>
|
| 450 |
+
<tr><td>только z0 (чистый латент)</td><td>идеальный латент</td><td>на сэмплинге встречает ошибку тела, к которой не привык</td></tr>
|
| 451 |
+
<tr><td>только предсказание тела</td><td>мусор в начале обучения</td><td>учится «исправлять мусор» и разучивается рисовать: 29.6 → 21.3 дБ</td></tr>
|
| 452 |
+
<tr><td><b>z0 + (ошибка тела).detach()</b></td><td>реальную ошибку</td><td>видит промах, но градиент через него не идёт; 24.7–25.9 дБ стабильно</td></tr>
|
| 453 |
+
</table>
|
| 454 |
+
<p class="caption">Сэмпл: 8 шагов тела + 4 шага декодера ≈ 35 мс на 4080 при 256². CFG 1.5 — выше 2 при недоученной
|
| 455 |
+
безусловной ветви ухудшает латент (замерено 0.19 → 0.28). Метрика tail(z0) в каждом логе ловит деградацию рендерера.</p>
|
| 456 |
+
|
| 457 |
+
<div class="foot"><span>UDiT · хвост</span><span>8 / 12</span></div>
|
| 458 |
+
</section>
|
| 459 |
+
|
| 460 |
+
<!-- 9 ---------------------------------------------------------------- -->
|
| 461 |
+
<section class="slide">
|
| 462 |
+
<p class="eyebrow">результат на узком домене</p>
|
| 463 |
+
<h2>Бабочки: детали растут с шагами и не насыщаются</h2>
|
| 464 |
+
|
| 465 |
+
<div class="grid2" style="margin-top:10px; grid-template-columns: 1fr 1.1fr;">
|
| 466 |
+
<div>
|
| 467 |
+
<table>
|
| 468 |
+
<tr><th>шаги</th><th class="n">hf / AE</th><th class="n">mse_lat</th><th class="n">high</th><th class="n">switch</th></tr>
|
| 469 |
+
<tr><td>4 000</td><td class="n">0.446 · 52%</td><td class="n">0.21</td><td class="n">0.42</td><td class="n">0.43</td></tr>
|
| 470 |
+
<tr><td>10 000</td><td class="n">0.436 · 51%</td><td class="n">0.164</td><td class="n">0.32</td><td class="n">0.68</td></tr>
|
| 471 |
+
<tr><td>20 000</td><td class="n">0.540 · 63%</td><td class="n">0.107</td><td class="n">0.19</td><td class="n">0.78</td></tr>
|
| 472 |
+
<tr><td><b>30 000</b></td><td class="n"><b>0.647 · 75%</b></td><td class="n"><b>0.072</b></td><td class="n"><b>0.114</b></td><td class="n">0.78</td></tr>
|
| 473 |
+
</table>
|
| 474 |
+
<p class="caption">hf — энергия высоких частот (лапласиан). AE-реконструкция того же вида: 0.859, оригинал 0.913.
|
| 475 |
+
«high» — ошибка на высоком шуме, то есть «какую особь рисовать»; она упала в 3.7 раза.</p>
|
| 476 |
+
<p class="big" style="margin-top:10px">Три сида дают три разные особи: 0.642 / 0.646 / 0.654 — модель не сколлапсировала в один мод.</p>
|
| 477 |
+
</div>
|
| 478 |
+
<div>
|
| 479 |
+
<img class="strip" src="img/samples.png" alt="Четыре вида бабочек, сгенерированные моделью">
|
| 480 |
+
<p class="caption">генерации на 30k шагов: Delias, Cepora, Troides, Catasticta — вид задаётся подписью из 16 токенов</p>
|
| 481 |
+
</div>
|
| 482 |
+
</div>
|
| 483 |
+
|
| 484 |
+
<div class="foot"><span>UDiT · результат</span><span>9 / 12</span></div>
|
| 485 |
+
</section>
|
| 486 |
+
|
| 487 |
+
<!-- 10 --------------------------------------------------------------- -->
|
| 488 |
+
<section class="slide">
|
| 489 |
+
<p class="eyebrow">цена</p>
|
| 490 |
+
<h2>177.3M параметров, 97 мс на шаг, 8.1 ГБ — обучение на одной карте</h2>
|
| 491 |
+
|
| 492 |
+
<div class="grid3" style="margin-top:16px">
|
| 493 |
+
<div>
|
| 494 |
+
<h3>Параметры по частям</h3>
|
| 495 |
+
<svg width="330" height="170" viewBox="0 0 330 170">
|
| 496 |
+
<rect x="0" y="20" width="240" height="24" fill="#a8551f"/><text x="248" y="37" class="mono-label">143.3 M · тело</text>
|
| 497 |
+
<rect x="0" y="54" width="17" height="24" fill="#1f6b66"/><text x="25" y="71" class="mono-label">10.0 M · текст</text>
|
| 498 |
+
<rect x="0" y="88" width="40" height="24" fill="#6d675c"/><text x="48" y="105" class="mono-label">23.9 M · хвост</text>
|
| 499 |
+
<line x1="0" y1="132" x2="300" y2="132" stroke="#d9d2c3"/>
|
| 500 |
+
<text x="0" y="152" class="mono-label" fill="#6d675c">всего 177.3M · чекпойнт 709 МБ</text>
|
| 501 |
+
</svg>
|
| 502 |
+
</div>
|
| 503 |
+
<div>
|
| 504 |
+
<h3>Арифметика на forward</h3>
|
| 505 |
+
<svg width="330" height="170" viewBox="0 0 330 170">
|
| 506 |
+
<rect x="0" y="20" width="20" height="24" fill="#1f6b66"/><text x="28" y="37" class="mono-label">20.4 · пирамида</text>
|
| 507 |
+
<rect x="0" y="54" width="230" height="24" fill="#e7c8b2"/><text x="238" y="71" class="mono-label" fill="#7c3d14">232 · плоский 1024</text>
|
| 508 |
+
<text x="0" y="106" class="mono-label" fill="#6d675c">+ хвост ≈ 29 GFLOP</text>
|
| 509 |
+
<text x="0" y="126" class="mono-label" fill="#6d675c">итого ≈ 50 против ≈ 262</text>
|
| 510 |
+
<text x="0" y="152" class="mono-label" fill="#6d675c">та же карта, в 5 раз больше шагов</text>
|
| 511 |
+
</svg>
|
| 512 |
+
</div>
|
| 513 |
+
<div>
|
| 514 |
+
<h3>Скорость и память</h3>
|
| 515 |
+
<svg width="330" height="170" viewBox="0 0 330 170">
|
| 516 |
+
<rect x="0" y="20" width="110" height="24" fill="#a8551f"/><text x="118" y="37" class="mono-label">97 мс · пирамида</text>
|
| 517 |
+
<rect x="0" y="54" width="222" height="24" fill="#e7c8b2"/><text x="230" y="71" class="mono-label" fill="#7c3d14">196 мс · плоский</text>
|
| 518 |
+
<text x="0" y="106" class="mono-label" fill="#6d675c">8.1 ГБ из 16 (bs8, 256²)</text>
|
| 519 |
+
<text x="0" y="126" class="mono-label" fill="#6d675c">8-битный AdamW: −1 ГБ состояний</text>
|
| 520 |
+
<text x="0" y="152" class="mono-label" fill="#6d675c">эффективность ~1.6 TFLOPS — упор в память</text>
|
| 521 |
+
</svg>
|
| 522 |
+
</div>
|
| 523 |
+
</div>
|
| 524 |
+
|
| 525 |
+
<div class="rule"></div>
|
| 526 |
+
<div class="grid2">
|
| 527 |
+
<div class="card">
|
| 528 |
+
<h3>Почему хватает 16 ГБ</h3>
|
| 529 |
+
<p class="muted">Память ест не модель (2.5 ГБ весов и состояний), а активации: они ~ токены × ширина × блоки.
|
| 530 |
+
Пирамида держит широкие слои на 16–64 токенах, поэтому активации маленькие.</p>
|
| 531 |
+
</div>
|
| 532 |
+
<div class="card">
|
| 533 |
+
<h3>Где запас</h3>
|
| 534 |
+
<p class="muted">Шаг упирается в память, а не в счёт. Запас есть на батч 16 (14.8 ГБ с хвостом) и на 512²
|
| 535 |
+
с батчем 1–2 — то есть на разрешение, где деталь физически возможна.</p>
|
| 536 |
+
</div>
|
| 537 |
+
</div>
|
| 538 |
+
|
| 539 |
+
<div class="foot"><span>UDiT · цена</span><span>10 / 12</span></div>
|
| 540 |
+
</section>
|
| 541 |
+
|
| 542 |
+
<!-- 11 --------------------------------------------------------------- -->
|
| 543 |
+
<section class="slide">
|
| 544 |
+
<p class="eyebrow">следующий прогон</p>
|
| 545 |
+
<h2>FLUX-Reason: 2 эпохи по 1.5M картинок, поток без кэша</h2>
|
| 546 |
+
|
| 547 |
+
<div class="grid2" style="margin-top:14px">
|
| 548 |
+
<div>
|
| 549 |
+
<table>
|
| 550 |
+
<tr><th>параметр</th><th>значение</th></tr>
|
| 551 |
+
<tr><td>датасет</td><td>LucasFang/FLUX-Reason-6M, часть Aesthetics-Part01 (415 шардов, ~250 ГБ)</td></tr>
|
| 552 |
+
<tr><td>разрешение / батч</td><td>256² · bs16</td></tr>
|
| 553 |
+
<tr><td>подпись</td><td>caption_entity → caption_composition (40–400 симв.), иначе caption_detail</td></tr>
|
| 554 |
+
<tr><td>бюджет</td><td>2 эпохи = 187 500 шагов ≈ 12.5 ч</td></tr>
|
| 555 |
+
<tr><td>оптимизатор</td><td>AdamW8bit, прогрев 500, cosine до 10%</td></tr>
|
| 556 |
+
<tr><td>дропауты</td><td>подпись 10% (CFG) · слова 5%</td></tr>
|
| 557 |
+
<tr><td>старт</td><td>веса бабочек (30k шагов)</td></tr>
|
| 558 |
+
</table>
|
| 559 |
+
</div>
|
| 560 |
+
<div>
|
| 561 |
+
<h3>Что уже видно (шаг 800)</h3>
|
| 562 |
+
<svg width="480" height="150" viewBox="0 0 480 150">
|
| 563 |
+
<text x="0" y="18" class="mono-label" fill="#6d675c">mse_lat общий и по зонам шума</text>
|
| 564 |
+
<line x1="0" y1="120" x2="470" y2="120" stroke="#d9d2c3"/>
|
| 565 |
+
<rect x="10" y="60" width="60" height="60" fill="#a8551f"/><text x="18" y="140" class="mono-label">0.33</text>
|
| 566 |
+
<rect x="110" y="113" width="60" height="7" fill="#1f6b66"/><text x="120" y="140" class="mono-label">0.037</text>
|
| 567 |
+
<rect x="210" y="120" width="60" height="0.5" fill="#6d675c"/>
|
| 568 |
+
<text x="330" y="70" class="mono-label" fill="#6d675c">low (t<0.3): 0.037</text>
|
| 569 |
+
<text x="330" y="90" class="mono-label" fill="#6d675c">high (t>0.7): 0.70</text>
|
| 570 |
+
<text x="330" y="110" class="mono-label" fill="#6d675c">241 мс/шаг · 66 картинок/с</text>
|
| 571 |
+
<text x="330" y="130" class="mono-label" fill="#6d675c">9.5 ГБ · skip 0</text>
|
| 572 |
+
</svg>
|
| 573 |
+
<p class="caption">«high» здесь в 6 раз выше, чем на бабочках: 1.5M разных сцен, «какую именно рисовать»
|
| 574 |
+
по подписи не угадать — это нормальный старт, а не поломка.</p>
|
| 575 |
+
</div>
|
| 576 |
+
</div>
|
| 577 |
+
|
| 578 |
+
<div class="rule"></div>
|
| 579 |
+
<div class="grid3">
|
| 580 |
+
<div><h3>Смотреть</h3><p class="muted">tmp/flux/log.csv · tmp/flux/samples/latest.png (лиса, стеклянный город, неон, импрессионист)</p></div>
|
| 581 |
+
<div><h3>Остановить / продолжить</h3><p class="muted">kill процесса · тот же запуск с --resume --out tmp/flux</p></div>
|
| 582 |
+
<div><h3>Честно про «2 эпохи»</h3><p class="muted">эпоха по Part01 = 1.5M картинок; все 4 части ≈ 1 ТБ, а на диске 352 ГБ</p></div>
|
| 583 |
+
</div>
|
| 584 |
+
|
| 585 |
+
<div class="foot"><span>UDiT · FLUX-Reason</span><span>11 / 12</span></div>
|
| 586 |
+
</section>
|
| 587 |
+
|
| 588 |
+
<!-- 12 --------------------------------------------------------------- -->
|
| 589 |
+
<section class="slide">
|
| 590 |
+
<p class="eyebrow">правила</p>
|
| 591 |
+
<h2>Всё, что ниже — вывод из замеров, а не из вкуса</h2>
|
| 592 |
+
|
| 593 |
+
<div class="grid2" style="margin-top:16px; gap:26px 40px">
|
| 594 |
+
<div>
|
| 595 |
+
<p><span class="tag">1</span> <b>Ширина растёт к низу.</b> <span class="muted">Плоский 1024 на всех уровнях: 196 мс и hf 0.18. Пирамида: 97 мс и 0.54.</span></p>
|
| 596 |
+
<p><span class="tag">2</span> <b>Текст не экономим.</b> <span class="muted">Кросс-внимание на самом мелком уровне: +2.5M и +9 мс, «switch» 0.43 → 0.78.</span></p>
|
| 597 |
+
<p><span class="tag">3</span> <b>Хвост учится на z0 + ошибка тела.</b> <span class="muted">Только z0 — не видит реальных ошибок; только предсказания тела — деградация 29.6 → 21.3 дБ.</span></p>
|
| 598 |
+
</div>
|
| 599 |
+
<div>
|
| 600 |
+
<p><span class="tag">4</span> <b>Бакеты кратны 64.</b> <span class="muted">Три даунсэмпла требуют латента, кратного 8. Аспект — центр-кроп, не растяжение.</span></p>
|
| 601 |
+
<p><span class="tag">5</span> <b>Метрики вместо впечатлений.</b> <span class="muted">mse_lat (общий, low, high), mse_pix, switch, tail(z0), hf(gen)/hf(AE), skip.</span></p>
|
| 602 |
+
<p><span class="tag">6</span> <b>Никаких костылей.</b> <span class="muted">Фиксированный шум, клип латента, «mix» латентов — выброшены: они прячут то, что мы измеряем.</span></p>
|
| 603 |
+
</div>
|
| 604 |
+
</div>
|
| 605 |
+
|
| 606 |
+
<div class="rule"></div>
|
| 607 |
+
<div class="grid4">
|
| 608 |
+
<div><h3>Дальше</h3><p class="muted">дожать FLUX-Reason до 2 эпох по Part01</p></div>
|
| 609 |
+
<div><h3>Потом</h3><p class="muted">люди: 5749 фото, 256² — лица и кисти</p></div>
|
| 610 |
+
<div><h3>Затем</h3><p class="muted">512² на людях: деталь физически разрешима</p></div>
|
| 611 |
+
<div><h3>Только потом</h3><p class="muted">ёмкость: ширина боттлнека и число блоков L0</p></div>
|
| 612 |
+
</div>
|
| 613 |
+
|
| 614 |
+
<div class="foot"><span>UDiT · правила</span><span>12 / 12</span></div>
|
| 615 |
+
</section>
|
| 616 |
+
|
| 617 |
+
</body>
|
| 618 |
+
</html>
|
src/export.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Экспорт обученного UDM в `transformer/`: safetensors + config.json.
|
| 2 |
+
|
| 3 |
+
Три части в одном файле, по префиксам:
|
| 4 |
+
body.* — тело (8 блоков из энкодера AE + 4 новых), поток в латентном пространстве
|
| 5 |
+
text_mixer.* — обучаемый выбор слоёв Ettin (SliceMixer)
|
| 6 |
+
tail.* — декодер AE (рендер латента в пиксели), дообученный как обычный flow
|
| 7 |
+
|
| 8 |
+
python3 src/export.py --ckpt tmp/run256/last.pt --out transformer
|
| 9 |
+
"""
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import json
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
from safetensors.torch import save_file
|
| 18 |
+
|
| 19 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def main():
|
| 23 |
+
ap = argparse.ArgumentParser()
|
| 24 |
+
ap.add_argument("--ckpt", default=str(ROOT / "tmp" / "run256" / "last.pt"))
|
| 25 |
+
ap.add_argument("--out", default=str(ROOT / "transformer"))
|
| 26 |
+
ap.add_argument("--metrics", default="", help="дописать метрики в config.json (строка)")
|
| 27 |
+
ap.add_argument("--half", action="store_true", help="хранить веса в fp16 (вдвое меньше, для публикации)")
|
| 28 |
+
args = ap.parse_args()
|
| 29 |
+
|
| 30 |
+
ck = torch.load(args.ckpt, map_location="cpu")
|
| 31 |
+
tensors = {}
|
| 32 |
+
for name, sd in (("body", ck["body"]), ("text_mixer", ck["mixer"]), ("tail", ck.get("decoder") or {})):
|
| 33 |
+
for k, v in sd.items():
|
| 34 |
+
tensors[f"{name}.{k}"] = (v.half() if args.half else v).contiguous()
|
| 35 |
+
out = Path(args.out)
|
| 36 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 37 |
+
save_file(tensors, str(out / "diffusion_pytorch_model.safetensors"))
|
| 38 |
+
|
| 39 |
+
ae_cfg = json.loads((ROOT / "vae" / "config.json").read_text())
|
| 40 |
+
ta = ck.get("args") or {} # чекпойнт помнит свои аргументы — не сочиняем заново
|
| 41 |
+
training = {
|
| 42 |
+
"dataset": ta.get("hf_dataset") or ta.get("data") or "~/dataset/butterflies_256x64",
|
| 43 |
+
"parts": ta.get("parts"),
|
| 44 |
+
"size": ta.get("size"),
|
| 45 |
+
"steps": int(ck["step"]),
|
| 46 |
+
"batch": ta.get("batch", 8),
|
| 47 |
+
"accum": ta.get("accum", 1),
|
| 48 |
+
"lr": ta.get("lr", 1e-4),
|
| 49 |
+
"lr_tail": ta.get("lr_tail", 3e-5),
|
| 50 |
+
"drop_text": ta.get("drop_text", 0.1),
|
| 51 |
+
"init": ta.get("init"),
|
| 52 |
+
"metrics": args.metrics,
|
| 53 |
+
}
|
| 54 |
+
cfg = {
|
| 55 |
+
"_class_name": "UDiT",
|
| 56 |
+
"name": "Ra",
|
| 57 |
+
"format": "udm/v1",
|
| 58 |
+
"step": int(ck["step"]),
|
| 59 |
+
"latent": {
|
| 60 |
+
"channels": ae_cfg["latent_channels"],
|
| 61 |
+
"spatial_factor": ae_cfg["patch_size"],
|
| 62 |
+
"mean": ae_cfg["latent_mean"],
|
| 63 |
+
"std": ae_cfg["latent_std"],
|
| 64 |
+
"source": "vae/ (Muinez/f8c32-ae)",
|
| 65 |
+
},
|
| 66 |
+
"text": {
|
| 67 |
+
"encoder": "text_encoder/ (jhu-clsp/ettin-encoder-150m, 768-dim, 22 слоя)",
|
| 68 |
+
"max_len": 192,
|
| 69 |
+
"slices": [2, 5, 8, 11, 14, 17, 20],
|
| 70 |
+
"slice_blocks": 2,
|
| 71 |
+
"trainable": "text_mixer.*",
|
| 72 |
+
},
|
| 73 |
+
"body": {
|
| 74 |
+
"kind": "пирамидальный U: ширина растёт к низу",
|
| 75 |
+
"levels": [
|
| 76 |
+
{"level": 0, "tokens": "÷1", "width": 192, "blocks": "conv 6 enc / 4 dec + 1 self-attn на сторону",
|
| 77 |
+
"text": "кросс-внимание в каждом conv-блоке и в self-attn"},
|
| 78 |
+
{"level": 1, "tokens": "÷2", "width": 512, "blocks": "attn 4 enc / 3 dec", "text": "кросс-внимание"},
|
| 79 |
+
{"level": 2, "tokens": "÷4", "width": 1024, "blocks": "attn 3 enc / 2 dec", "text": "кросс-внимание"},
|
| 80 |
+
{"level": 3, "tokens": "÷8", "width": 1536, "blocks": "attn 2 (боттлнек)", "text": "кросс-внимание"},
|
| 81 |
+
],
|
| 82 |
+
"skips": "аддитивные (SD-стиль): e0/e1/e2 прибавляются в декодере",
|
| 83 |
+
"down": "mean-pool 2× + Linear",
|
| 84 |
+
"up": "Linear + nearest 2×",
|
| 85 |
+
"rope": "2D RoPE, base 100, фаза в латентных пикселях на каждом уровне",
|
| 86 |
+
"parametrization": "x0 латента",
|
| 87 |
+
},
|
| 88 |
+
"tail": {
|
| 89 |
+
"source": "декодер AE, 6 блоков @512, PixelShuffle ×3",
|
| 90 |
+
"trained_as": "flow по пикселям, условие z0 + (ошибка тела).detach()",
|
| 91 |
+
"sample_steps": 4,
|
| 92 |
+
},
|
| 93 |
+
"params": {
|
| 94 |
+
"body": round(sum(v.numel() for k, v in tensors.items() if k.startswith("body.")) / 1e6, 2),
|
| 95 |
+
"text_mixer": round(sum(v.numel() for k, v in tensors.items()
|
| 96 |
+
if k.startswith("text_mixer.")) / 1e6, 2),
|
| 97 |
+
"tail": round(sum(v.numel() for k, v in tensors.items() if k.startswith("tail.")) / 1e6, 2),
|
| 98 |
+
},
|
| 99 |
+
"training": {k: v for k, v in training.items() if v is not None},
|
| 100 |
+
"storage_dtype": "float16" if args.half else "float32",
|
| 101 |
+
}
|
| 102 |
+
cfg["params"]["total"] = round(sum(cfg["params"].values()), 2)
|
| 103 |
+
(out / "config.json").write_text(json.dumps(cfg, indent=2, ensure_ascii=False))
|
| 104 |
+
|
| 105 |
+
print(f"сохранено: {out}/diffusion_pytorch_model.safetensors "
|
| 106 |
+
f"({sum(v.numel() for v in tensors.values())/1e6:.1f}M параметров, "
|
| 107 |
+
f"{(out / 'diffusion_pytorch_model.safetensors').stat().st_size/1e6:.0f} МБ)")
|
| 108 |
+
print(f"части: {cfg['params']}")
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def load_exported(model, enc, ae, path, device=None):
|
| 112 |
+
"""Загрузить из `transformer/` (config.json + safetensors с префиксами body./text_mixer./tail.)."""
|
| 113 |
+
import json
|
| 114 |
+
from safetensors.torch import load_file
|
| 115 |
+
path = Path(path)
|
| 116 |
+
sd = load_file(str(path / "diffusion_pytorch_model.safetensors"), device=str(device) if device else None)
|
| 117 |
+
def sub(prefix):
|
| 118 |
+
return {k[len(prefix) + 1:]: v for k, v in sd.items() if k.startswith(prefix + ".")}
|
| 119 |
+
model.body.load_state_dict(sub("body"), strict=False)
|
| 120 |
+
enc.mixer.load_state_dict(sub("text_mixer"))
|
| 121 |
+
ae.decoder.load_state_dict(sub("tail"))
|
| 122 |
+
cfg_path = path / "config.json"
|
| 123 |
+
return json.loads(cfg_path.read_text()) if cfg_path.exists() else {}
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
if __name__ == "__main__":
|
| 127 |
+
main()
|
src/make_dataset.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Сборка набора из папки «картинка + .txt»: бакеты (кратны --multiple) и центр-кроп.
|
| 2 |
+
|
| 3 |
+
python3 src/make_dataset.py --src ~/dataset/butterflies/butterfly \
|
| 4 |
+
--out ~/dataset/butterflies_256 --long-side 256 --eval-n 30
|
| 5 |
+
"""
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import argparse
|
| 9 |
+
import csv
|
| 10 |
+
import math
|
| 11 |
+
import random
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
from PIL import Image, ImageOps
|
| 15 |
+
|
| 16 |
+
BUCKETS = {}
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def buckets(long_side: int, multiple: int = 16) -> dict:
|
| 20 |
+
L = long_side
|
| 21 |
+
def r16(x): return max(multiple, int(round(x / multiple)) * multiple)
|
| 22 |
+
return {
|
| 23 |
+
"1:1": (L, L),
|
| 24 |
+
"4:3": (L, r16(L * 3 / 4)),
|
| 25 |
+
"3:4": (r16(L * 3 / 4), L),
|
| 26 |
+
"3:2": (L, r16(L * 2 / 3)),
|
| 27 |
+
"2:3": (r16(L * 2 / 3), L),
|
| 28 |
+
"16:9": (L, r16(L * 9 / 16)),
|
| 29 |
+
"9:16": (r16(L * 9 / 16), L),
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def plan_size(w, h, max_upscale=1.4):
|
| 34 |
+
ratio = w / h
|
| 35 |
+
label, (tw, th) = min(BUCKETS.items(), key=lambda kv: abs(math.log(ratio / (kv[1][0] / kv[1][1]))))
|
| 36 |
+
if ratio > tw / th:
|
| 37 |
+
cw, ch = round(h * tw / th), h
|
| 38 |
+
else:
|
| 39 |
+
cw, ch = w, round(w * th / tw)
|
| 40 |
+
cw, ch = min(cw, w), min(ch, h)
|
| 41 |
+
if cw * max_upscale < tw or ch * max_upscale < th:
|
| 42 |
+
return None
|
| 43 |
+
return label, tw, th, cw, ch
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def main():
|
| 47 |
+
ap = argparse.ArgumentParser()
|
| 48 |
+
ap.add_argument("--src", required=True)
|
| 49 |
+
ap.add_argument("--out", required=True)
|
| 50 |
+
ap.add_argument("--long-side", type=int, default=256)
|
| 51 |
+
ap.add_argument("--eval-n", type=int, default=30)
|
| 52 |
+
ap.add_argument("--max-upscale", type=float, default=1.4)
|
| 53 |
+
ap.add_argument("--multiple", type=int, default=64,
|
| 54 |
+
help="кратность сторон: три даунсэмпла тела требуют латент, кратный 8")
|
| 55 |
+
ap.add_argument("--seed", type=int, default=0)
|
| 56 |
+
args = ap.parse_args()
|
| 57 |
+
|
| 58 |
+
global BUCKETS
|
| 59 |
+
BUCKETS = buckets(args.long_side, args.multiple)
|
| 60 |
+
src, out = Path(args.src), Path(args.out)
|
| 61 |
+
for sub in ("train", "eval"):
|
| 62 |
+
(out / sub).mkdir(parents=True, exist_ok=True)
|
| 63 |
+
|
| 64 |
+
pairs = []
|
| 65 |
+
for img in sorted(list(src.glob("*.png")) + list(src.glob("*.jpg"))):
|
| 66 |
+
txt = img.with_suffix(".txt")
|
| 67 |
+
if txt.exists():
|
| 68 |
+
pairs.append((img, txt))
|
| 69 |
+
random.Random(args.seed).shuffle(pairs)
|
| 70 |
+
print(f"пар: {len(pairs)}")
|
| 71 |
+
|
| 72 |
+
rows, kept = [], 0
|
| 73 |
+
for i, (img, txt) in enumerate(pairs):
|
| 74 |
+
im = ImageOps.exif_transpose(Image.open(img)).convert("RGB")
|
| 75 |
+
plan = plan_size(im.width, im.height, args.max_upscale)
|
| 76 |
+
if plan is None:
|
| 77 |
+
continue
|
| 78 |
+
label, tw, th, cw, ch = plan
|
| 79 |
+
left, top = (im.width - cw) // 2, (im.height - ch) // 2
|
| 80 |
+
im = im.crop((left, top, left + cw, top + ch)).resize((tw, th), Image.LANCZOS)
|
| 81 |
+
split = "eval" if i < args.eval_n else "train"
|
| 82 |
+
name = f"{img.stem}"
|
| 83 |
+
im.save(out / split / f"{name}.jpg", quality=95)
|
| 84 |
+
(out / split / f"{name}.txt").write_text(txt.read_text().strip() + "\n")
|
| 85 |
+
rows.append({"split": split, "name": name, "w": tw, "h": th, "bucket": label})
|
| 86 |
+
kept += 1
|
| 87 |
+
with open(out / "index.csv", "w", newline="") as fh:
|
| 88 |
+
w = csv.DictWriter(fh, fieldnames=["split", "name", "w", "h", "bucket"])
|
| 89 |
+
w.writeheader(); w.writerows(rows)
|
| 90 |
+
print(f"готово: {kept} (train {kept-args.eval_n}, eval {args.eval_n}) -> {out}")
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
if __name__ == "__main__":
|
| 94 |
+
main()
|
src/make_dataset_people.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Сборка набора по классам (woman/man/cat) из ~/dataset/allphoto_train — тест анатомии.
|
| 2 |
+
|
| 3 |
+
Зачем: бабочки однородны и анатомию не показывают. Нужен набор, где модель обязана выучить
|
| 4 |
+
тело: разные люди, позы, ракурсы, и капшены, которые называют, кто это и что делает.
|
| 5 |
+
|
| 6 |
+
Классы: woman / man / cat — по N уникальных капшенов на класс (дедуп по первым 60 символам,
|
| 7 |
+
иначе один photoshoot даёт десятки почти одинаковых кадров).
|
| 8 |
+
|
| 9 |
+
python3 src/make_dataset_people.py --n 60 --out ~/dataset/testset_anatomy
|
| 10 |
+
"""
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import argparse
|
| 14 |
+
import csv
|
| 15 |
+
import math
|
| 16 |
+
import random
|
| 17 |
+
import re
|
| 18 |
+
import shutil
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
|
| 21 |
+
from PIL import Image, ImageOps
|
| 22 |
+
|
| 23 |
+
SRC = Path.home() / "dataset" / "allphoto_train"
|
| 24 |
+
|
| 25 |
+
CLASSES = {
|
| 26 |
+
"woman": ("woman", "women", "girl"),
|
| 27 |
+
"man": ("man", "men", "boy"),
|
| 28 |
+
"cat": ("cat",),
|
| 29 |
+
}
|
| 30 |
+
# бакеты как в moss/src/make_dataset.py: обе стороны кратны 16 (латент /8, потом patch-2),
|
| 31 |
+
# соотношение выбирается ближайшим по |log(ratio_источника / ratio_бакета)|, затем ЦЕНТР-КРОП —
|
| 32 |
+
# растягивать нельзя, иначе лица и тела деформируются
|
| 33 |
+
def buckets(long_side: int = 512, multiple: int = 64) -> dict:
|
| 34 |
+
"""Бакеты: длинная сторона long_side, обе стороны кратны 16 (латент /8, затем /2)."""
|
| 35 |
+
L = long_side
|
| 36 |
+
def r16(x): return max(multiple, int(round(x / multiple)) * multiple)
|
| 37 |
+
return {
|
| 38 |
+
"1:1": (L, L),
|
| 39 |
+
"4:3": (L, r16(L * 3 / 4)),
|
| 40 |
+
"3:4": (r16(L * 3 / 4), L),
|
| 41 |
+
"3:2": (L, r16(L * 2 / 3)),
|
| 42 |
+
"2:3": (r16(L * 2 / 3), L),
|
| 43 |
+
"16:9": (L, r16(L * 9 / 16)),
|
| 44 |
+
"9:16": (r16(L * 9 / 16), L),
|
| 45 |
+
}
|
| 46 |
+
BUCKETS = buckets(512)
|
| 47 |
+
|
| 48 |
+
# для людей требуем, чтобы в кадре было тело, а не только лицо: бабочки-портреты анатомию не учат
|
| 49 |
+
BODY_WORDS = ("standing", "walking", "sitting", "seated", "jeans", "shorts", "dress", "skirt",
|
| 50 |
+
"shirt", "legs", "arms", "hands", "wearing", "full body", "walks", "stands")
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def classify(cap: str) -> str | None:
|
| 54 |
+
low = cap.lower()
|
| 55 |
+
for cls, words in CLASSES.items():
|
| 56 |
+
if any(re.search(rf"\b{w}\b", low) for w in words):
|
| 57 |
+
# «man» внутри «woman» уже отсечён \b; но «woman» не должна становиться «man»
|
| 58 |
+
if cls == "man" and re.search(r"\bwoman\b", low):
|
| 59 |
+
return "woman"
|
| 60 |
+
return cls
|
| 61 |
+
return None
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def trim_black(im: Image.Image, thr: float = 12.0, var: float = 8.0) -> Image.Image:
|
| 65 |
+
"""Срезает однородные чёрные поля по краям (в allphoto_train их полно: 71 из 171 кадра)."""
|
| 66 |
+
import numpy as np
|
| 67 |
+
a = np.asarray(im.convert("L"), dtype=np.float32)
|
| 68 |
+
def uniform_edge(line): # чёрная и без структуры -> рамка, а не ночная сцена
|
| 69 |
+
return line.mean() < thr and line.std() < var
|
| 70 |
+
top, bot, left, right = 0, a.shape[0], 0, a.shape[1]
|
| 71 |
+
while top < bot - 8 and uniform_edge(a[top]): top += 1
|
| 72 |
+
while bot > top + 8 and uniform_edge(a[bot - 1]): bot -= 1
|
| 73 |
+
while left < right - 8 and uniform_edge(a[top:bot, left]): left += 1
|
| 74 |
+
while right > left + 8 and uniform_edge(a[top:bot, right - 1]): right -= 1
|
| 75 |
+
return im.crop((left, top, right, bot)) if (left or top or right != a.shape[1] or bot != a.shape[0]) else im
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def plan_size(w: int, h: int, max_upscale: float = 1.4):
|
| 79 |
+
"""(w,h) -> (label, tw, th, cw, ch) или None, если источника не хватает (как в moss)."""
|
| 80 |
+
ratio = w / h
|
| 81 |
+
label, (tw, th) = min(BUCKETS.items(), key=lambda kv: abs(math.log(ratio / (kv[1][0] / kv[1][1]))))
|
| 82 |
+
if ratio > tw / th:
|
| 83 |
+
cw, ch = round(h * tw / th), h
|
| 84 |
+
else:
|
| 85 |
+
cw, ch = w, round(w * th / tw)
|
| 86 |
+
cw, ch = min(cw, w), min(ch, h)
|
| 87 |
+
if cw * max_upscale < tw or ch * max_upscale < th:
|
| 88 |
+
return None
|
| 89 |
+
return label, tw, th, cw, ch
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def load_bucketed(path: Path, max_upscale: float = 1.4):
|
| 93 |
+
"""EXIF-поворот -> срез чёрных полей -> ближайший бакет -> центр-кроп -> ресайз."""
|
| 94 |
+
im = ImageOps.exif_transpose(Image.open(path)).convert("RGB")
|
| 95 |
+
im = trim_black(im)
|
| 96 |
+
plan = plan_size(im.width, im.height, max_upscale)
|
| 97 |
+
if plan is None:
|
| 98 |
+
return None, None
|
| 99 |
+
label, tw, th, cw, ch = plan
|
| 100 |
+
left, top = (im.width - cw) // 2, (im.height - ch) // 2
|
| 101 |
+
return im.crop((left, top, left + cw, top + ch)).resize((tw, th), Image.LANCZOS), label
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def main():
|
| 105 |
+
ap = argparse.ArgumentParser()
|
| 106 |
+
ap.add_argument("--n", type=int, default=60, help="картинок на класс")
|
| 107 |
+
ap.add_argument("--eval-n", type=int, default=4, help="сколько на класс отложить для показа")
|
| 108 |
+
ap.add_argument("--max-upscale", type=float, default=1.4)
|
| 109 |
+
ap.add_argument("--long-side", type=int, default=512)
|
| 110 |
+
ap.add_argument("--multiple", type=int, default=64)
|
| 111 |
+
ap.add_argument("--seed", type=int, default=0)
|
| 112 |
+
ap.add_argument("--out", default=str(Path.home() / "dataset" / "testset_anatomy"))
|
| 113 |
+
args = ap.parse_args()
|
| 114 |
+
|
| 115 |
+
global BUCKETS
|
| 116 |
+
BUCKETS = buckets(args.long_side, args.multiple)
|
| 117 |
+
out = Path(args.out)
|
| 118 |
+
for sub in ("train", "eval"):
|
| 119 |
+
(out / sub).mkdir(parents=True, exist_ok=True)
|
| 120 |
+
|
| 121 |
+
caps = sorted(SRC.glob("*.txt"))
|
| 122 |
+
rng = random.Random(args.seed)
|
| 123 |
+
rng.shuffle(caps)
|
| 124 |
+
|
| 125 |
+
seen_prefix: set[str] = set()
|
| 126 |
+
picked: dict[str, list[tuple[Path, str]]] = {c: [] for c in CLASSES}
|
| 127 |
+
need = args.n + args.eval_n
|
| 128 |
+
for f in caps:
|
| 129 |
+
cap = " ".join(f.read_text().split())
|
| 130 |
+
if len(cap) < 40:
|
| 131 |
+
continue
|
| 132 |
+
cls = classify(cap)
|
| 133 |
+
if cls is None or len(picked[cls]) >= need:
|
| 134 |
+
continue
|
| 135 |
+
low = cap.lower()
|
| 136 |
+
if cls == "cat" and re.search(r"\b(dog|puppy|kitten|rabbit)\b", low) and "cat" not in low:
|
| 137 |
+
continue
|
| 138 |
+
if cls in ("woman", "man") and not any(w in low for w in BODY_WORDS):
|
| 139 |
+
continue
|
| 140 |
+
key = cap[:60].lower()
|
| 141 |
+
if key in seen_prefix:
|
| 142 |
+
continue
|
| 143 |
+
img = f.with_suffix(".jpg")
|
| 144 |
+
if not img.exists():
|
| 145 |
+
continue
|
| 146 |
+
seen_prefix.add(key)
|
| 147 |
+
picked[cls].append((img, cap))
|
| 148 |
+
if all(len(v) >= need for v in picked.values()):
|
| 149 |
+
break
|
| 150 |
+
|
| 151 |
+
rows = []
|
| 152 |
+
for cls, items in picked.items():
|
| 153 |
+
for i, (img, cap) in enumerate(items):
|
| 154 |
+
split = "eval" if i < args.eval_n else "train"
|
| 155 |
+
im, bucket = load_bucketed(img)
|
| 156 |
+
if im is None:
|
| 157 |
+
continue
|
| 158 |
+
name = f"{cls}_{i:03d}"
|
| 159 |
+
im.save(out / split / f"{name}.jpg", quality=92)
|
| 160 |
+
(out / split / f"{name}.txt").write_text(cap + "\n")
|
| 161 |
+
rows.append({"split": split, "class": cls, "name": name, "w": im.width, "h": im.height,
|
| 162 |
+
"bucket": bucket, "caption": cap})
|
| 163 |
+
print(f" {split}/{name} {im.width}x{im.height} | {cap[:80]}")
|
| 164 |
+
|
| 165 |
+
with open(out / "index.csv", "w", newline="") as fh:
|
| 166 |
+
w = csv.DictWriter(fh, fieldnames=["split", "class", "name", "w", "h", "bucket", "caption"])
|
| 167 |
+
w.writeheader(); w.writerows(rows)
|
| 168 |
+
per = {c: sum(1 for r in rows if r["class"] == c and r["split"] == "train") for c in CLASSES}
|
| 169 |
+
print(f"\nитого: {sum(per.values())} train {per} + {sum(1 for r in rows if r['split']=='eval')} eval"
|
| 170 |
+
f" -> {out}")
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
if __name__ == "__main__":
|
| 174 |
+
main()
|
src/model.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""UDiT — пирамидальный U-трансформер: тело (латентный поток) + хвост (декодер AE).
|
| 2 |
+
|
| 3 |
+
Идея: ширина растёт там, где токенов мало. Узко на высоком разрешении, широко внизу,
|
| 4 |
+
как в U-Net'ах SD (320/640/1280 при 64/32/16), а не 1024 на всех уровнях.
|
| 5 |
+
|
| 6 |
+
512², латент f8 (64×36 = 2304 токена):
|
| 7 |
+
L0 ÷1 2304 tok C=192 conv-блоки (depthwise 3×3 + expand 4×) 6 enc / 4 dec
|
| 8 |
+
L1 ÷2 576 tok C=512 трансформер + cross-attn к тексту 4 enc / 3 dec
|
| 9 |
+
L2 ÷4 144 tok C=1024 трансформер, полное внимание 3 enc / 2 dec
|
| 10 |
+
L3 ÷8 36 tok C=1536 трансформер, полное внимание 2 (боттлнек)
|
| 11 |
+
|
| 12 |
+
Скипы — аддитивные (SD-стиль): после e0/e1/e2 признаки прибавляются в декодере.
|
| 13 |
+
Переходы: mean-pool 2× + Linear вниз, Linear + nearest 2× вверх.
|
| 14 |
+
Оценка: ~160M параметров и ~70 GFLOP против ~580 GFLOP у плоского 1024-стека.
|
| 15 |
+
|
| 16 |
+
Хвост — декодер AE (рендер латента в пиксели), текст — Ettin + SliceMixer (из `text.py`).
|
| 17 |
+
"""
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
import math
|
| 21 |
+
import sys
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
import torch.nn as nn
|
| 26 |
+
import torch.nn.functional as F
|
| 27 |
+
|
| 28 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 29 |
+
sys.path.insert(0, str(ROOT / "vae"))
|
| 30 |
+
|
| 31 |
+
from ae.model import AutoEncoder # noqa: E402
|
| 32 |
+
|
| 33 |
+
DEV = "cuda" if torch.cuda.is_available() else "cpu"
|
| 34 |
+
_ROPE_CACHE: dict = {}
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def t_embed(t: torch.Tensor, dim: int = 256) -> torch.Tensor:
|
| 38 |
+
"""Синусоидальный эмбеддинг времени (t в [0,1] умножается на 1000)."""
|
| 39 |
+
half = dim // 2
|
| 40 |
+
f = torch.exp(-math.log(10000.0) * torch.arange(half, device=t.device, dtype=torch.float32) / half)
|
| 41 |
+
a = (t.float()[:, None] * 1000.0) * f[None]
|
| 42 |
+
return torch.cat([a.sin(), a.cos()], -1)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def body_rope(q, k, gh, gw, base: float = 100.0):
|
| 46 |
+
"""2D RoPE: 4 четверти head_dim, две крутятся по x, две по y. Фаза — в латентных пикселях."""
|
| 47 |
+
key = (gh, gw, q.shape[-1], q.device, base)
|
| 48 |
+
tabs = _ROPE_CACHE.get(key)
|
| 49 |
+
if tabs is None:
|
| 50 |
+
dq = q.shape[-1] // 4
|
| 51 |
+
fr = torch.exp(-math.log(base) * torch.arange(dq, device=q.device, dtype=torch.float32) / dq)
|
| 52 |
+
yy = torch.arange(gh, device=q.device).repeat_interleave(gw).float()
|
| 53 |
+
xx = torch.arange(gw, device=q.device).repeat(gh).float()
|
| 54 |
+
ax, ay = xx[:, None] * fr, yy[:, None] * fr
|
| 55 |
+
tabs = (ax.cos()[None, None], ax.sin()[None, None], ay.cos()[None, None], ay.sin()[None, None])
|
| 56 |
+
_ROPE_CACHE[key] = tabs
|
| 57 |
+
cx, sx, cy, sy = tabs
|
| 58 |
+
dq = q.shape[-1] // 4
|
| 59 |
+
|
| 60 |
+
def rot(t):
|
| 61 |
+
a, b, c, e = t[..., :dq], t[..., dq:2 * dq], t[..., 2 * dq:3 * dq], t[..., 3 * dq:]
|
| 62 |
+
return torch.cat([a * cx - b * sx, a * sx + b * cx, c * cy - e * sy, c * sy + e * cy], dim=-1)
|
| 63 |
+
|
| 64 |
+
return rot(q), rot(k)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class RMSNorm(nn.Module):
|
| 68 |
+
def __init__(self, d: int, eps: float = 1e-6):
|
| 69 |
+
super().__init__()
|
| 70 |
+
self.d, self.eps = d, eps
|
| 71 |
+
self.scale = nn.Parameter(torch.zeros(d))
|
| 72 |
+
|
| 73 |
+
def forward(self, x):
|
| 74 |
+
return F.rms_norm(x.float(), (self.d,), weight=(self.scale.float() + 1.0), eps=self.eps).to(x.dtype)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class ConvBlock(nn.Module):
|
| 78 |
+
"""ConvNeXt-ish: RMSNorm → depthwise 3×3 → expand 4× → GELU → project, всё под adaLN-гейтом."""
|
| 79 |
+
|
| 80 |
+
def __init__(self, c: int, expand: int = 4, text_dim: int | None = None, heads: int = 6):
|
| 81 |
+
super().__init__()
|
| 82 |
+
self.c, self.hid, self.h, self.hd = c, c * expand, heads, c // heads
|
| 83 |
+
self.norm = RMSNorm(c)
|
| 84 |
+
self.norm_x = RMSNorm(c)
|
| 85 |
+
self.dw = nn.Conv2d(c, c, 3, padding=1, groups=c)
|
| 86 |
+
self.up = nn.Linear(c, self.hid)
|
| 87 |
+
self.down = nn.Linear(self.hid, c)
|
| 88 |
+
self.mod = nn.Parameter(torch.zeros(5, c)) # shift, scale, gate | shift_x, scale_x
|
| 89 |
+
self.xatt = text_dim is not None
|
| 90 |
+
if self.xatt: # текст на мелком уровне: не экономим
|
| 91 |
+
self.xq, self.xo = nn.Linear(c, c, bias=False), nn.Linear(c, c, bias=False)
|
| 92 |
+
nn.init.zeros_(self.down.weight); nn.init.zeros_(self.down.bias)
|
| 93 |
+
|
| 94 |
+
def forward(self, x, vec, gh, gw, text=None, text_mask=None):
|
| 95 |
+
B = x.shape[0]
|
| 96 |
+
shift, scale, gate, shift_x, scale_x = (self.mod + vec).unbind(1)
|
| 97 |
+
h = self.norm(x) * (1 + scale[:, None]) + shift[:, None]
|
| 98 |
+
h = self.dw(h.transpose(1, 2).reshape(B, self.c, gh, gw)).flatten(2).transpose(1, 2)
|
| 99 |
+
x = x + gate[:, None] * self.down(F.gelu(self.up(h)))
|
| 100 |
+
if self.xatt and text is not None:
|
| 101 |
+
N = x.shape[1]
|
| 102 |
+
qq = self.xq(self.norm_x(x) * (1 + scale_x[:, None]) + shift_x[:, None])
|
| 103 |
+
qq = qq.reshape(B, N, self.h, self.hd).transpose(1, 2)
|
| 104 |
+
kk, vv = text
|
| 105 |
+
mask = text_mask[:, None, None, :] if text_mask is not None else None
|
| 106 |
+
xa = F.scaled_dot_product_attention(qq, kk, vv, attn_mask=mask)
|
| 107 |
+
x = x + self.xo(xa.transpose(1, 2).reshape(B, N, self.c))
|
| 108 |
+
return x
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class AttnBlock(nn.Module):
|
| 112 |
+
"""Pre-norm трансформер: self-attention с 2D RoPE + (опц.) cross-attn к тексту + FFN."""
|
| 113 |
+
|
| 114 |
+
def __init__(self, c: int, heads: int, xatt_dim: int | None, mlp: float = 2.0):
|
| 115 |
+
super().__init__()
|
| 116 |
+
self.c, self.h, self.hd = c, heads, c // heads
|
| 117 |
+
self.n1, self.n2, self.n3 = RMSNorm(c), RMSNorm(c), RMSNorm(c)
|
| 118 |
+
self.qkv = nn.Linear(c, 3 * c, bias=False)
|
| 119 |
+
self.o = nn.Linear(c, c, bias=False)
|
| 120 |
+
self.up, self.down = nn.Linear(c, int(c * mlp)), nn.Linear(int(c * mlp), c)
|
| 121 |
+
self.mod = nn.Parameter(torch.zeros(6, c))
|
| 122 |
+
self.xatt = xatt_dim is not None
|
| 123 |
+
if self.xatt:
|
| 124 |
+
self.xq, self.xo = nn.Linear(c, c, bias=False), nn.Linear(c, c, bias=False)
|
| 125 |
+
nn.init.zeros_(self.down.weight); nn.init.zeros_(self.down.bias)
|
| 126 |
+
|
| 127 |
+
def forward(self, x, vec, gh, gw, text=None, text_mask=None):
|
| 128 |
+
B, N, C = x.shape
|
| 129 |
+
s1, g1, s2, g2, s3, g3 = (self.mod + vec).unbind(1)
|
| 130 |
+
h = self.n1(x) * (1 + s1[:, None])
|
| 131 |
+
q, k, v = self.qkv(h).reshape(B, N, 3, self.h, self.hd).permute(2, 0, 3, 1, 4)
|
| 132 |
+
q, k = body_rope(q, k, gh, gw)
|
| 133 |
+
a = F.scaled_dot_product_attention(q, k, v).transpose(1, 2).reshape(B, N, C)
|
| 134 |
+
x = x + g1[:, None] * self.o(a)
|
| 135 |
+
if self.xatt and text is not None:
|
| 136 |
+
qq = self.xq(self.n2(x)).reshape(B, N, self.h, self.hd).transpose(1, 2)
|
| 137 |
+
kk, vv = text
|
| 138 |
+
mask = text_mask[:, None, None, :] if text_mask is not None else None
|
| 139 |
+
xa = F.scaled_dot_product_attention(qq, kk, vv, attn_mask=mask)
|
| 140 |
+
x = x + g2[:, None] * self.xo(xa.transpose(1, 2).reshape(B, N, C))
|
| 141 |
+
h = self.n3(x) * (1 + s3[:, None])
|
| 142 |
+
return x + g3[:, None] * self.down(F.gelu(self.up(h)))
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
class Body(nn.Module):
|
| 146 |
+
"""U-стек: ширина 192 → 512 → 1024 → 1536 с аддитивными скипами."""
|
| 147 |
+
|
| 148 |
+
def __init__(self, latent_ch: int = 32, text_dim: int = 768,
|
| 149 |
+
widths: tuple = (192, 512, 1024, 1536), enc: tuple = (6, 4, 3, 2),
|
| 150 |
+
dec: tuple = (4, 3, 2), xatt: bool = True):
|
| 151 |
+
super().__init__()
|
| 152 |
+
w0, w1, w2, w3 = widths
|
| 153 |
+
self.widths, self.enc, self.dec = widths, enc, dec
|
| 154 |
+
heads = {0: 6, 1: 8, 2: 8, 3: 8}
|
| 155 |
+
self.stem = nn.Linear(latent_ch, w0)
|
| 156 |
+
self.e0 = nn.ModuleList([ConvBlock(w0, text_dim=text_dim if xatt else None) for _ in range(enc[0])])
|
| 157 |
+
self.e0a = AttnBlock(w0, 6, text_dim if xatt else None)
|
| 158 |
+
self.d0a = AttnBlock(w0, 6, text_dim if xatt else None)
|
| 159 |
+
self.e1 = nn.ModuleList([AttnBlock(w1, heads[1], text_dim if xatt else None) for _ in range(enc[1])])
|
| 160 |
+
self.e2 = nn.ModuleList([AttnBlock(w2, heads[2], text_dim if xatt else None) for _ in range(enc[2])])
|
| 161 |
+
self.e3 = nn.ModuleList([AttnBlock(w3, heads[3], text_dim if xatt else None) for _ in range(enc[3])])
|
| 162 |
+
self.d2 = nn.ModuleList([AttnBlock(w2, heads[2], text_dim if xatt else None) for _ in range(dec[2])])
|
| 163 |
+
self.d1 = nn.ModuleList([AttnBlock(w1, heads[1], text_dim if xatt else None) for _ in range(dec[1])])
|
| 164 |
+
self.d0 = nn.ModuleList([ConvBlock(w0, text_dim=text_dim if xatt else None) for _ in range(dec[0])])
|
| 165 |
+
self.dn01, self.dn12, self.dn23 = nn.Linear(w0, w1), nn.Linear(w1, w2), nn.Linear(w2, w3)
|
| 166 |
+
self.up32, self.up21, self.up10 = nn.Linear(w3, w2), nn.Linear(w2, w1), nn.Linear(w1, w0)
|
| 167 |
+
self.tmlp = nn.Sequential(nn.Linear(256, 512), nn.SiLU(), nn.Linear(512, 512))
|
| 168 |
+
self.pmlp = nn.Linear(text_dim, 512)
|
| 169 |
+
self.ada = nn.ModuleDict({f"l{i}": nn.Sequential(nn.SiLU(), nn.Linear(512, (5 if i == 0 else 6) * w))
|
| 170 |
+
for i, w in enumerate(widths)})
|
| 171 |
+
self.ada["l0a"] = nn.Sequential(nn.SiLU(), nn.Linear(512, 6 * w0)) # для self-attn на L0
|
| 172 |
+
self.xk0 = nn.Linear(text_dim, 2 * w0)
|
| 173 |
+
self.xk1, self.xk2, self.xk3 = (nn.Linear(text_dim, 2 * w1), nn.Linear(text_dim, 2 * w2),
|
| 174 |
+
nn.Linear(text_dim, 2 * w3))
|
| 175 |
+
self.out_norm = RMSNorm(w0)
|
| 176 |
+
self.out = nn.Linear(w0, latent_ch)
|
| 177 |
+
nn.init.zeros_(self.out.weight); nn.init.zeros_(self.out.bias)
|
| 178 |
+
|
| 179 |
+
# ---- переходы
|
| 180 |
+
@staticmethod
|
| 181 |
+
def downsample(x, gh, gw, proj):
|
| 182 |
+
B = x.shape[0]
|
| 183 |
+
d = x.view(B, gh // 2, 2, gw // 2, 2, -1).mean(dim=(2, 4))
|
| 184 |
+
return proj(d.reshape(B, (gh // 2) * (gw // 2), -1))
|
| 185 |
+
|
| 186 |
+
@staticmethod
|
| 187 |
+
def upsample(x, gh, gw, proj):
|
| 188 |
+
"""(B, gh*gw, C) -> (B, 4*gh*gw, C') в родительском row-major порядке."""
|
| 189 |
+
B = x.shape[0]
|
| 190 |
+
d = proj(x).view(B, gh, 1, gw, 1, -1).expand(B, gh, 2, gw, 2, proj.out_features)
|
| 191 |
+
return d.reshape(B, 4 * gh * gw, proj.out_features)
|
| 192 |
+
|
| 193 |
+
def _txt(self, proj, text, heads):
|
| 194 |
+
B, L, _ = text.shape
|
| 195 |
+
kk, vv = proj(text).chunk(2, dim=-1)
|
| 196 |
+
hd = kk.shape[-1] // heads
|
| 197 |
+
return (kk.reshape(B, L, heads, hd).transpose(1, 2), vv.reshape(B, L, heads, hd).transpose(1, 2))
|
| 198 |
+
|
| 199 |
+
def forward(self, z, t, text, text_mask, gh=None, gw=None):
|
| 200 |
+
"""gh/gw принимаются для совместимости с интерфейсом UDM и игнорируются."""
|
| 201 |
+
B, C, gh0, gw0 = z.shape
|
| 202 |
+
cond = self.tmlp(t_embed(t, 256)) + self.pmlp(text.mean(1))
|
| 203 |
+
v = [self.ada[f"l{i}"](cond).view(B, 5 if i == 0 else 6, -1) for i in range(4)]
|
| 204 |
+
v0a = self.ada["l0a"](cond).view(B, 6, -1)
|
| 205 |
+
tk0 = self._txt(self.xk0, text, 6)
|
| 206 |
+
tk1 = self._txt(self.xk1, text, 8)
|
| 207 |
+
tk2 = self._txt(self.xk2, text, 8)
|
| 208 |
+
tk3 = self._txt(self.xk3, text, 8)
|
| 209 |
+
|
| 210 |
+
x = self.stem(z.flatten(2).transpose(1, 2))
|
| 211 |
+
for blk in self.e0:
|
| 212 |
+
x = blk(x, v[0], gh0, gw0, tk0, text_mask)
|
| 213 |
+
x = self.e0a(x, v0a, gh0, gw0, tk0, text_mask)
|
| 214 |
+
s0 = x
|
| 215 |
+
g1h, g1w = gh0 // 2, gw0 // 2
|
| 216 |
+
x = self.downsample(x, gh0, gw0, self.dn01)
|
| 217 |
+
for blk in self.e1:
|
| 218 |
+
x = blk(x, v[1], g1h, g1w, tk1, text_mask)
|
| 219 |
+
s1 = x
|
| 220 |
+
g2h, g2w = g1h // 2, g1w // 2
|
| 221 |
+
x = self.downsample(x, g1h, g1w, self.dn12)
|
| 222 |
+
for blk in self.e2:
|
| 223 |
+
x = blk(x, v[2], g2h, g2w, tk2, text_mask)
|
| 224 |
+
s2 = x
|
| 225 |
+
g3h, g3w = g2h // 2, g2w // 2
|
| 226 |
+
x = self.downsample(x, g2h, g2w, self.dn23)
|
| 227 |
+
for blk in self.e3:
|
| 228 |
+
x = blk(x, v[3], g3h, g3w, tk3, text_mask)
|
| 229 |
+
# декодер: скипы аддитивные
|
| 230 |
+
x = self.upsample(x, g3h, g3w, self.up32) + s2
|
| 231 |
+
for blk in self.d2:
|
| 232 |
+
x = blk(x, v[2], g2h, g2w, tk2, text_mask)
|
| 233 |
+
x = self.upsample(x, g2h, g2w, self.up21) + s1
|
| 234 |
+
for blk in self.d1:
|
| 235 |
+
x = blk(x, v[1], g1h, g1w, tk1, text_mask)
|
| 236 |
+
x = self.upsample(x, g1h, g1w, self.up10) + s0
|
| 237 |
+
x = self.d0a(x, v0a, gh0, gw0, tk0, text_mask)
|
| 238 |
+
for blk in self.d0:
|
| 239 |
+
x = blk(x, v[0], gh0, gw0, tk0, text_mask)
|
| 240 |
+
return self.out(self.out_norm(x)).transpose(1, 2).reshape(B, C, gh0, gw0)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
class UDiT(nn.Module):
|
| 244 |
+
"""Пирамидальное тело + хвост-декодер AE; интерфейс как у UDM."""
|
| 245 |
+
|
| 246 |
+
def __init__(self, ae: AutoEncoder, text_dim: int = 768, **kw):
|
| 247 |
+
super().__init__()
|
| 248 |
+
self.ae = ae
|
| 249 |
+
self.body = Body(latent_ch=ae.config.latent_channels, text_dim=text_dim, **kw)
|
| 250 |
+
|
| 251 |
+
def to_raw(self, z):
|
| 252 |
+
return z * self.ae.latent_std + self.ae.latent_mean
|
| 253 |
+
|
| 254 |
+
@torch.no_grad()
|
| 255 |
+
def sample(self, text, text_mask, gh, gw, steps=4, tail_steps=4, generator=None,
|
| 256 |
+
param="x0", cfg=1.0, null_text=None, null_mask=None, clip_norm=0.0):
|
| 257 |
+
dev = self.ae.device
|
| 258 |
+
z = torch.randn(1, self.ae.config.latent_channels, gh, gw, device=dev, generator=generator)
|
| 259 |
+
with torch.autocast(dev.type, torch.bfloat16, enabled=dev.type == "cuda"):
|
| 260 |
+
for i in range(steps):
|
| 261 |
+
t = torch.full((1,), 1.0 - i / steps, device=dev)
|
| 262 |
+
o = self.body(z, t, text, text_mask).float()
|
| 263 |
+
if cfg > 1.0 and null_text is not None:
|
| 264 |
+
ou = self.body(z, t, null_text, null_mask).float()
|
| 265 |
+
o = ou + cfg * (o - ou)
|
| 266 |
+
v = o if param == "vel" else (z - o) / t.clamp(min=1e-2)[:, None, None, None]
|
| 267 |
+
z = z - v / steps
|
| 268 |
+
if clip_norm:
|
| 269 |
+
z = z.clamp(-clip_norm, clip_norm)
|
| 270 |
+
return self.ae.decode_raw(self.to_raw(z), steps=tail_steps, generator=generator)
|
src/publish.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Сборка публикационного дерева: код + доки + веса в fp16.
|
| 3 |
+
|
| 4 |
+
python3 src/publish.py --out tmp/publish # только собрать
|
| 5 |
+
python3 src/publish.py --out tmp/publish --push # собрать и залить (REPO_ID)
|
| 6 |
+
|
| 7 |
+
Локальные fp32-веса остаются на месте: они нужны обучению, а на Hub едет fp16.
|
| 8 |
+
"""
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import argparse
|
| 12 |
+
import json
|
| 13 |
+
import shutil
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
from safetensors.torch import load_file, save_file
|
| 18 |
+
|
| 19 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 20 |
+
REPO_ID = "AiArtLab/ra"
|
| 21 |
+
|
| 22 |
+
COPY = ["README.md", ".gitattributes", ".gitignore", "docs", "src",
|
| 23 |
+
"vae/config.json", "vae/LICENSE", "vae/README.md", "vae/.gitattributes", "vae/example.py",
|
| 24 |
+
"vae/ae", "text_encoder/config.json", "text_encoder/tokenizer.json",
|
| 25 |
+
"text_encoder/tokenizer_config.json", "text_encoder/special_tokens_map.json",
|
| 26 |
+
"text_encoder/README.md", "text_encoder/.gitattributes",
|
| 27 |
+
"transformer/config.json"]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def half_file(src: Path, dst: Path, key_prefix: str = ""):
|
| 31 |
+
tensors = load_file(str(src))
|
| 32 |
+
out, overflow = {}, 0
|
| 33 |
+
for k, v in tensors.items():
|
| 34 |
+
name = f"{key_prefix}{k}" if key_prefix else k
|
| 35 |
+
if v.is_floating_point():
|
| 36 |
+
if v.numel() and float(v.abs().max()) > 65504:
|
| 37 |
+
overflow += 1
|
| 38 |
+
v = v.half()
|
| 39 |
+
out[name] = v.contiguous()
|
| 40 |
+
assert torch.isfinite(out[name]).all(), f"{name}: не-конечные значения после fp16"
|
| 41 |
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
| 42 |
+
save_file(out, str(dst))
|
| 43 |
+
print(f"[fp16] {dst.relative_to(dst.parents[1])}: {len(out)} тензоров, "
|
| 44 |
+
f"{src.stat().st_size / 2**20:.0f} → {dst.stat().st_size / 2**20:.0f} МиБ"
|
| 45 |
+
+ (f", ВНИМАНИЕ: {overflow} тензоров вне диапазона fp16" if overflow else ""), flush=True)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def main():
|
| 49 |
+
ap = argparse.ArgumentParser()
|
| 50 |
+
ap.add_argument("--out", default=str(ROOT / "tmp" / "publish"))
|
| 51 |
+
ap.add_argument("--repo", default=REPO_ID)
|
| 52 |
+
ap.add_argument("--push", action="store_true", help="залить собранное на Hub (публичный репозиторий)")
|
| 53 |
+
args = ap.parse_args()
|
| 54 |
+
out = Path(args.out)
|
| 55 |
+
if out.exists():
|
| 56 |
+
shutil.rmtree(out)
|
| 57 |
+
out.mkdir(parents=True)
|
| 58 |
+
|
| 59 |
+
for rel in COPY:
|
| 60 |
+
src = ROOT / rel
|
| 61 |
+
dst = out / rel
|
| 62 |
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
| 63 |
+
if src.is_dir():
|
| 64 |
+
shutil.copytree(src, dst, ignore=shutil.ignore_patterns("__pycache__", ".ipynb_checkpoints"))
|
| 65 |
+
else:
|
| 66 |
+
shutil.copy2(src, dst)
|
| 67 |
+
|
| 68 |
+
half_file(ROOT / "transformer" / "diffusion_pytorch_model.safetensors",
|
| 69 |
+
out / "transformer" / "diffusion_pytorch_model.safetensors")
|
| 70 |
+
half_file(ROOT / "vae" / "model.safetensors", out / "vae" / "model.safetensors")
|
| 71 |
+
|
| 72 |
+
sd = torch.load(ROOT / "text_encoder" / "pytorch_model.bin", map_location="cpu", weights_only=True)
|
| 73 |
+
sd = {k: v for k, v in sd.items() if not k.startswith(("head.", "decoder."))} # MLM-голова не нужна
|
| 74 |
+
save_file({k: v.half().contiguous() for k, v in sd.items()},
|
| 75 |
+
str(out / "text_encoder" / "model.safetensors"))
|
| 76 |
+
print(f"[fp16] text_encoder/model.safetensors: {len(sd)} тензоров "
|
| 77 |
+
f"({sum(v.numel() for v in sd.values()) / 1e6:.1f}M), "
|
| 78 |
+
f"{(out / 'text_encoder' / 'model.safetensors').stat().st_size / 2**20:.0f} МиБ", flush=True)
|
| 79 |
+
|
| 80 |
+
cfg_path = out / "transformer" / "config.json"
|
| 81 |
+
cfg = json.loads(cfg_path.read_text())
|
| 82 |
+
cfg["storage_dtype"] = "float16"
|
| 83 |
+
cfg_path.write_text(json.dumps(cfg, indent=2, ensure_ascii=False))
|
| 84 |
+
|
| 85 |
+
total = sum(f.stat().st_size for f in out.rglob("*") if f.is_file())
|
| 86 |
+
print(f"[готово] {out}: {total / 2**30:.2f} ГиБ", flush=True)
|
| 87 |
+
|
| 88 |
+
if args.push:
|
| 89 |
+
from huggingface_hub import HfApi
|
| 90 |
+
api = HfApi()
|
| 91 |
+
api.create_repo(args.repo, repo_type="model", private=False, exist_ok=True)
|
| 92 |
+
api.upload_folder(folder_path=str(out), repo_id=args.repo, repo_type="model",
|
| 93 |
+
commit_message=f"Ra fp16 (step {cfg['step']})")
|
| 94 |
+
print(f"[push] https://huggingface.co/{args.repo}", flush=True)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
if __name__ == "__main__":
|
| 98 |
+
main()
|
src/sample.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Сэмпл из transformer/ (или из .pt чекпойнта обучения).
|
| 3 |
+
|
| 4 |
+
python3 src/sample.py --prompt "A photograph of a red fox in fresh snow." --out fox.png
|
| 5 |
+
"""
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import argparse
|
| 9 |
+
import sys
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
import torch
|
| 13 |
+
from PIL import Image
|
| 14 |
+
|
| 15 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 16 |
+
sys.path[:0] = [str(ROOT / "src"), str(ROOT / "vae")]
|
| 17 |
+
|
| 18 |
+
from model import DEV, UDiT # noqa: E402
|
| 19 |
+
from text import EttinText # noqa: E402
|
| 20 |
+
from ae.model import AutoEncoder # noqa: E402
|
| 21 |
+
from export import load_exported # noqa: E402
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def main():
|
| 25 |
+
ap = argparse.ArgumentParser()
|
| 26 |
+
ap.add_argument("--model", default=str(ROOT / "transformer"), help="папка transformer/ или .pt")
|
| 27 |
+
ap.add_argument("--prompt", required=True)
|
| 28 |
+
ap.add_argument("--out", default=str(ROOT / "tmp" / "sample.png"))
|
| 29 |
+
ap.add_argument("--size", type=int, default=256, help="сторона кадра, кратна 64")
|
| 30 |
+
ap.add_argument("--steps", type=int, default=16, help="шагов тела")
|
| 31 |
+
ap.add_argument("--tail-steps", type=int, default=8, help="шагов декодера (хвоста)")
|
| 32 |
+
ap.add_argument("--cfg", type=float, default=2.5)
|
| 33 |
+
ap.add_argument("--seed", type=int, default=0)
|
| 34 |
+
ap.add_argument("--n", type=int, default=1, help="сколько картинок подряд от --seed")
|
| 35 |
+
ap.add_argument("--text-len", type=int, default=192)
|
| 36 |
+
args = ap.parse_args()
|
| 37 |
+
|
| 38 |
+
ae = AutoEncoder.from_pretrained(str(ROOT / "vae"), device=DEV)
|
| 39 |
+
enc = EttinText(str(ROOT / "text_encoder"), DEV, max_len=args.text_len)
|
| 40 |
+
m = UDiT(ae, text_dim=enc.dim).to(DEV)
|
| 41 |
+
|
| 42 |
+
src = Path(args.model)
|
| 43 |
+
if src.is_dir():
|
| 44 |
+
step = load_exported(m, enc, ae, src, DEV).get("step")
|
| 45 |
+
else:
|
| 46 |
+
sd = torch.load(src, map_location=DEV, weights_only=False)
|
| 47 |
+
own = m.body.state_dict()
|
| 48 |
+
m.body.load_state_dict({k: v for k, v in sd["body"].items() if k in own}, strict=False)
|
| 49 |
+
enc.mixer.load_state_dict(sd["mixer"])
|
| 50 |
+
if sd.get("decoder") is not None:
|
| 51 |
+
ae.decoder.load_state_dict(sd["decoder"])
|
| 52 |
+
step = sd.get("step")
|
| 53 |
+
m.eval()
|
| 54 |
+
print(f"[model] {src}, шаг {step}", flush=True)
|
| 55 |
+
|
| 56 |
+
null_hs, null_mk = enc.slices([""], pad_to_len=args.text_len)
|
| 57 |
+
null_t = enc.mix(null_hs.float())
|
| 58 |
+
hs, mk = enc.slices([args.prompt], pad_to_len=args.text_len)
|
| 59 |
+
txt = enc.mix(hs.float())
|
| 60 |
+
|
| 61 |
+
out = Path(args.out)
|
| 62 |
+
out.parent.mkdir(parents=True, exist_ok=True)
|
| 63 |
+
gh = gw = args.size // 8
|
| 64 |
+
for i in range(args.n):
|
| 65 |
+
y = m.sample(txt, mk, gh, gw, steps=args.steps, tail_steps=args.tail_steps, cfg=args.cfg,
|
| 66 |
+
null_text=null_t, null_mask=null_mk,
|
| 67 |
+
generator=torch.Generator(DEV).manual_seed(args.seed + i))
|
| 68 |
+
im = Image.fromarray(((y[0].clamp(-1, 1) + 1) * 127.5).byte().permute(1, 2, 0).cpu().numpy())
|
| 69 |
+
path = out if args.n == 1 else out.with_name(f"{out.stem}_{args.seed + i}{out.suffix}")
|
| 70 |
+
im.save(path)
|
| 71 |
+
print(f"[ok] {path} {im.size[0]}x{im.size[1]}, seed {args.seed + i}", flush=True)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
if __name__ == "__main__":
|
| 75 |
+
main()
|
src/text.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Текстовый путь UDM: замороженный Ettin + обучаемый выбор слоёв (по идее moss/adapter).
|
| 2 |
+
|
| 3 |
+
Почему не один слой. Скрытые состояния растут по норме с глубиной (в Qwen3.5-9B — в 40 раз,
|
| 4 |
+
13 у слоя 2 против 557 у слоя 27), поэтому «взять последний слой» теряет лексику и цифры,
|
| 5 |
+
а «взять ранний» — семантику. В moss/adapter это решили иначе: несколько срезов по K
|
| 6 |
+
нормируются ПОШТУЧНО, идут последовательностью длины K через 2 блока self-attention, затем
|
| 7 |
+
`Linear(K→1)` схлопывает ось срезов, а результат zero-init'ом добавляется к основному пути.
|
| 8 |
+
Тот же приём (плюс 0.8B вместо 9B) дал cos 0.924 к учителю.
|
| 9 |
+
|
| 10 |
+
Здесь то же самое, но цель — не регрессия к чужому пространству, а обусловливание ДиТ:
|
| 11 |
+
выход миксера идёт в `xkv` (кросс-внимание) и в `pmlp` (adaLN).
|
| 12 |
+
|
| 13 |
+
Срезы Ettin-150M: 22 слоя, global attention каждые 3 → берём выходы после глобальных слоёв.
|
| 14 |
+
"""
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import math
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
import torch.nn as nn
|
| 21 |
+
import torch.nn.functional as F
|
| 22 |
+
|
| 23 |
+
# выходы после глобальных слоёв ModernBERT (22 слоя, global_attn_every_n_layers=3)
|
| 24 |
+
ETTIN_150M_SLICES = (2, 5, 8, 11, 14, 17, 20)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class AttnBlock(nn.Module):
|
| 28 |
+
"""Pre-norm self-attention + MLP (для последовательности длины K — оси срезов)."""
|
| 29 |
+
|
| 30 |
+
def __init__(self, d: int, heads: int = 8, ffn_mult: float = 2.0):
|
| 31 |
+
super().__init__()
|
| 32 |
+
self.h = heads
|
| 33 |
+
self.n1, self.n2 = nn.LayerNorm(d), nn.LayerNorm(d)
|
| 34 |
+
self.qkv = nn.Linear(d, 3 * d)
|
| 35 |
+
self.proj = nn.Linear(d, d)
|
| 36 |
+
self.fc1 = nn.Linear(d, int(d * ffn_mult))
|
| 37 |
+
self.fc2 = nn.Linear(int(d * ffn_mult), d)
|
| 38 |
+
|
| 39 |
+
def forward(self, x):
|
| 40 |
+
b, n, d = x.shape
|
| 41 |
+
q, k, v = self.qkv(self.n1(x)).reshape(b, n, 3, self.h, d // self.h).permute(2, 0, 3, 1, 4)
|
| 42 |
+
a = F.scaled_dot_product_attention(q, k, v).transpose(1, 2).reshape(b, n, d)
|
| 43 |
+
x = x + self.proj(a)
|
| 44 |
+
return x + self.fc2(F.gelu(self.fc1(self.n2(x))))
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class SliceMixer(nn.Module):
|
| 48 |
+
"""Выбор слоя: пер-срезный LayerNorm → attention по оси срезов → Linear(K→1) → zero-init out.
|
| 49 |
+
|
| 50 |
+
blocks=0 оставляет только обученные веса слоёв (Linear(K→1) поверх нормированных срезов) —
|
| 51 |
+
дешёвый вариант «какой слой важнее», без внимания между срезами.
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
def __init__(self, n_slices: int, d: int, blocks: int = 2, heads: int = 8):
|
| 55 |
+
super().__init__()
|
| 56 |
+
self.n, self.d = n_slices, d
|
| 57 |
+
self.per = nn.LayerNorm(d, elementwise_affine=False)
|
| 58 |
+
self.blocks = nn.ModuleList([AttnBlock(d, heads) for _ in range(blocks)])
|
| 59 |
+
self.proj = nn.Linear(n_slices, 1, bias=False)
|
| 60 |
+
self.out = nn.Linear(d, d)
|
| 61 |
+
nn.init.zeros_(self.out.weight)
|
| 62 |
+
nn.init.zeros_(self.out.bias)
|
| 63 |
+
|
| 64 |
+
def forward(self, slices): # (B, L, K, D)
|
| 65 |
+
b, l, k, d = slices.shape
|
| 66 |
+
normed = self.per(slices) # пер-срезная нормировка ОБЯЗАТЕЛЬНА:
|
| 67 |
+
h = normed.reshape(b * l, k, d) # сырые нормы срезов растут с глубиной (52…548)
|
| 68 |
+
for blk in self.blocks:
|
| 69 |
+
h = blk(h)
|
| 70 |
+
mixed = self.out(self.proj(h.permute(0, 2, 1)).squeeze(-1)).reshape(b, l, d)
|
| 71 |
+
return normed.mean(2) + mixed # среднее по нормированным срезам + zero-init добавка
|
| 72 |
+
|
| 73 |
+
def layer_weights(self) -> torch.Tensor:
|
| 74 |
+
"""Что микшер выучил: ненормированные веса слоёв (для логов)."""
|
| 75 |
+
return self.proj.weight.detach().squeeze()
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class EttinText(nn.Module):
|
| 79 |
+
"""Замороженный энкодер Ettin-150M + обучаемый SliceMixer. Возвращает (B,L,768) и маску."""
|
| 80 |
+
|
| 81 |
+
def __init__(self, path: str, device, max_len: int = 192, slices=ETTIN_150M_SLICES,
|
| 82 |
+
blocks: int = 2, dtype=torch.bfloat16):
|
| 83 |
+
super().__init__()
|
| 84 |
+
from transformers import AutoModel, AutoTokenizer
|
| 85 |
+
self.max_len, self.layer_idx = max_len, tuple(slices)
|
| 86 |
+
self.tok = AutoTokenizer.from_pretrained(path)
|
| 87 |
+
self.model = AutoModel.from_pretrained(path, dtype=dtype).to(device).eval().requires_grad_(False)
|
| 88 |
+
self.dim = self.model.config.hidden_size
|
| 89 |
+
self.mixer = SliceMixer(len(self.layer_idx), self.dim, blocks=blocks).to(device)
|
| 90 |
+
self.device = torch.device(device)
|
| 91 |
+
|
| 92 |
+
def tokenize(self, caps: list[str], pad_to: int = 8, pad_to_len: int | None = None):
|
| 93 |
+
# padding="longest" (а не до max_len): короткие капшены стоят дешевле
|
| 94 |
+
t = self.tok(caps, padding="longest", truncation=True, max_length=self.max_len,
|
| 95 |
+
return_tensors="pt")
|
| 96 |
+
ids, mask = t["input_ids"], t["attention_mask"].bool()
|
| 97 |
+
into = pad_to_len or ids.shape[1]
|
| 98 |
+
extra = into - ids.shape[1]
|
| 99 |
+
if extra > 0:
|
| 100 |
+
ids = F.pad(ids, (0, extra), value=self.tok.pad_token_id)
|
| 101 |
+
mask = F.pad(mask, (0, extra))
|
| 102 |
+
elif extra < 0:
|
| 103 |
+
ids, mask = ids[:, :into], mask[:, :into]
|
| 104 |
+
else:
|
| 105 |
+
extra = (-ids.shape[1]) % pad_to
|
| 106 |
+
if extra:
|
| 107 |
+
ids = F.pad(ids, (0, extra), value=self.tok.pad_token_id)
|
| 108 |
+
mask = F.pad(mask, (0, extra))
|
| 109 |
+
return ids.to(self.device), mask.to(self.device)
|
| 110 |
+
|
| 111 |
+
@torch.no_grad()
|
| 112 |
+
def slices(self, caps: list[str], pad_to_len: int | None = None):
|
| 113 |
+
"""Срезы слоёв — считаются ОДИН раз на капшен (энкодер заморожен)."""
|
| 114 |
+
ids, mask = self.tokenize(caps, pad_to_len=pad_to_len)
|
| 115 |
+
out = self.model(input_ids=ids, attention_mask=mask, output_hidden_states=True)
|
| 116 |
+
return torch.stack([out.hidden_states[i] for i in self.layer_idx], dim=2), mask # (B,L,K,D)
|
| 117 |
+
|
| 118 |
+
def mix(self, hs):
|
| 119 |
+
"""Обучаемый путь: среднее по нормированным срезам + zero-init добавка микшера."""
|
| 120 |
+
return self.mixer(hs.float())
|
| 121 |
+
|
| 122 |
+
def forward(self, caps: list[str]):
|
| 123 |
+
hs, mask = self.slices(caps)
|
| 124 |
+
return self.mix(hs), mask, hs
|
| 125 |
+
|
| 126 |
+
def trainable(self):
|
| 127 |
+
return list(self.mixer.parameters())
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def empty_text(enc: EttinText):
|
| 131 |
+
"""Пустой промпт = [CLS][SEP] — безусловный контекст для CFG."""
|
| 132 |
+
return enc([""])
|
src/train.py
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Обучение UDiT на папке «картинка + .txt». Квадратные кадры (--size), кэш латентов и текста.
|
| 3 |
+
|
| 4 |
+
python3 src/train.py --data ~/dataset/butterflies_256sq/train \
|
| 5 |
+
--eval ~/dataset/butterflies_256sq/eval --epochs 80 --batch 8
|
| 6 |
+
|
| 7 |
+
Что внутри (перенесено из привычек agat/train.py):
|
| 8 |
+
* бюджет в эпохах (`--epochs`) или шагах (`--steps`), lr: прогрев → cosine → полка;
|
| 9 |
+
* AdamW8bit (bitsandbytes), если он есть, иначе обычный AdamW — печатается, что взято;
|
| 10 |
+
* дропаут подписи (`--drop-text`) для CFG и дропаут слов (`--drop-word`) внутри подписи;
|
| 11 |
+
* сэмплы на старте (шаг 0), каждые `--sample-every`; держим `--keep` последних чекпойнтов и
|
| 12 |
+
`--samples-keep` сеток; сохранение атомарное;
|
| 13 |
+
* `--probe`: подбирает максимальный батч, который влезает в память, и печатает его;
|
| 14 |
+
* EMA сглаживание печатаемого лосса (`--ema-decay`), `--seed` случайный по умолчанию (печатается);
|
| 15 |
+
* метрики: mse_lat (общий, low t<0.3, high t>0.7), mse_pix, switch, tail(z0), hf(gen)/hf(AE), skip;
|
| 16 |
+
* SIGUSR1 печатает стеки всех потоков (видно, ждёт процесс GPU или сеть, без py-spy).
|
| 17 |
+
"""
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
import argparse
|
| 21 |
+
import faulthandler
|
| 22 |
+
import json
|
| 23 |
+
import math
|
| 24 |
+
import os
|
| 25 |
+
import random
|
| 26 |
+
import shutil
|
| 27 |
+
import signal
|
| 28 |
+
import time
|
| 29 |
+
from pathlib import Path
|
| 30 |
+
|
| 31 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 32 |
+
signal.signal(signal.SIGINT, signal.default_int_handler)
|
| 33 |
+
faulthandler.register(signal.SIGUSR1)
|
| 34 |
+
|
| 35 |
+
import torch # noqa: E402
|
| 36 |
+
import torch.nn.functional as F # noqa: E402
|
| 37 |
+
|
| 38 |
+
from model import DEV, UDiT # noqa: E402
|
| 39 |
+
from text import EttinText # noqa: E402
|
| 40 |
+
from ae.model import AutoEncoder, patchify # noqa: E402
|
| 41 |
+
|
| 42 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
# ------------------------------------------------------------------ расписание
|
| 46 |
+
def lr_at(step: int, base: float, min_lr: float, warmup: int, period: int, start: int = 0) -> float:
|
| 47 |
+
"""Прогрев `warmup` шагов от `start`, затем cosine до `min_lr` за `period` шагов."""
|
| 48 |
+
rel = step - start
|
| 49 |
+
if warmup and rel <= warmup:
|
| 50 |
+
return base * min(1.0, rel / max(warmup, 1))
|
| 51 |
+
if period <= warmup:
|
| 52 |
+
return base
|
| 53 |
+
p = min(1.0, (rel - warmup) / (period - warmup))
|
| 54 |
+
return min_lr + (base - min_lr) * 0.5 * (1 + math.cos(math.pi * p))
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class HFImageStream:
|
| 58 |
+
"""Поток parquet-шардов HF: декод/ресайз/кроп в фоновых потоках, обучение не ждёт диск.
|
| 59 |
+
|
| 60 |
+
Латент и текст НЕ кэшируются (каждая картинка встречается один раз), поэтому шаг дороже,
|
| 61 |
+
чем на папке: +3-4 мс на картинку у AE и +1.2 мс у Ettin.
|
| 62 |
+
"""
|
| 63 |
+
|
| 64 |
+
def __init__(self, dataset, parts, size, fields, lo, hi, shuffle_buffer=1000, prefetch=48,
|
| 65 |
+
workers=8, seed=0):
|
| 66 |
+
import queue
|
| 67 |
+
self.q, self.stop = queue.Queue(maxsize=prefetch), False
|
| 68 |
+
self.size, self.fields, self.lo, self.hi = size, fields, lo, hi
|
| 69 |
+
self.dataset, self.parts = dataset, parts
|
| 70 |
+
self.buffer, self.workers, self.seed = shuffle_buffer, workers, seed
|
| 71 |
+
import threading
|
| 72 |
+
threading.Thread(target=self._loop, daemon=True).start()
|
| 73 |
+
|
| 74 |
+
def _items(self):
|
| 75 |
+
"""Шарды всех частей перемешиваются между собой (иначе эпоха идёт блоками по частям)."""
|
| 76 |
+
import random
|
| 77 |
+
from datasets import load_dataset
|
| 78 |
+
from huggingface_hub import list_repo_files
|
| 79 |
+
repo = list_repo_files(self.dataset, repo_type="dataset")
|
| 80 |
+
files = [f for f in repo
|
| 81 |
+
if f.endswith(".parquet") and any(f.startswith(p + "/") for p in self.parts)]
|
| 82 |
+
if not files: # запасной путь, если листинг не отдал файлы
|
| 83 |
+
files = [f"hf://datasets/{self.dataset}/{p}/*.parquet" for p in self.parts]
|
| 84 |
+
else:
|
| 85 |
+
random.Random(self.seed).shuffle(files)
|
| 86 |
+
files = [f"hf://datasets/{self.dataset}/{f}" for f in files]
|
| 87 |
+
print(f"[stream] шардов: {len(files)} из частей {self.parts}", flush=True)
|
| 88 |
+
ds = load_dataset("parquet", data_files=files, split="train", streaming=True)
|
| 89 |
+
return ds.shuffle(buffer_size=self.buffer, seed=self.seed)
|
| 90 |
+
|
| 91 |
+
def _prepare(self, item):
|
| 92 |
+
from PIL import Image
|
| 93 |
+
import numpy as np
|
| 94 |
+
im = item["image"]
|
| 95 |
+
if im.mode != "RGB":
|
| 96 |
+
im = im.convert("RGB")
|
| 97 |
+
w, h = im.size
|
| 98 |
+
sc = self.size / min(w, h)
|
| 99 |
+
if abs(sc - 1.0) > 1e-3:
|
| 100 |
+
im = im.resize((max(self.size, round(w * sc)), max(self.size, round(h * sc))), Image.LANCZOS)
|
| 101 |
+
w, h = im.size
|
| 102 |
+
left, top = (w - self.size) // 2, (h - self.size) // 2
|
| 103 |
+
im = im.crop((left, top, left + self.size, top + self.size))
|
| 104 |
+
arr = torch.from_numpy(np.asarray(im, dtype=np.float32)).permute(2, 0, 1) / 127.5 - 1.0
|
| 105 |
+
return arr, self._caption(item)
|
| 106 |
+
|
| 107 |
+
def _caption(self, item):
|
| 108 |
+
clean = lambda x: (x or "").replace("\n", " ").strip() # noqa: E731
|
| 109 |
+
for f in self.fields:
|
| 110 |
+
t = clean(item.get(f))
|
| 111 |
+
if self.lo <= len(t) <= self.hi:
|
| 112 |
+
return t
|
| 113 |
+
return clean(item.get("caption_detail"))
|
| 114 |
+
|
| 115 |
+
def _loop(self):
|
| 116 |
+
import collections
|
| 117 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 118 |
+
with ThreadPoolExecutor(max_workers=self.workers) as pool:
|
| 119 |
+
while not self.stop:
|
| 120 |
+
try:
|
| 121 |
+
it = iter(self._items())
|
| 122 |
+
flying = collections.deque()
|
| 123 |
+
while not self.stop:
|
| 124 |
+
while len(flying) < self.workers:
|
| 125 |
+
flying.append(pool.submit(self._prepare, next(it)))
|
| 126 |
+
self.q.put(flying.popleft().result())
|
| 127 |
+
except Exception as e:
|
| 128 |
+
print(f"[stream] сбой ({type(e).__name__}: {e}), переподключение через 5 с", flush=True)
|
| 129 |
+
time.sleep(5)
|
| 130 |
+
|
| 131 |
+
def batch(self, n):
|
| 132 |
+
imgs, caps = [], []
|
| 133 |
+
while len(imgs) < n:
|
| 134 |
+
arr, cap = self.q.get()
|
| 135 |
+
imgs.append(arr); caps.append(cap)
|
| 136 |
+
return torch.stack(imgs), caps
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def hf_energy(t: torch.Tensor) -> float:
|
| 140 |
+
k = torch.tensor([[0.0, 1.0, 0.0], [1.0, -4.0, 1.0], [0.0, 1.0, 0.0]], device=t.device)
|
| 141 |
+
return F.conv2d(t.float().mean(1, keepdim=True), k.view(1, 1, 3, 3), padding=1).std().item()
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def psnr(a, b) -> float:
|
| 145 |
+
return 10 * math.log10(4 / max((a.clamp(-1, 1) - b).pow(2).mean().item(), 1e-12))
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def rotate(path: Path, keep: int, pattern: str):
|
| 149 |
+
"""Оставить `keep` последних файлов, совпадающих с pattern (по номеру шага в имени)."""
|
| 150 |
+
files = sorted(path.glob(pattern))
|
| 151 |
+
for f in files[:-keep]:
|
| 152 |
+
f.unlink()
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def main():
|
| 156 |
+
ap = argparse.ArgumentParser(description="UDiT: обучение на папке пар <имя>.jpg + <имя>.txt")
|
| 157 |
+
ap.add_argument("--data", default=str(Path.home() / "dataset" / "butterflies_256sq" / "train"))
|
| 158 |
+
ap.add_argument("--eval", default=str(Path.home() / "dataset" / "butterflies_256sq" / "eval"))
|
| 159 |
+
ap.add_argument("--limit", type=int, default=0, help="взять только первые N картинок (папка)")
|
| 160 |
+
ap.add_argument("--hf-dataset", default="", help="например LucasFang/FLUX-Reason-6M (поток)")
|
| 161 |
+
ap.add_argument("--parts", default="Aesthetics-Part01,Aesthetics-Part02,Imaginative,Text")
|
| 162 |
+
ap.add_argument("--caption-fields", default="caption_entity,caption_composition",
|
| 163 |
+
help="короткие поля подписи по порядку предпочтения (иначе caption_detail)")
|
| 164 |
+
ap.add_argument("--caption-len", default=(40, 400), nargs=2, type=int, metavar=("MIN", "MAX"))
|
| 165 |
+
ap.add_argument("--shuffle-buffer", type=int, default=1000)
|
| 166 |
+
ap.add_argument("--prefetch", type=int, default=48)
|
| 167 |
+
ap.add_argument("--workers", type=int, default=8)
|
| 168 |
+
ap.add_argument("--images-per-epoch", type=int, default=5_890_279,
|
| 169 |
+
help="сколько картинок в эпохе потока (для пересчёта --epochs в шаги)")
|
| 170 |
+
ap.add_argument("--size", type=int, default=256, help="разрешение обучения (кадры квадратные)")
|
| 171 |
+
ap.add_argument("--text-len", type=int, default=192, help="предел длины подписи в токенах")
|
| 172 |
+
ap.add_argument("--epochs", type=float, default=None, help="бюджет в эпохах (приоритетнее --steps)")
|
| 173 |
+
ap.add_argument("--steps", type=int, default=20000, help="бюджет в шагах оптимизатора")
|
| 174 |
+
ap.add_argument("--batch", type=int, default=8)
|
| 175 |
+
ap.add_argument("--accum", type=int, default=1, help="накопление: эффективный батч = batch*accum")
|
| 176 |
+
ap.add_argument("--probe", action="store_true", help="подобрать максимальный батч под память и выйти")
|
| 177 |
+
ap.add_argument("--lr", type=float, default=1e-4)
|
| 178 |
+
ap.add_argument("--lr-warmup", type=int, default=500)
|
| 179 |
+
ap.add_argument("--lr-min", type=float, default=None, help="нижняя точка cosine (по умолчанию 10%% от --lr)")
|
| 180 |
+
ap.add_argument("--lr-period", type=int, default=0, help="к какому шагу cosine доходит до --lr-min")
|
| 181 |
+
ap.add_argument("--drop-text", type=float, default=0.1, help="доля строк с пустой подписью (CFG)")
|
| 182 |
+
ap.add_argument("--drop-word", type=float, default=0.05, help="доля токенов подписи, скрытых в строке")
|
| 183 |
+
ap.add_argument("--cfg", type=float, default=1.5, help="CFG при показе сэмплов")
|
| 184 |
+
ap.add_argument("--sample-steps", type=int, default=8, help="шагов тела при сэмплинге")
|
| 185 |
+
ap.add_argument("--tail-steps", type=int, default=4, help="шагов декодера AE")
|
| 186 |
+
ap.add_argument("--lr-tail", type=float, default=3e-5)
|
| 187 |
+
ap.add_argument("--tail", choices=["frozen", "train"], default="train")
|
| 188 |
+
ap.add_argument("--init", default=None, help="стартовые веса: чекпойнт или папка transformer/")
|
| 189 |
+
ap.add_argument("--resume", action="store_true", help="продолжить с <out>/last.pt")
|
| 190 |
+
ap.add_argument("--out", default=str(ROOT / "tmp" / "run"))
|
| 191 |
+
ap.add_argument("--log-every", type=int, default=50)
|
| 192 |
+
ap.add_argument("--sample-every", type=int, default=1000, help="0 = не сэмплировать")
|
| 193 |
+
ap.add_argument("--save-every", type=int, default=2000, help="0 = только в конце")
|
| 194 |
+
ap.add_argument("--keep", type=int, default=2, help="сколько чекпойнтов держать")
|
| 195 |
+
ap.add_argument("--samples-keep", type=int, default=4, help="сколько сеток сэмплов держать")
|
| 196 |
+
ap.add_argument("--ema-decay", type=float, default=0.995, help="сглаживание печатаемого лосса")
|
| 197 |
+
ap.add_argument("--seed", type=int, default=None)
|
| 198 |
+
args = ap.parse_args()
|
| 199 |
+
|
| 200 |
+
seed = args.seed if args.seed is not None else random.SystemRandom().randrange(2 ** 31)
|
| 201 |
+
torch.manual_seed(seed)
|
| 202 |
+
print(f"[seed] {seed}", flush=True)
|
| 203 |
+
|
| 204 |
+
out = Path(args.out); (out / "samples").mkdir(parents=True, exist_ok=True)
|
| 205 |
+
(out / "ckpt").mkdir(exist_ok=True)
|
| 206 |
+
log_path = out / "log.csv"
|
| 207 |
+
if not (args.resume and log_path.exists()):
|
| 208 |
+
log_path.write_text("step,mse_lat,low,high,mse_pix,switch,bright,contrast,lat_pc_min,hf_gen,tail_z0,sec\n")
|
| 209 |
+
|
| 210 |
+
from PIL import Image
|
| 211 |
+
import numpy as np
|
| 212 |
+
|
| 213 |
+
def load(path: Path):
|
| 214 |
+
im = Image.open(path).convert("RGB")
|
| 215 |
+
return torch.from_numpy(np.asarray(im, dtype=np.float32)).permute(2, 0, 1) / 127.5 - 1
|
| 216 |
+
|
| 217 |
+
# ---------------------------------------------------------------- данные
|
| 218 |
+
stream = None
|
| 219 |
+
if args.hf_dataset:
|
| 220 |
+
stream = HFImageStream(args.hf_dataset, args.parts.split(","), args.size,
|
| 221 |
+
args.caption_fields.split(","), args.caption_len[0], args.caption_len[1],
|
| 222 |
+
args.shuffle_buffer, args.prefetch, args.workers, seed)
|
| 223 |
+
n = args.images_per_epoch
|
| 224 |
+
per_epoch = max(1, n // (args.batch * args.accum))
|
| 225 |
+
steps = int(round(args.epochs * per_epoch)) if args.epochs else args.steps
|
| 226 |
+
print(f"[data] поток {args.hf_dataset} [{args.parts}], ~{n} картинок/эпоха, "
|
| 227 |
+
f"{per_epoch} шагов/эпоха → бюджет {steps} шагов "
|
| 228 |
+
f"(~{steps * args.batch / 62 / 3600:.1f} ч при 62 картинках/с)", flush=True)
|
| 229 |
+
ae = AutoEncoder.from_pretrained(str(ROOT / "vae"), device=DEV)
|
| 230 |
+
enc = EttinText(str(ROOT / "text_encoder"), DEV, max_len=args.text_len)
|
| 231 |
+
HS = MK = X = Z = None
|
| 232 |
+
HS_E, MK_E = enc.slices([""], pad_to_len=args.text_len)
|
| 233 |
+
HS_E = HS_E.to(torch.bfloat16)
|
| 234 |
+
else:
|
| 235 |
+
files = sorted(Path(args.data).glob("*.jpg")) + sorted(Path(args.data).glob("*.png"))
|
| 236 |
+
if args.limit:
|
| 237 |
+
files = files[: args.limit]
|
| 238 |
+
caps = [f.with_suffix(".txt").read_text().strip() for f in files]
|
| 239 |
+
ae = AutoEncoder.from_pretrained(str(ROOT / "vae"), device=DEV)
|
| 240 |
+
enc = EttinText(str(ROOT / "text_encoder"), DEV, max_len=args.text_len)
|
| 241 |
+
X = torch.stack([load(f) for f in files]).to(DEV)
|
| 242 |
+
with torch.no_grad():
|
| 243 |
+
Z = torch.cat([ae.encode(X[i:i + 8]) for i in range(0, len(X), 8)])
|
| 244 |
+
HS, MK = enc.slices(caps)
|
| 245 |
+
HS, MK = HS.to(torch.bfloat16), MK
|
| 246 |
+
HS_E, MK_E = enc.slices([""], pad_to_len=HS.shape[1])
|
| 247 |
+
HS_E = HS_E.to(torch.bfloat16)
|
| 248 |
+
n = len(files)
|
| 249 |
+
per_epoch = max(1, n // (args.batch * args.accum))
|
| 250 |
+
steps = int(round(args.epochs * per_epoch)) if args.epochs else args.steps
|
| 251 |
+
print(f"[data] {n} пар, латент {tuple(Z.shape[1:])}, текст {tuple(HS.shape[1:])}, "
|
| 252 |
+
f"эпоха = {per_epoch} шагов → бюджет {steps}", flush=True)
|
| 253 |
+
|
| 254 |
+
# ---------------------------------------------------------------- модель и оптимизатор
|
| 255 |
+
m = UDiT(ae, text_dim=enc.dim).to(DEV)
|
| 256 |
+
start = 0
|
| 257 |
+
|
| 258 |
+
def load_body(sd):
|
| 259 |
+
own = m.body.state_dict()
|
| 260 |
+
take = {k: v for k, v in sd.items() if k in own and own[k].shape == v.shape}
|
| 261 |
+
m.body.load_state_dict(take, strict=False)
|
| 262 |
+
return len(take)
|
| 263 |
+
|
| 264 |
+
if args.init:
|
| 265 |
+
p = Path(args.init)
|
| 266 |
+
if p.is_dir():
|
| 267 |
+
from export import load_exported
|
| 268 |
+
cfg = load_exported(m, enc, ae, p, DEV)
|
| 269 |
+
print(f"[init] папка {p}, шаг {cfg.get('step')}", flush=True)
|
| 270 |
+
else:
|
| 271 |
+
sd = torch.load(p, map_location=DEV)
|
| 272 |
+
n_take = load_body(sd["body"]); enc.mixer.load_state_dict(sd["mixer"])
|
| 273 |
+
if sd.get("decoder") is not None and args.tail == "train":
|
| 274 |
+
ae.decoder.load_state_dict(sd["decoder"])
|
| 275 |
+
start = int(sd.get("step", 0))
|
| 276 |
+
print(f"[init] {p}: перенесено {n_take} тензоров, шаг {start}", flush=True)
|
| 277 |
+
if args.resume and (out / "last.pt").exists():
|
| 278 |
+
sd = torch.load(out / "last.pt", map_location=DEV)
|
| 279 |
+
load_body(sd["body"]); enc.mixer.load_state_dict(sd["mixer"])
|
| 280 |
+
if sd.get("decoder") is not None and args.tail == "train":
|
| 281 |
+
ae.decoder.load_state_dict(sd["decoder"])
|
| 282 |
+
start = int(sd.get("step", 0))
|
| 283 |
+
print(f"[resume] {out / 'last.pt'} → шаг {start}", flush=True)
|
| 284 |
+
|
| 285 |
+
n_body = sum(p.numel() for p in m.body.parameters())
|
| 286 |
+
n_tail = sum(p.numel() for p in ae.decoder.parameters())
|
| 287 |
+
print(f"[model] тело {n_body/1e6:.1f}M + миксер {sum(p.numel() for p in enc.trainable())/1e6:.2f}M "
|
| 288 |
+
f"+ хвост {n_tail/1e6:.1f}M [{args.tail}]", flush=True)
|
| 289 |
+
|
| 290 |
+
groups = [{"params": list(m.body.parameters()), "lr": args.lr},
|
| 291 |
+
{"params": enc.trainable(), "lr": args.lr}]
|
| 292 |
+
if args.tail == "train":
|
| 293 |
+
ae.decoder.requires_grad_(True)
|
| 294 |
+
groups.append({"params": list(ae.decoder.parameters()), "lr": args.lr_tail})
|
| 295 |
+
params = [p for g in groups for p in g["params"]]
|
| 296 |
+
try:
|
| 297 |
+
import bitsandbytes as bnb
|
| 298 |
+
opt = bnb.optim.AdamW8bit(groups, betas=(0.9, 0.95), weight_decay=0.0)
|
| 299 |
+
print("[opt] AdamW8bit", flush=True)
|
| 300 |
+
except Exception as e:
|
| 301 |
+
opt = torch.optim.AdamW(groups, betas=(0.9, 0.95), weight_decay=0.0)
|
| 302 |
+
print(f"[opt] AdamW fp32 (8bit недоступен: {type(e).__name__})", flush=True)
|
| 303 |
+
|
| 304 |
+
lr_min = args.lr_min if args.lr_min is not None else args.lr * 0.1
|
| 305 |
+
period = args.lr_period or steps # длина cosine в шагах, отсчёт от start
|
| 306 |
+
print(f"[lr] {args.lr:g}: прогрев {args.lr_warmup} от шага {start}, cosine до {lr_min:g} к шагу {start + period}", flush=True)
|
| 307 |
+
|
| 308 |
+
# ---------------------------------------------------------------- показ сэмплов
|
| 309 |
+
SAMPLE_PROMPTS = [
|
| 310 |
+
"A photograph of a red fox sleeping on a mossy log in a misty forest at dawn.",
|
| 311 |
+
"A city made of glass where rivers of light flow between the towers, cinematic lighting.",
|
| 312 |
+
'A neon sign reading "hello world" mounted on a brick wall at night in the rain.',
|
| 313 |
+
"An impressionist oil painting of a woman with a parasol standing by a lake.",
|
| 314 |
+
]
|
| 315 |
+
show_txt, show_x = [], []
|
| 316 |
+
if stream is not None:
|
| 317 |
+
for cap in SAMPLE_PROMPTS:
|
| 318 |
+
hs, mk = enc.slices([cap])
|
| 319 |
+
show_txt.append((enc.mix(hs.float()), mk))
|
| 320 |
+
else:
|
| 321 |
+
ev_all = sorted(Path(args.eval).glob("*.jpg")) + sorted(Path(args.eval).glob("*.png"))
|
| 322 |
+
ev_cls = {}
|
| 323 |
+
for f in ev_all:
|
| 324 |
+
ev_cls.setdefault(f.stem.split("_")[0], f)
|
| 325 |
+
show = [ev_cls[k] for k in list(ev_cls)[:4]] or ev_all[:4]
|
| 326 |
+
for f in show:
|
| 327 |
+
hs, mk = enc.slices([f.with_suffix(".txt").read_text().strip()], pad_to_len=HS.shape[1])
|
| 328 |
+
show_txt.append((enc.mix(hs.float()), mk))
|
| 329 |
+
show_x.append(load(f)[None].to(DEV))
|
| 330 |
+
|
| 331 |
+
def sample_grid(step):
|
| 332 |
+
tiles, hfs, ys = [], [], []
|
| 333 |
+
for txt, mk in show_txt:
|
| 334 |
+
y = m.sample(txt, mk, lat_hw[0], lat_hw[1], steps=args.sample_steps,
|
| 335 |
+
tail_steps=args.tail_steps, cfg=args.cfg,
|
| 336 |
+
null_text=enc.mix(HS_E.float()), null_mask=MK_E,
|
| 337 |
+
generator=torch.Generator(DEV).manual_seed(0))
|
| 338 |
+
tiles.append(Image.fromarray(((y[0].clamp(-1, 1) + 1) * 127.5).byte()
|
| 339 |
+
.permute(1, 2, 0).cpu().numpy()))
|
| 340 |
+
hfs.append(hf_energy(y)); ys.append(y)
|
| 341 |
+
if show_x:
|
| 342 |
+
ae_rec = ae.decode_raw(ae.encode(show_x[0]) * ae.latent_std + ae.latent_mean,
|
| 343 |
+
steps=args.tail_steps, generator=torch.Generator(DEV).manual_seed(0))
|
| 344 |
+
hf_ae = hf_energy(ae_rec)
|
| 345 |
+
else:
|
| 346 |
+
hf_ae = float("nan")
|
| 347 |
+
W, H = tiles[0].size
|
| 348 |
+
canvas = Image.new("RGB", (W * len(tiles), H), "white")
|
| 349 |
+
for i, t in enumerate(tiles):
|
| 350 |
+
canvas.paste(t, (i * W, 0))
|
| 351 |
+
canvas.save(out / "samples" / "latest.png")
|
| 352 |
+
canvas.save(out / "samples" / f"step_{step:07d}.png")
|
| 353 |
+
rotate(out / "samples", args.samples_keep, "step_*.png")
|
| 354 |
+
bright = float(torch.stack(ys).mean())
|
| 355 |
+
contrast = float(torch.stack(ys).std())
|
| 356 |
+
return hfs[0], hf_ae, canvas, bright, contrast
|
| 357 |
+
|
| 358 |
+
def save(step):
|
| 359 |
+
sd = {"body": {k: v.cpu() for k, v in m.body.state_dict().items()},
|
| 360 |
+
"mixer": {k: v.cpu() for k, v in enc.mixer.state_dict().items()},
|
| 361 |
+
"decoder": ({k: v.cpu() for k, v in ae.decoder.state_dict().items()}
|
| 362 |
+
if args.tail == "train" else None),
|
| 363 |
+
"step": step, "args": vars(args), "seed": seed}
|
| 364 |
+
torch.save(sd, out / "last.pt")
|
| 365 |
+
torch.save(sd, out / "ckpt" / f"step_{step:07d}.pt")
|
| 366 |
+
rotate(out / "ckpt", args.keep, "step_*.pt")
|
| 367 |
+
|
| 368 |
+
# ---------------------------------------------------------------- цикл
|
| 369 |
+
lat_hw = (args.size // 8, args.size // 8)
|
| 370 |
+
pkx = m.ae._packing([lat_hw] * args.batch)
|
| 371 |
+
if args.probe:
|
| 372 |
+
best = args.batch
|
| 373 |
+
for bs in (args.batch, args.batch * 2, args.batch * 4, args.batch * 8):
|
| 374 |
+
try:
|
| 375 |
+
z = torch.randn(bs, 32, lat_hw[0], lat_hw[1], device=DEV)
|
| 376 |
+
t = torch.rand(bs, device=DEV)
|
| 377 |
+
if stream is not None: # в потоке текста нет — берём фиктивный
|
| 378 |
+
hs_probe, mk_probe = enc.slices(["probe caption"] * bs)
|
| 379 |
+
else:
|
| 380 |
+
hs_probe, mk_probe = HS[:bs], MK[:bs]
|
| 381 |
+
with torch.autocast(DEV, torch.bfloat16, enabled=DEV == "cuda"):
|
| 382 |
+
m.body(z, t, enc.mix(hs_probe.float()), mk_probe).float().pow(2).mean().backward()
|
| 383 |
+
best = bs
|
| 384 |
+
m.zero_grad(set_to_none=True)
|
| 385 |
+
except torch.OutOfMemoryError:
|
| 386 |
+
m.zero_grad(set_to_none=True); torch.cuda.empty_cache(); break
|
| 387 |
+
print(f"[probe] максимальный батч ≈ {best} (пик {torch.cuda.max_memory_allocated()/1e9:.1f} ГБ)",
|
| 388 |
+
flush=True)
|
| 389 |
+
if stream is not None:
|
| 390 |
+
stream.stop = True
|
| 391 |
+
import os as _os
|
| 392 |
+
_os._exit(0)
|
| 393 |
+
|
| 394 |
+
if stream is not None:
|
| 395 |
+
def get_batch():
|
| 396 |
+
x, caps = stream.batch(args.batch)
|
| 397 |
+
x = x.to(DEV)
|
| 398 |
+
with torch.no_grad():
|
| 399 |
+
z = ae.encode(x)
|
| 400 |
+
hs, mk = enc.slices(caps)
|
| 401 |
+
return x, z, hs.to(torch.bfloat16), mk
|
| 402 |
+
else:
|
| 403 |
+
def get_batch():
|
| 404 |
+
idx = torch.randint(0, n, (args.batch,), device=DEV)
|
| 405 |
+
return X[idx], Z[idx], HS[idx], MK[idx]
|
| 406 |
+
|
| 407 |
+
hf_gen, hf_ae, _, bright0, contrast0 = sample_grid(start)
|
| 408 |
+
print(f"[sample] шаг {start}: яркость {bright0:+.3f}, контраст {contrast0:.3f}, hf {hf_gen:.3f}", flush=True)
|
| 409 |
+
|
| 410 |
+
t0 = time.perf_counter(); prev_el = t0
|
| 411 |
+
ema = {}
|
| 412 |
+
run = {"lat": 0.0, "low": 0.0, "high": 0.0, "pix": 0.0, "n_low": 0, "n_high": 0, "n": 0}
|
| 413 |
+
skips = 0
|
| 414 |
+
for step in range(start + 1, start + steps + 1):
|
| 415 |
+
opt.zero_grad(set_to_none=True)
|
| 416 |
+
ok = 0
|
| 417 |
+
for _ in range(args.accum):
|
| 418 |
+
x0, z0, hs, mk = get_batch()
|
| 419 |
+
# дропаут подписи (CFG) и дропаут слов внутри подписи
|
| 420 |
+
if args.drop_text:
|
| 421 |
+
drop = torch.rand(args.batch, device=DEV) < args.drop_text
|
| 422 |
+
if drop.any(): # пустая подпись обрезается под длину батча
|
| 423 |
+
L = hs.shape[1]
|
| 424 |
+
hs = torch.where(drop[:, None, None, None], HS_E[:, :L].expand_as(hs), hs)
|
| 425 |
+
mk = torch.where(drop[:, None], MK_E[:, :L].expand_as(mk), mk)
|
| 426 |
+
if args.drop_word:
|
| 427 |
+
word = (torch.rand(mk.shape, device=DEV) < args.drop_word) & mk
|
| 428 |
+
hs = hs.masked_fill(word[:, :, None, None], 0)
|
| 429 |
+
mk = mk & ~word
|
| 430 |
+
with torch.autocast(DEV, torch.bfloat16, enabled=DEV == "cuda"):
|
| 431 |
+
txt = enc.mix(hs.float())
|
| 432 |
+
tb = torch.rand(args.batch, device=DEV)
|
| 433 |
+
eb = torch.randn_like(z0)
|
| 434 |
+
zt = (1 - tb[:, None, None, None]) * z0 + tb[:, None, None, None] * eb
|
| 435 |
+
z_hat = m.body(zt, tb, txt, mk)
|
| 436 |
+
per_elem = (z_hat.float() - z0.float()).pow(2).mean(dim=(1, 2, 3))
|
| 437 |
+
low, high = tb < 0.3, tb > 0.7
|
| 438 |
+
loss_l = per_elem.mean()
|
| 439 |
+
raw = m.to_raw(z0 + (z_hat - z0).detach())
|
| 440 |
+
tx = torch.rand(args.batch, device=DEV)
|
| 441 |
+
ex = torch.randn_like(x0)
|
| 442 |
+
xt = (1 - tx[:, None, None, None]) * x0 + tx[:, None, None, None] * ex
|
| 443 |
+
xt_packed = pkx.pack([patchify(xt[i].to(ae.compute_dtype), 8) for i in range(args.batch)])
|
| 444 |
+
z_packed = pkx.pack([raw[i].flatten(1).transpose(0, 1) for i in range(args.batch)])
|
| 445 |
+
preds = ae.decoder(xt_packed, tx, z_packed, pkx)
|
| 446 |
+
pix_terms = torch.stack([(p.float() - x0[i]).pow(2).mean() for i, p in enumerate(preds)])
|
| 447 |
+
loss_p = pix_terms.mean()
|
| 448 |
+
total = loss_l + loss_p
|
| 449 |
+
if not torch.isfinite(total):
|
| 450 |
+
skips += 1
|
| 451 |
+
continue
|
| 452 |
+
(total / args.accum).backward()
|
| 453 |
+
ok += 1
|
| 454 |
+
run["lat"] += loss_l.item(); run["pix"] += loss_p.item()
|
| 455 |
+
if low.any():
|
| 456 |
+
run["low"] += per_elem[low].mean().item(); run["n_low"] += 1
|
| 457 |
+
if high.any():
|
| 458 |
+
run["high"] += per_elem[high].mean().item(); run["n_high"] += 1
|
| 459 |
+
if not ok:
|
| 460 |
+
print(f" ! шаг {step}: не-конечный лосс, пропуск (всего {skips})", flush=True)
|
| 461 |
+
continue
|
| 462 |
+
run["n"] += 1
|
| 463 |
+
torch.nn.utils.clip_grad_norm_(params, 1.0)
|
| 464 |
+
lr = lr_at(step, args.lr, lr_min, args.lr_warmup, period, start)
|
| 465 |
+
for g, base in zip(opt.param_groups, (args.lr, args.lr, args.lr_tail)):
|
| 466 |
+
g["lr"] = lr * (base / args.lr)
|
| 467 |
+
opt.step()
|
| 468 |
+
|
| 469 |
+
if step % args.log_every == 0:
|
| 470 |
+
d = args.ema_decay
|
| 471 |
+
cur = {"lat": run["lat"] / run["n"], "pix": run["pix"] / run["n"],
|
| 472 |
+
"low": run["low"] / max(run["n_low"], 1), "high": run["high"] / max(run["n_high"], 1)}
|
| 473 |
+
for k, v in cur.items():
|
| 474 |
+
ema[k] = v if k not in ema else d * ema[k] + (1 - d) * v
|
| 475 |
+
el = time.perf_counter() - t0; inc = el - prev_el; prev_el = el
|
| 476 |
+
print(f" {step:6d} | mse_lat {cur['lat']:.4f} (ema {ema['lat']:.4f}; low {cur['low']:.3f} "
|
| 477 |
+
f"high {cur['high']:.3f}) | mse_pix {cur['pix']:.4f} | lr {lr:.1e} | {el:6.0f}s "
|
| 478 |
+
f"({inc / max(run['n'], 1) * 1e3:.0f} мс/шаг) | skip {skips}", flush=True)
|
| 479 |
+
run = {"lat": 0.0, "low": 0.0, "high": 0.0, "pix": 0.0, "n_low": 0, "n_high": 0, "n": 0}
|
| 480 |
+
|
| 481 |
+
if args.sample_every and step % args.sample_every == 0:
|
| 482 |
+
zs = []
|
| 483 |
+
lat_pc_min = float("nan")
|
| 484 |
+
for txt, mk in show_txt[:2]:
|
| 485 |
+
z = torch.randn(1, 32, lat_hw[0], lat_hw[1], device=DEV,
|
| 486 |
+
generator=torch.Generator(DEV).manual_seed(7))
|
| 487 |
+
with torch.no_grad(), torch.autocast(DEV, torch.bfloat16, enabled=DEV == "cuda"):
|
| 488 |
+
for i in range(args.sample_steps):
|
| 489 |
+
t = torch.full((1,), 1.0 - i / args.sample_steps, device=DEV)
|
| 490 |
+
o = m.body(z, t, txt, mk).float()
|
| 491 |
+
ou = m.body(z, t, enc.mix(HS_E.float()), MK_E).float()
|
| 492 |
+
o = ou + args.cfg * (o - ou)
|
| 493 |
+
z = z - (z - o) / t.clamp(min=1e-2)[:, None, None, None] / args.sample_steps
|
| 494 |
+
zs.append(z)
|
| 495 |
+
switch = (((zs[0] - zs[1]).norm() / zs[0].norm()).item() if len(zs) > 1 else float("nan"))
|
| 496 |
+
if zs:
|
| 497 |
+
lat_pc_min = float(zs[0].std(dim=(0, 2, 3)).min()) # сторож сжатия дисперсии латента
|
| 498 |
+
hfg, hfa, canvas, bright, contrast = sample_grid(step)
|
| 499 |
+
if show_x:
|
| 500 |
+
z_ref = ae.encode(show_x[0])
|
| 501 |
+
tail_psnr = psnr(ae.decode_raw(m.to_raw(z_ref), steps=args.tail_steps,
|
| 502 |
+
generator=torch.Generator(DEV).manual_seed(0)), show_x[0])
|
| 503 |
+
else:
|
| 504 |
+
tail_psnr = float("nan")
|
| 505 |
+
el = time.perf_counter() - t0
|
| 506 |
+
print(f" ↳ switch {switch:.3f} | яркость {bright:+.3f} контраст {contrast:.3f} | "
|
| 507 |
+
f"латент: std {lat_pc_min:.2f} (у данных ~0.8–1.3) | hf {hfg:.3f} | tail(z0) {tail_psnr:.2f}",
|
| 508 |
+
flush=True)
|
| 509 |
+
with open(log_path, "a") as fh:
|
| 510 |
+
fh.write(f"{step},{ema.get('lat', 0):.5f},{ema.get('low', 0):.5f},{ema.get('high', 0):.5f},"
|
| 511 |
+
f"{ema.get('pix', 0):.5f},{switch:.4f},{bright:.4f},{contrast:.4f},"
|
| 512 |
+
f"{lat_pc_min:.4f},{hfg:.4f},{tail_psnr:.2f},{el:.1f}\n")
|
| 513 |
+
if args.save_every and step % args.save_every == 0:
|
| 514 |
+
save(step)
|
| 515 |
+
save(start + steps)
|
| 516 |
+
if stream is not None:
|
| 517 |
+
stream.stop = True # фоновые потоки не должны переживать интерпретатор
|
| 518 |
+
print("[done]", flush=True)
|
| 519 |
+
import os as _os
|
| 520 |
+
_os._exit(0) # обойти шумную финализацию streaming-генераторов
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
if __name__ == "__main__":
|
| 524 |
+
main()
|
text_encoder/.gitattributes
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
text_encoder/README.md
ADDED
|
@@ -0,0 +1,971 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
language:
|
| 3 |
+
- en
|
| 4 |
+
license: mit
|
| 5 |
+
pipeline_tags:
|
| 6 |
+
- fill-mask
|
| 7 |
+
- feature-extraction
|
| 8 |
+
library_name: transformers
|
| 9 |
+
tags:
|
| 10 |
+
- modernbert
|
| 11 |
+
- ettin
|
| 12 |
+
- encoder
|
| 13 |
+
- text-embeddings
|
| 14 |
+
- retrieval
|
| 15 |
+
- classification
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
# Ettin: an Open Suite of Paired Encoders and Decoders
|
| 19 |
+
|
| 20 |
+
[](https://opensource.org/licenses/MIT)
|
| 21 |
+
[](https://arxiv.org/abs/2507.11412)
|
| 22 |
+
[](https://huggingface.co/jhu-clsp)
|
| 23 |
+
[](https://huggingface.co/datasets/jhu-clsp)
|
| 24 |
+
[](https://github.com/jhu-clsp/ettin-encoder-vs-decoder)
|
| 25 |
+
|
| 26 |
+
> 🎯 **TL;DR**: State-of-the-art paired encoder and decoder models (17M-1B params) trained identically for fair comparison with open data. Encoders beat ModernBERT. Decoders beat Llama 3.2/SmolLM2.
|
| 27 |
+
|
| 28 |
+
📄 [Paper](https://arxiv.org/abs/2507.11412) | 🚀 [GitHub Repository](https://github.com/jhu-clsp/ettin-encoder-vs-decoder)
|
| 29 |
+
|
| 30 |
+
This model is part of the Ettin suite - the first collection of paired encoder-only and decoder-only models trained with identical data, architecture, and training recipes. Ettin enables fair comparisons between encoder and decoder architectures across multiple scales, providing state-of-the-art performance for open-data models in their respective size categories.
|
| 31 |
+
|
| 32 |
+
## Table of Contents
|
| 33 |
+
- [Performance Highlights](#performance-highlights)
|
| 34 |
+
- [Quick Start](#quick-start)
|
| 35 |
+
- [Model Description](#model-description)
|
| 36 |
+
- [Training Data](#training-data)
|
| 37 |
+
- [Model Family](#model-family)
|
| 38 |
+
- [Encoder Models](#encoder-models)
|
| 39 |
+
- [Decoder Models](#decoder-models)
|
| 40 |
+
- [Cross-Objective Models](#cross-objective-models)
|
| 41 |
+
- [Accessing Training Checkpoints](#accessing-training-checkpoints)
|
| 42 |
+
- [Research Applications](#research-applications)
|
| 43 |
+
- [Training Details](#training-details)
|
| 44 |
+
- [Model Architecture](#model-architecture)
|
| 45 |
+
- [Usage Examples](#usage-examples)
|
| 46 |
+
- [Fine-tuning Examples](#fine-tuning-examples)
|
| 47 |
+
- [Citation](#citation)
|
| 48 |
+
|
| 49 |
+
## 📊 Performance Highlights
|
| 50 |
+
|
| 51 |
+
### Encoder Tasks (vs. ModernBERT)
|
| 52 |
+
- **GLUE Average**: 88.9 vs 88.4 (Base), 90.8 vs 90.4 (Large)
|
| 53 |
+
- **MTEB v2 English Retrieval**: 45.7 vs 43.9 (Base), 48.4 vs 47.0 (Large)
|
| 54 |
+
- **Code Search and Long Context**: Superior performance on CodeSearchNet and MLDR
|
| 55 |
+
|
| 56 |
+
### Decoder Tasks (vs. SmolLM2 & Llama 3.2)
|
| 57 |
+
- **Average Score**: 46.2 vs 45.2 (SmolLM2-135M)
|
| 58 |
+
- **1B Model**: 59.0 vs 56.6 (Llama 3.2-1B)
|
| 59 |
+
- **Generative Tasks**: Competitive across all model sizes
|
| 60 |
+
|
| 61 |
+
### Key Finding
|
| 62 |
+
**Architecture-specific advantages persist**: A 400M encoder outperforms a 1B decoder on classification tasks, while a 400M decoder outperforms a 1B encoder on generation tasks.
|
| 63 |
+
|
| 64 |
+
## 🚀 Quick Start
|
| 65 |
+
|
| 66 |
+
### Installation
|
| 67 |
+
```bash
|
| 68 |
+
pip install torch>=1.9.0
|
| 69 |
+
# until the new pip release, install from main to use decoders (transformers>=4.54.X will contain it)
|
| 70 |
+
# encoders work with transformers>=4.48.0
|
| 71 |
+
pip install git+https://github.com/huggingface/transformers.git
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
### 30-Second Examples
|
| 75 |
+
|
| 76 |
+
**Encoder for Classification/Embeddings:**
|
| 77 |
+
```python
|
| 78 |
+
from transformers import AutoTokenizer, AutoModel
|
| 79 |
+
|
| 80 |
+
tokenizer = AutoTokenizer.from_pretrained("jhu-clsp/ettin-encoder-150m")
|
| 81 |
+
model = AutoModel.from_pretrained("jhu-clsp/ettin-encoder-150m")
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
**Decoder for Text Generation:**
|
| 85 |
+
```python
|
| 86 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 87 |
+
|
| 88 |
+
tokenizer = AutoTokenizer.from_pretrained("jhu-clsp/ettin-decoder-150m")
|
| 89 |
+
model = AutoModelForCausalLM.from_pretrained("jhu-clsp/ettin-decoder-150m")
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
## Model Description
|
| 93 |
+
|
| 94 |
+
Ettin models are designed to provide a foundation for comparing encoder-only and decoder-only architectures. Unlike previous comparisons that were limited by different training data, architectures, and recipes, Ettin models use:
|
| 95 |
+
|
| 96 |
+
1. **Identical training data** - Same high-quality mixture across all models
|
| 97 |
+
2. **Open Training Data** - Data is available now with batch-level training data for each of the 250+ checkpoints
|
| 98 |
+
3. **Matched architectures** - Only differing in attention patterns (bidirectional vs causal) and training objectives (MLM vs CLM)
|
| 99 |
+
4. **Consistent training recipe** - Three-phase training with 2T tokens
|
| 100 |
+
5. **Multiple scales** - From 17M to 1B parameters
|
| 101 |
+
|
| 102 |
+
This approach allows for true apples-to-apples comparisons between encoder and decoder models, revealing the inherent strengths of each architecture.
|
| 103 |
+
|
| 104 |
+
## Training Data
|
| 105 |
+
|
| 106 |
+
The training data is publicly available and split across different phases:
|
| 107 |
+
|
| 108 |
+
- **Pre-training Data**: [jhu-clsp/ettin-pretraining-data](https://huggingface.co/datasets/jhu-clsp/ettin-pretraining-data) - 1.7T tokens of diverse data mixture
|
| 109 |
+
- **Mid-training/Extension Data**: [jhu-clsp/ettin-extension-data](https://huggingface.co/datasets/jhu-clsp/ettin-extension-data) - 250B tokens of higher-quality filtered data
|
| 110 |
+
- **Decay Phase Data**: [jhu-clsp/ettin-decay-data](https://huggingface.co/datasets/jhu-clsp/ettin-decay-data) - 100B tokens of premium data sources
|
| 111 |
+
- **Training Data Order**: [jhu-clsp/ettin-data-order](https://huggingface.co/datasets/jhu-clsp/ettin-data-order) - Batch-level training order (columns: input_ids, step)
|
| 112 |
+
|
| 113 |
+
## Model Family
|
| 114 |
+
|
| 115 |
+
### Encoder Models
|
| 116 |
+
|
| 117 |
+
| Size | Model | Parameters | Best For | Download |
|
| 118 |
+
|:-----|:------|:-----------|:---------|:---------|
|
| 119 |
+
| XXS | [ettin-encoder-17m](https://huggingface.co/jhu-clsp/ettin-encoder-17m) | 17M | Mobile/Edge devices | [](https://huggingface.co/jhu-clsp/ettin-encoder-17m) |
|
| 120 |
+
| XS | [ettin-encoder-32m](https://huggingface.co/jhu-clsp/ettin-encoder-32m) | 32M | Fast inference | [](https://huggingface.co/jhu-clsp/ettin-encoder-32m) |
|
| 121 |
+
| Small | [ettin-encoder-68m](https://huggingface.co/jhu-clsp/ettin-encoder-68m) | 68M | Balanced performance | [](https://huggingface.co/jhu-clsp/ettin-encoder-68m) |
|
| 122 |
+
| Base | [ettin-encoder-150m](https://huggingface.co/jhu-clsp/ettin-encoder-150m) | 150M | Standard use cases | [](https://huggingface.co/jhu-clsp/ettin-encoder-150m) |
|
| 123 |
+
| Large | [ettin-encoder-400m](https://huggingface.co/jhu-clsp/ettin-encoder-400m) | 400M | High accuracy needs | [](https://huggingface.co/jhu-clsp/ettin-encoder-400m) |
|
| 124 |
+
| XL | [ettin-encoder-1b](https://huggingface.co/jhu-clsp/ettin-encoder-1b) | 1B | Best performance | [](https://huggingface.co/jhu-clsp/ettin-encoder-1b) |
|
| 125 |
+
|
| 126 |
+
### Decoder Models
|
| 127 |
+
|
| 128 |
+
| Size | Model | Parameters | Best For | Download |
|
| 129 |
+
|:-----|:------|:-----------|:---------|:---------|
|
| 130 |
+
| XXS | [ettin-decoder-17m](https://huggingface.co/jhu-clsp/ettin-decoder-17m) | 17M | Lightweight generation | [](https://huggingface.co/jhu-clsp/ettin-decoder-17m) |
|
| 131 |
+
| XS | [ettin-decoder-32m](https://huggingface.co/jhu-clsp/ettin-decoder-32m) | 32M | Quick prototyping | [](https://huggingface.co/jhu-clsp/ettin-decoder-32m) |
|
| 132 |
+
| Small | [ettin-decoder-68m](https://huggingface.co/jhu-clsp/ettin-decoder-68m) | 68M | Efficient generation | [](https://huggingface.co/jhu-clsp/ettin-decoder-68m) |
|
| 133 |
+
| Base | [ettin-decoder-150m](https://huggingface.co/jhu-clsp/ettin-decoder-150m) | 150M | Standard generation | [](https://huggingface.co/jhu-clsp/ettin-decoder-150m) |
|
| 134 |
+
| Large | [ettin-decoder-400m](https://huggingface.co/jhu-clsp/ettin-decoder-400m) | 400M | Quality generation | [](https://huggingface.co/jhu-clsp/ettin-decoder-400m) |
|
| 135 |
+
| XL | [ettin-decoder-1b](https://huggingface.co/jhu-clsp/ettin-decoder-1b) | 1B | Best generation | [](https://huggingface.co/jhu-clsp/ettin-decoder-1b) |
|
| 136 |
+
|
| 137 |
+
### Cross-Objective Models
|
| 138 |
+
|
| 139 |
+
These models demonstrate what happens when you continue training encoders as decoders (and vice versa). **Important**: Load these models using the architecture they were *converted to*, not their original architecture.
|
| 140 |
+
|
| 141 |
+
#### Encoders Trained from Decoders (Decoder → MLM)
|
| 142 |
+
**Load as encoders** using `AutoModel` or `AutoModelForMaskedLM`:
|
| 143 |
+
|
| 144 |
+
| Size | Model | Parameters | Description | Download |
|
| 145 |
+
|:-----|:------|:-----------|:------------|:---------|
|
| 146 |
+
| XXS | [ettin-encoder-from-decoder-17m](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-17m) | 17M | Decoder → MLM continued training | [](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-17m) |
|
| 147 |
+
| XS | [ettin-encoder-from-decoder-32m](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-32m) | 32M | Decoder → MLM continued training | [](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-32m) |
|
| 148 |
+
| Small | [ettin-encoder-from-decoder-68m](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-68m) | 68M | Decoder → MLM continued training | [](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-68m) |
|
| 149 |
+
| Base | [ettin-encoder-from-decoder-150m](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-150m) | 150M | Decoder → MLM continued training | [](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-150m) |
|
| 150 |
+
| Large | [ettin-encoder-from-decoder-400m](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-400m) | 400M | Decoder → MLM continued training | [](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-400m) |
|
| 151 |
+
| XL | [ettin-encoder-from-decoder-1b](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-1b) | 1B | Decoder → MLM continued training | [](https://huggingface.co/jhu-clsp/ettin-encoder-from-decoder-1b) |
|
| 152 |
+
|
| 153 |
+
#### Decoders Trained from Encoders (Encoder → CLM)
|
| 154 |
+
**Load as decoders** using `AutoModelForCausalLM`:
|
| 155 |
+
|
| 156 |
+
| Size | Model | Parameters | Description | Download |
|
| 157 |
+
|:-----|:------|:-----------|:------------|:---------|
|
| 158 |
+
| XXS | [ettin-decoder-from-encoder-17m](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-17m) | 17M | Encoder → CLM continued training | [](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-17m) |
|
| 159 |
+
| XS | [ettin-decoder-from-encoder-32m](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-32m) | 32M | Encoder → CLM continued training | [](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-32m) |
|
| 160 |
+
| Small | [ettin-decoder-from-encoder-68m](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-68m) | 68M | Encoder → CLM continued training | [](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-68m) |
|
| 161 |
+
| Base | [ettin-decoder-from-encoder-150m](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-150m) | 150M | Encoder → CLM continued training | [](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-150m) |
|
| 162 |
+
| Large | [ettin-decoder-from-encoder-400m](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-400m) | 400M | Encoder → CLM continued training | [](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-400m) |
|
| 163 |
+
| XL | [ettin-decoder-from-encoder-1b](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-1b) | 1B | Encoder → CLM continued training | [](https://huggingface.co/jhu-clsp/ettin-decoder-from-encoder-1b) |
|
| 164 |
+
|
| 165 |
+
**Example Usage for Cross-Objective Models:**
|
| 166 |
+
```python
|
| 167 |
+
# Encoder-from-decoder: Load as encoder
|
| 168 |
+
from transformers import AutoTokenizer, AutoModel
|
| 169 |
+
tokenizer = AutoTokenizer.from_pretrained("jhu-clsp/ettin-encoder-from-decoder-150m")
|
| 170 |
+
model = AutoModel.from_pretrained("jhu-clsp/ettin-encoder-from-decoder-150m")
|
| 171 |
+
|
| 172 |
+
# Decoder-from-encoder: Load as decoder
|
| 173 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 174 |
+
tokenizer = AutoTokenizer.from_pretrained("jhu-clsp/ettin-decoder-from-encoder-150m")
|
| 175 |
+
model = AutoModelForCausalLM.from_pretrained("jhu-clsp/ettin-decoder-from-encoder-150m")
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
## Accessing Training Checkpoints
|
| 179 |
+
|
| 180 |
+
Beyond the final models listed above, we provide access to intermediate training checkpoints for research and analysis purposes. These checkpoints allow you to study model behavior and performance throughout the training process. You can get the checkpoints either in HF format or raw for continued pre-training (e.g. Composer format).
|
| 181 |
+
|
| 182 |
+
#### Raw Checkpoints
|
| 183 |
+
All raw training checkpoints are available in the [jhu-clsp/ettin-checkpoints](https://huggingface.co/datasets/jhu-clsp/ettin-checkpoints) dataset.
|
| 184 |
+
|
| 185 |
+
#### HuggingFace Format Checkpoints
|
| 186 |
+
Each model repository contains multiple tagged versions representing different training stages:
|
| 187 |
+
|
| 188 |
+
- **`step{number}`** - Pretraining phase checkpoints (e.g., `step599525`, `step596528`)
|
| 189 |
+
- **`ext{number}`** - Extension/mid-training phase checkpoints (e.g., `ext1000`, `ext2000`)
|
| 190 |
+
- **`decay{number}`** - Decay phase checkpoints (e.g., `decay100`, `decay500`)
|
| 191 |
+
|
| 192 |
+
```python
|
| 193 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 194 |
+
|
| 195 |
+
# Load a specific pretraining checkpoint
|
| 196 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 197 |
+
"jhu-clsp/ettin-decoder-400m",
|
| 198 |
+
revision="step590532" # Specific checkpoint tag
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
# Load an extension phase checkpoint
|
| 202 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 203 |
+
"jhu-clsp/ettin-decoder-400m",
|
| 204 |
+
revision="ext1000"
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
# Load a decay phase checkpoint
|
| 208 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 209 |
+
"jhu-clsp/ettin-decoder-400m",
|
| 210 |
+
revision="decay100"
|
| 211 |
+
)
|
| 212 |
+
```
|
| 213 |
+
|
| 214 |
+
This checkpoint availability enables detailed analysis of training dynamics, loss curves, and capability emergence across the complete 2T token training process.
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
## 🔬 Research Applications
|
| 218 |
+
|
| 219 |
+
### What Makes Ettin Unique
|
| 220 |
+
|
| 221 |
+
Ettin provides the first **controlled comparison** of encoder vs. decoder architectures:
|
| 222 |
+
|
| 223 |
+
- **Identical Training Data**: Same 2T token mixture across all models
|
| 224 |
+
- **Matched Architectures**: Only attention patterns and objectives differ
|
| 225 |
+
- **Open Everything**: Training data, model weights, and batch-level training order
|
| 226 |
+
- **Multiple Scales**: Fair comparison from 17M to 1B parameters
|
| 227 |
+
- **250+ Checkpoints**: Complete training trajectory analysis
|
| 228 |
+
|
| 229 |
+
### Use Cases for Researchers
|
| 230 |
+
|
| 231 |
+
- **Architecture Studies**: Compare encoder vs decoder capabilities fairly
|
| 232 |
+
- **Training Dynamics**: Analyze 250+ checkpoints with batch-level data ordering
|
| 233 |
+
- **Scaling Laws**: Study how architectural advantages change with scale
|
| 234 |
+
- **Transfer Learning**: Investigate cross-objective training effectiveness
|
| 235 |
+
- **Replication Studies**: First open replication of ModernBERT training recipe
|
| 236 |
+
|
| 237 |
+
### Reproducibility
|
| 238 |
+
|
| 239 |
+
All training artifacts are publicly available:
|
| 240 |
+
- Training data with exact batch ordering
|
| 241 |
+
- Model checkpoints every 8.5B tokens
|
| 242 |
+
- Complete hyperparameter configurations
|
| 243 |
+
- Training code and evaluation scripts
|
| 244 |
+
|
| 245 |
+
## Training Details
|
| 246 |
+
|
| 247 |
+
**Data:** High-quality mixture including DCLM, Dolma v1.7, scientific papers, code, and curated sources totaling 2T+ tokens
|
| 248 |
+
|
| 249 |
+
**Architecture:** Transformer with RoPE, GLU activations, and prenorm layers
|
| 250 |
+
|
| 251 |
+
**Training Phases:**
|
| 252 |
+
- **Pre-training**: 1.7T tokens with diverse data mixture
|
| 253 |
+
- **Mid-training**: 250B tokens with higher-quality filtered data and context extension to 8K
|
| 254 |
+
- **Decay phase**: 100B tokens with premium data sources
|
| 255 |
+
|
| 256 |
+
**Key Features:**
|
| 257 |
+
- Context length: Up to 8K tokens
|
| 258 |
+
- Vocabulary: 50,368 tokens (ModernBERT tokenizer)
|
| 259 |
+
- Deep but efficient architectures following MobileLLM principles
|
| 260 |
+
|
| 261 |
+
## Model Architecture
|
| 262 |
+
|
| 263 |
+
| Parameter | 17M | 32M | 68M | 150M | 400M | 1B |
|
| 264 |
+
|:----------|:----|:----|:----|:-----|:-----|:---|
|
| 265 |
+
| Layers | 7 | 10 | 19 | 22 | 28 | 28 |
|
| 266 |
+
| Hidden Size | 256 | 384 | 512 | 768 | 1024 | 1792 |
|
| 267 |
+
| Intermediate Size | 384 | 576 | 768 | 1152 | 2624 | 3840 |
|
| 268 |
+
| Attention Heads | 4 | 6 | 8 | 12 | 16 | 28 |
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
## Usage Examples
|
| 273 |
+
|
| 274 |
+
### Encoder: Masked Language Modeling
|
| 275 |
+
<details>
|
| 276 |
+
<summary>Click to expand **encoder** usage examples</summary>
|
| 277 |
+
|
| 278 |
+
```python
|
| 279 |
+
from transformers import AutoTokenizer, AutoModelForMaskedLM
|
| 280 |
+
import torch
|
| 281 |
+
|
| 282 |
+
# Load MLM model
|
| 283 |
+
tokenizer = AutoTokenizer.from_pretrained("jhu-clsp/ettin-encoder-150m")
|
| 284 |
+
model = AutoModelForMaskedLM.from_pretrained("jhu-clsp/ettin-encoder-150m")
|
| 285 |
+
|
| 286 |
+
def predict_masked_token(text):
|
| 287 |
+
inputs = tokenizer(text, return_tensors="pt")
|
| 288 |
+
with torch.no_grad():
|
| 289 |
+
outputs = model(**inputs)
|
| 290 |
+
|
| 291 |
+
# Get predictions for [MASK] tokens
|
| 292 |
+
mask_indices = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)
|
| 293 |
+
predictions = outputs.logits[mask_indices]
|
| 294 |
+
|
| 295 |
+
# Get top 5 predictions
|
| 296 |
+
top_tokens = torch.topk(predictions, 5, dim=-1)
|
| 297 |
+
return [tokenizer.decode(token) for token in top_tokens.indices[0]]
|
| 298 |
+
|
| 299 |
+
# Example
|
| 300 |
+
masked_text = "The capital of France is [MASK]."
|
| 301 |
+
predictions = predict_masked_token(masked_text)
|
| 302 |
+
print(f"Predictions: {predictions}")
|
| 303 |
+
```
|
| 304 |
+
|
| 305 |
+
</details>
|
| 306 |
+
|
| 307 |
+
### Decoder: Text Generation
|
| 308 |
+
|
| 309 |
+
<details>
|
| 310 |
+
<summary>Click to expand **decoder text generation**</summary>
|
| 311 |
+
|
| 312 |
+
```python
|
| 313 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 314 |
+
import torch
|
| 315 |
+
|
| 316 |
+
# Load model and tokenizer
|
| 317 |
+
tokenizer = AutoTokenizer.from_pretrained("jhu-clsp/ettin-decoder-150m")
|
| 318 |
+
model = AutoModelForCausalLM.from_pretrained("jhu-clsp/ettin-decoder-150m")
|
| 319 |
+
|
| 320 |
+
# Set pad token if needed
|
| 321 |
+
if tokenizer.pad_token is None:
|
| 322 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 323 |
+
|
| 324 |
+
def generate_text(prompt, max_length=100, temperature=0.7):
|
| 325 |
+
inputs = tokenizer(prompt, return_tensors="pt")
|
| 326 |
+
|
| 327 |
+
with torch.no_grad():
|
| 328 |
+
outputs = model.generate(
|
| 329 |
+
inputs.input_ids,
|
| 330 |
+
max_length=max_length,
|
| 331 |
+
temperature=temperature,
|
| 332 |
+
do_sample=True,
|
| 333 |
+
pad_token_id=tokenizer.eos_token_id,
|
| 334 |
+
num_return_sequences=1
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
return tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 338 |
+
|
| 339 |
+
# Example usage
|
| 340 |
+
prompt = "The future of artificial intelligence is"
|
| 341 |
+
generated = generate_text(prompt)
|
| 342 |
+
print(generated)
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
</details>
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
## Fine-tuning Examples
|
| 349 |
+
|
| 350 |
+
### Encoders
|
| 351 |
+
<details><summary>Click to see how to finetune this into a dense embedding model using Sentence Transformers</summary>
|
| 352 |
+
|
| 353 |
+
```python
|
| 354 |
+
import argparse
|
| 355 |
+
|
| 356 |
+
from datasets import load_dataset
|
| 357 |
+
from sentence_transformers import (
|
| 358 |
+
SentenceTransformer,
|
| 359 |
+
SentenceTransformerTrainer,
|
| 360 |
+
SentenceTransformerTrainingArguments,
|
| 361 |
+
)
|
| 362 |
+
from sentence_transformers.evaluation import TripletEvaluator
|
| 363 |
+
from sentence_transformers.losses import CachedMultipleNegativesRankingLoss
|
| 364 |
+
from sentence_transformers.training_args import BatchSamplers
|
| 365 |
+
|
| 366 |
+
def main():
|
| 367 |
+
# parse the lr & model name
|
| 368 |
+
parser = argparse.ArgumentParser()
|
| 369 |
+
parser.add_argument("--lr", type=float, default=8e-5)
|
| 370 |
+
parser.add_argument("--model_name", type=str, default="jhu-clsp/ettin-encoder-150m")
|
| 371 |
+
args = parser.parse_args()
|
| 372 |
+
lr = args.lr
|
| 373 |
+
model_name = args.model_name
|
| 374 |
+
model_shortname = model_name.split("/")[-1]
|
| 375 |
+
|
| 376 |
+
# 1. Load a model to finetune
|
| 377 |
+
model = SentenceTransformer(model_name)
|
| 378 |
+
|
| 379 |
+
# 2. Load a dataset to finetune on
|
| 380 |
+
dataset = load_dataset(
|
| 381 |
+
"sentence-transformers/msmarco-co-condenser-margin-mse-sym-mnrl-mean-v1",
|
| 382 |
+
"triplet-hard",
|
| 383 |
+
split="train",
|
| 384 |
+
)
|
| 385 |
+
dataset_dict = dataset.train_test_split(test_size=1_000, seed=12)
|
| 386 |
+
train_dataset = dataset_dict["train"].select(range(1_250_000))
|
| 387 |
+
eval_dataset = dataset_dict["test"]
|
| 388 |
+
|
| 389 |
+
# 3. Define a loss function
|
| 390 |
+
loss = CachedMultipleNegativesRankingLoss(model, mini_batch_size=16) # Increase mini_batch_size if you have enough VRAM
|
| 391 |
+
|
| 392 |
+
run_name = f"{model_shortname}-DPR-{lr}"
|
| 393 |
+
# 4. (Optional) Specify training arguments
|
| 394 |
+
args = SentenceTransformerTrainingArguments(
|
| 395 |
+
# Required parameter:
|
| 396 |
+
output_dir=f"output/{model_shortname}/{run_name}",
|
| 397 |
+
# Optional training parameters:
|
| 398 |
+
num_train_epochs=1,
|
| 399 |
+
per_device_train_batch_size=512,
|
| 400 |
+
per_device_eval_batch_size=512,
|
| 401 |
+
warmup_ratio=0.05,
|
| 402 |
+
fp16=False, # Set to False if GPU can't handle FP16
|
| 403 |
+
bf16=True, # Set to True if GPU supports BF16
|
| 404 |
+
batch_sampler=BatchSamplers.NO_DUPLICATES, # (Cached)MultipleNegativesRankingLoss benefits from no duplicates
|
| 405 |
+
learning_rate=lr,
|
| 406 |
+
# Optional tracking/debugging parameters:
|
| 407 |
+
save_strategy="steps",
|
| 408 |
+
save_steps=500,
|
| 409 |
+
save_total_limit=2,
|
| 410 |
+
logging_steps=500,
|
| 411 |
+
run_name=run_name, # Used in `wandb`, `tensorboard`, `neptune`, etc. if installed
|
| 412 |
+
)
|
| 413 |
+
|
| 414 |
+
# 5. (Optional) Create an evaluator & evaluate the base model
|
| 415 |
+
dev_evaluator = TripletEvaluator(
|
| 416 |
+
anchors=eval_dataset["query"],
|
| 417 |
+
positives=eval_dataset["positive"],
|
| 418 |
+
negatives=eval_dataset["negative"],
|
| 419 |
+
name="msmarco-co-condenser-dev",
|
| 420 |
+
)
|
| 421 |
+
dev_evaluator(model)
|
| 422 |
+
|
| 423 |
+
# 6. Create a trainer & train
|
| 424 |
+
trainer = SentenceTransformerTrainer(
|
| 425 |
+
model=model,
|
| 426 |
+
args=args,
|
| 427 |
+
train_dataset=train_dataset,
|
| 428 |
+
eval_dataset=eval_dataset,
|
| 429 |
+
loss=loss,
|
| 430 |
+
evaluator=dev_evaluator,
|
| 431 |
+
)
|
| 432 |
+
trainer.train()
|
| 433 |
+
|
| 434 |
+
# 7. (Optional) Evaluate the trained model on the evaluator after training
|
| 435 |
+
dev_evaluator(model)
|
| 436 |
+
|
| 437 |
+
# 8. Save the model
|
| 438 |
+
model.save_pretrained(f"output/{model_shortname}/{run_name}/final")
|
| 439 |
+
|
| 440 |
+
# 9. (Optional) Push it to the Hugging Face Hub
|
| 441 |
+
model.push_to_hub(run_name, private=False)
|
| 442 |
+
|
| 443 |
+
if __name__ == "__main__":
|
| 444 |
+
main()
|
| 445 |
+
```
|
| 446 |
+
</details>
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
<details><summary>Click to see how to finetune this into a multi-vector embedding model with PyLate</summary>
|
| 450 |
+
|
| 451 |
+
```python
|
| 452 |
+
from datasets import load_dataset
|
| 453 |
+
from pylate import losses, models, utils
|
| 454 |
+
from sentence_transformers import (
|
| 455 |
+
SentenceTransformerTrainer,
|
| 456 |
+
SentenceTransformerTrainingArguments,
|
| 457 |
+
)
|
| 458 |
+
|
| 459 |
+
def main():
|
| 460 |
+
# Load the datasets required for knowledge distillation (train, queries, documents)
|
| 461 |
+
train = load_dataset(
|
| 462 |
+
path="lightonai/ms-marco-en-bge",
|
| 463 |
+
name="train",
|
| 464 |
+
)
|
| 465 |
+
|
| 466 |
+
queries = load_dataset(
|
| 467 |
+
path="lightonai/ms-marco-en-bge",
|
| 468 |
+
name="queries",
|
| 469 |
+
)
|
| 470 |
+
|
| 471 |
+
documents = load_dataset(
|
| 472 |
+
path="lightonai/ms-marco-en-bge",
|
| 473 |
+
name="documents",
|
| 474 |
+
)
|
| 475 |
+
|
| 476 |
+
# Set the transformation to load the documents/queries texts using the corresponding ids on the fly
|
| 477 |
+
train.set_transform(
|
| 478 |
+
utils.KDProcessing(queries=queries, documents=documents).transform,
|
| 479 |
+
)
|
| 480 |
+
|
| 481 |
+
# Define the base model, training parameters, and output directory
|
| 482 |
+
num_train_epochs = 1
|
| 483 |
+
lr = 8e-5
|
| 484 |
+
batch_size = 16
|
| 485 |
+
accum_steps = 1
|
| 486 |
+
model_name = "jhu-clsp/ettin-encoder-150m"
|
| 487 |
+
model_shortname = model_name.split("/")[-1]
|
| 488 |
+
|
| 489 |
+
# Set the run name for logging and output directory
|
| 490 |
+
run_name = f"{model_shortname}-colbert-KD-{lr}"
|
| 491 |
+
output_dir = f"output/{model_shortname}/{run_name}"
|
| 492 |
+
|
| 493 |
+
# Initialize the ColBERT model from the base model
|
| 494 |
+
model = models.ColBERT(model_name_or_path=model_name)
|
| 495 |
+
|
| 496 |
+
# Configure the training arguments (e.g., epochs, batch size, learning rate)
|
| 497 |
+
args = SentenceTransformerTrainingArguments(
|
| 498 |
+
output_dir=output_dir,
|
| 499 |
+
num_train_epochs=num_train_epochs,
|
| 500 |
+
per_device_train_batch_size=batch_size,
|
| 501 |
+
fp16=False, # Set to False if you get an error that your GPU can't run on FP16
|
| 502 |
+
bf16=True, # Set to True if you have a GPU that supports BF16
|
| 503 |
+
run_name=run_name,
|
| 504 |
+
logging_steps=10,
|
| 505 |
+
learning_rate=lr,
|
| 506 |
+
gradient_accumulation_steps=accum_steps,
|
| 507 |
+
warmup_ratio=0.05,
|
| 508 |
+
)
|
| 509 |
+
|
| 510 |
+
# Use the Distillation loss function for training
|
| 511 |
+
train_loss = losses.Distillation(model=model)
|
| 512 |
+
|
| 513 |
+
# Initialize the trainer
|
| 514 |
+
trainer = SentenceTransformerTrainer(
|
| 515 |
+
model=model,
|
| 516 |
+
args=args,
|
| 517 |
+
train_dataset=train,
|
| 518 |
+
loss=train_loss,
|
| 519 |
+
data_collator=utils.ColBERTCollator(tokenize_fn=model.tokenize),
|
| 520 |
+
)
|
| 521 |
+
|
| 522 |
+
# Start the training process
|
| 523 |
+
trainer.train()
|
| 524 |
+
|
| 525 |
+
model.save_pretrained(f"{output_dir}/final")
|
| 526 |
+
|
| 527 |
+
if __name__ == "__main__":
|
| 528 |
+
main()
|
| 529 |
+
|
| 530 |
+
```
|
| 531 |
+
</details>
|
| 532 |
+
|
| 533 |
+
<details><summary>Click to see how to finetune this into a sparse retrieval model using Sentence Transformers</summary>
|
| 534 |
+
|
| 535 |
+
```python
|
| 536 |
+
import logging
|
| 537 |
+
|
| 538 |
+
from datasets import load_dataset
|
| 539 |
+
|
| 540 |
+
from sentence_transformers import (
|
| 541 |
+
SparseEncoder,
|
| 542 |
+
SparseEncoderModelCardData,
|
| 543 |
+
SparseEncoderTrainer,
|
| 544 |
+
SparseEncoderTrainingArguments,
|
| 545 |
+
)
|
| 546 |
+
from sentence_transformers.sparse_encoder.evaluation import SparseNanoBEIREvaluator
|
| 547 |
+
from sentence_transformers.sparse_encoder.losses import SparseMultipleNegativesRankingLoss, SpladeLoss
|
| 548 |
+
from sentence_transformers.training_args import BatchSamplers
|
| 549 |
+
|
| 550 |
+
logging.basicConfig(format="%(asctime)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S", level=logging.INFO)
|
| 551 |
+
|
| 552 |
+
# 1. Load a model to finetune with 2. (Optional) model card data
|
| 553 |
+
model = SparseEncoder(
|
| 554 |
+
"jhu-clsp/ettin-encoder-150m",
|
| 555 |
+
model_card_data=SparseEncoderModelCardData(
|
| 556 |
+
language="en",
|
| 557 |
+
license="apache-2.0",
|
| 558 |
+
)
|
| 559 |
+
)
|
| 560 |
+
|
| 561 |
+
# 3. Load a dataset to finetune on
|
| 562 |
+
full_dataset = load_dataset("sentence-transformers/natural-questions", split="train").select(range(100_000))
|
| 563 |
+
dataset_dict = full_dataset.train_test_split(test_size=1_000, seed=12)
|
| 564 |
+
train_dataset = dataset_dict["train"]
|
| 565 |
+
eval_dataset = dataset_dict["test"]
|
| 566 |
+
|
| 567 |
+
# 4. Define a loss function
|
| 568 |
+
loss = SpladeLoss(
|
| 569 |
+
model=model,
|
| 570 |
+
loss=SparseMultipleNegativesRankingLoss(model=model),
|
| 571 |
+
query_regularizer_weight=5e-5,
|
| 572 |
+
document_regularizer_weight=3e-5,
|
| 573 |
+
)
|
| 574 |
+
|
| 575 |
+
# 5. (Optional) Specify training arguments
|
| 576 |
+
run_name = "splade-distilbert-base-uncased-nq"
|
| 577 |
+
args = SparseEncoderTrainingArguments(
|
| 578 |
+
# Required parameter:
|
| 579 |
+
output_dir=f"models/{run_name}",
|
| 580 |
+
# Optional training parameters:
|
| 581 |
+
num_train_epochs=1,
|
| 582 |
+
per_device_train_batch_size=16,
|
| 583 |
+
per_device_eval_batch_size=16,
|
| 584 |
+
learning_rate=2e-5,
|
| 585 |
+
warmup_ratio=0.1,
|
| 586 |
+
fp16=True, # Set to False if you get an error that your GPU can't run on FP16
|
| 587 |
+
bf16=False, # Set to True if you have a GPU that supports BF16
|
| 588 |
+
batch_sampler=BatchSamplers.NO_DUPLICATES, # MultipleNegativesRankingLoss benefits from no duplicate samples in a batch
|
| 589 |
+
# Optional tracking/debugging parameters:
|
| 590 |
+
eval_strategy="steps",
|
| 591 |
+
eval_steps=1000,
|
| 592 |
+
save_strategy="steps",
|
| 593 |
+
save_steps=1000,
|
| 594 |
+
save_total_limit=2,
|
| 595 |
+
logging_steps=200,
|
| 596 |
+
run_name=run_name, # Will be used in W&B if `wandb` is installed
|
| 597 |
+
)
|
| 598 |
+
|
| 599 |
+
# 6. (Optional) Create an evaluator & evaluate the base model
|
| 600 |
+
dev_evaluator = SparseNanoBEIREvaluator(dataset_names=["msmarco", "nfcorpus", "nq"], batch_size=16)
|
| 601 |
+
|
| 602 |
+
# 7. Create a trainer & train
|
| 603 |
+
trainer = SparseEncoderTrainer(
|
| 604 |
+
model=model,
|
| 605 |
+
args=args,
|
| 606 |
+
train_dataset=train_dataset,
|
| 607 |
+
eval_dataset=eval_dataset,
|
| 608 |
+
loss=loss,
|
| 609 |
+
evaluator=dev_evaluator,
|
| 610 |
+
)
|
| 611 |
+
trainer.train()
|
| 612 |
+
|
| 613 |
+
# 8. Evaluate the model performance again after training
|
| 614 |
+
dev_evaluator(model)
|
| 615 |
+
|
| 616 |
+
# 9. Save the trained model
|
| 617 |
+
model.save_pretrained(f"models/{run_name}/final")
|
| 618 |
+
|
| 619 |
+
# 10. (Optional) Push it to the Hugging Face Hub
|
| 620 |
+
model.push_to_hub(run_name)
|
| 621 |
+
|
| 622 |
+
```
|
| 623 |
+
</details>
|
| 624 |
+
|
| 625 |
+
<details><summary>Click to see how to finetune this into a reranker model using Sentence Transformers</summary>
|
| 626 |
+
|
| 627 |
+
```python
|
| 628 |
+
import logging
|
| 629 |
+
import traceback
|
| 630 |
+
|
| 631 |
+
import torch
|
| 632 |
+
from datasets import load_dataset
|
| 633 |
+
|
| 634 |
+
from sentence_transformers import SentenceTransformer
|
| 635 |
+
from sentence_transformers.cross_encoder import (
|
| 636 |
+
CrossEncoder,
|
| 637 |
+
CrossEncoderModelCardData,
|
| 638 |
+
CrossEncoderTrainer,
|
| 639 |
+
CrossEncoderTrainingArguments,
|
| 640 |
+
)
|
| 641 |
+
from sentence_transformers.cross_encoder.evaluation import (
|
| 642 |
+
CrossEncoderNanoBEIREvaluator,
|
| 643 |
+
CrossEncoderRerankingEvaluator,
|
| 644 |
+
)
|
| 645 |
+
from sentence_transformers.cross_encoder.losses import BinaryCrossEntropyLoss
|
| 646 |
+
from sentence_transformers.evaluation import SequentialEvaluator
|
| 647 |
+
from sentence_transformers.util import mine_hard_negatives
|
| 648 |
+
|
| 649 |
+
# Set the log level to INFO to get more information
|
| 650 |
+
logging.basicConfig(format="%(asctime)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S", level=logging.INFO)
|
| 651 |
+
|
| 652 |
+
|
| 653 |
+
def main():
|
| 654 |
+
model_name = "jhu-clsp/ettin-encoder-150m"
|
| 655 |
+
|
| 656 |
+
train_batch_size = 64
|
| 657 |
+
num_epochs = 1
|
| 658 |
+
num_hard_negatives = 5 # How many hard negatives should be mined for each question-answer pair
|
| 659 |
+
|
| 660 |
+
# 1a. Load a model to finetune with 1b. (Optional) model card data
|
| 661 |
+
model = CrossEncoder(
|
| 662 |
+
model_name,
|
| 663 |
+
model_card_data=CrossEncoderModelCardData(
|
| 664 |
+
language="en",
|
| 665 |
+
license="apache-2.0",
|
| 666 |
+
),
|
| 667 |
+
)
|
| 668 |
+
print("Model max length:", model.max_length)
|
| 669 |
+
print("Model num labels:", model.num_labels)
|
| 670 |
+
|
| 671 |
+
# 2a. Load the GooAQ dataset: https://huggingface.co/datasets/sentence-transformers/gooaq
|
| 672 |
+
logging.info("Read the gooaq training dataset")
|
| 673 |
+
full_dataset = load_dataset("sentence-transformers/gooaq", split="train").select(range(100_000))
|
| 674 |
+
dataset_dict = full_dataset.train_test_split(test_size=1_000, seed=12)
|
| 675 |
+
train_dataset = dataset_dict["train"]
|
| 676 |
+
eval_dataset = dataset_dict["test"]
|
| 677 |
+
logging.info(train_dataset)
|
| 678 |
+
logging.info(eval_dataset)
|
| 679 |
+
|
| 680 |
+
# 2b. Modify our training dataset to include hard negatives using a very efficient embedding model
|
| 681 |
+
embedding_model = SentenceTransformer("sentence-transformers/static-retrieval-mrl-en-v1", device="cpu")
|
| 682 |
+
hard_train_dataset = mine_hard_negatives(
|
| 683 |
+
train_dataset,
|
| 684 |
+
embedding_model,
|
| 685 |
+
num_negatives=num_hard_negatives, # How many negatives per question-answer pair
|
| 686 |
+
margin=0, # Similarity between query and negative samples should be x lower than query-positive similarity
|
| 687 |
+
range_min=0, # Skip the x most similar samples
|
| 688 |
+
range_max=100, # Consider only the x most similar samples
|
| 689 |
+
sampling_strategy="top", # Sample the top negatives from the range
|
| 690 |
+
batch_size=4096, # Use a batch size of 4096 for the embedding model
|
| 691 |
+
output_format="labeled-pair", # The output format is (query, passage, label), as required by BinaryCrossEntropyLoss
|
| 692 |
+
use_faiss=True,
|
| 693 |
+
)
|
| 694 |
+
logging.info(hard_train_dataset)
|
| 695 |
+
|
| 696 |
+
# 2c. (Optionally) Save the hard training dataset to disk
|
| 697 |
+
# hard_train_dataset.save_to_disk("gooaq-hard-train")
|
| 698 |
+
# Load again with:
|
| 699 |
+
# hard_train_dataset = load_from_disk("gooaq-hard-train")
|
| 700 |
+
|
| 701 |
+
# 3. Define our training loss.
|
| 702 |
+
# pos_weight is recommended to be set as the ratio between positives to negatives, a.k.a. `num_hard_negatives`
|
| 703 |
+
loss = BinaryCrossEntropyLoss(model=model, pos_weight=torch.tensor(num_hard_negatives))
|
| 704 |
+
|
| 705 |
+
# 4a. Define evaluators. We use the CrossEncoderNanoBEIREvaluator, which is a light-weight evaluator for English reranking
|
| 706 |
+
nano_beir_evaluator = CrossEncoderNanoBEIREvaluator(
|
| 707 |
+
dataset_names=["msmarco", "nfcorpus", "nq"],
|
| 708 |
+
batch_size=train_batch_size,
|
| 709 |
+
)
|
| 710 |
+
|
| 711 |
+
# 4b. Define a reranking evaluator by mining hard negatives given query-answer pairs
|
| 712 |
+
# We include the positive answer in the list of negatives, so the evaluator can use the performance of the
|
| 713 |
+
# embedding model as a baseline.
|
| 714 |
+
hard_eval_dataset = mine_hard_negatives(
|
| 715 |
+
eval_dataset,
|
| 716 |
+
embedding_model,
|
| 717 |
+
corpus=full_dataset["answer"], # Use the full dataset as the corpus
|
| 718 |
+
num_negatives=30, # How many documents to rerank
|
| 719 |
+
batch_size=4096,
|
| 720 |
+
include_positives=True,
|
| 721 |
+
output_format="n-tuple",
|
| 722 |
+
use_faiss=True,
|
| 723 |
+
)
|
| 724 |
+
logging.info(hard_eval_dataset)
|
| 725 |
+
reranking_evaluator = CrossEncoderRerankingEvaluator(
|
| 726 |
+
samples=[
|
| 727 |
+
{
|
| 728 |
+
"query": sample["question"],
|
| 729 |
+
"positive": [sample["answer"]],
|
| 730 |
+
"documents": [sample[column_name] for column_name in hard_eval_dataset.column_names[2:]],
|
| 731 |
+
}
|
| 732 |
+
for sample in hard_eval_dataset
|
| 733 |
+
],
|
| 734 |
+
batch_size=train_batch_size,
|
| 735 |
+
name="gooaq-dev",
|
| 736 |
+
# Realistic setting: only rerank the positives that the retriever found
|
| 737 |
+
# Set to True to rerank *all* positives
|
| 738 |
+
always_rerank_positives=False,
|
| 739 |
+
)
|
| 740 |
+
|
| 741 |
+
# 4c. Combine the evaluators & run the base model on them
|
| 742 |
+
evaluator = SequentialEvaluator([reranking_evaluator, nano_beir_evaluator])
|
| 743 |
+
evaluator(model)
|
| 744 |
+
|
| 745 |
+
# 5. Define the training arguments
|
| 746 |
+
short_model_name = model_name if "/" not in model_name else model_name.split("/")[-1]
|
| 747 |
+
run_name = f"reranker-{short_model_name}-gooaq-bce"
|
| 748 |
+
args = CrossEncoderTrainingArguments(
|
| 749 |
+
# Required parameter:
|
| 750 |
+
output_dir=f"models/{run_name}",
|
| 751 |
+
# Optional training parameters:
|
| 752 |
+
num_train_epochs=num_epochs,
|
| 753 |
+
per_device_train_batch_size=train_batch_size,
|
| 754 |
+
per_device_eval_batch_size=train_batch_size,
|
| 755 |
+
learning_rate=2e-5,
|
| 756 |
+
warmup_ratio=0.1,
|
| 757 |
+
fp16=False, # Set to False if you get an error that your GPU can't run on FP16
|
| 758 |
+
bf16=True, # Set to True if you have a GPU that supports BF16
|
| 759 |
+
dataloader_num_workers=4,
|
| 760 |
+
load_best_model_at_end=True,
|
| 761 |
+
metric_for_best_model="eval_gooaq-dev_ndcg@10",
|
| 762 |
+
# Optional tracking/debugging parameters:
|
| 763 |
+
eval_strategy="steps",
|
| 764 |
+
eval_steps=1000,
|
| 765 |
+
save_strategy="steps",
|
| 766 |
+
save_steps=1000,
|
| 767 |
+
save_total_limit=2,
|
| 768 |
+
logging_steps=200,
|
| 769 |
+
logging_first_step=True,
|
| 770 |
+
run_name=run_name, # Will be used in W&B if `wandb` is installed
|
| 771 |
+
seed=12,
|
| 772 |
+
)
|
| 773 |
+
|
| 774 |
+
# 6. Create the trainer & start training
|
| 775 |
+
trainer = CrossEncoderTrainer(
|
| 776 |
+
model=model,
|
| 777 |
+
args=args,
|
| 778 |
+
train_dataset=hard_train_dataset,
|
| 779 |
+
loss=loss,
|
| 780 |
+
evaluator=evaluator,
|
| 781 |
+
)
|
| 782 |
+
trainer.train()
|
| 783 |
+
|
| 784 |
+
# 7. Evaluate the final model, useful to include these in the model card
|
| 785 |
+
evaluator(model)
|
| 786 |
+
|
| 787 |
+
# 8. Save the final model
|
| 788 |
+
final_output_dir = f"models/{run_name}/final"
|
| 789 |
+
model.save_pretrained(final_output_dir)
|
| 790 |
+
|
| 791 |
+
# 9. (Optional) save the model to the Hugging Face Hub!
|
| 792 |
+
# It is recommended to run `huggingface-cli login` to log into your Hugging Face account first
|
| 793 |
+
try:
|
| 794 |
+
model.push_to_hub(run_name)
|
| 795 |
+
except Exception:
|
| 796 |
+
logging.error(
|
| 797 |
+
f"Error uploading model to the Hugging Face Hub:
|
| 798 |
+
{traceback.format_exc()}To upload it manually, you can run "
|
| 799 |
+
f"`huggingface-cli login`, followed by loading the model using `model = CrossEncoder({final_output_dir!r})` "
|
| 800 |
+
f"and saving it using `model.push_to_hub('{run_name}')`."
|
| 801 |
+
)
|
| 802 |
+
|
| 803 |
+
|
| 804 |
+
if __name__ == "__main__":
|
| 805 |
+
main()
|
| 806 |
+
|
| 807 |
+
```
|
| 808 |
+
</details>
|
| 809 |
+
|
| 810 |
+
### Decoders
|
| 811 |
+
|
| 812 |
+
<details>
|
| 813 |
+
<summary>Click to expand decoder training code</summary>
|
| 814 |
+
|
| 815 |
+
# Full training
|
| 816 |
+
```bash
|
| 817 |
+
python trl/scripts/sft.py \
|
| 818 |
+
--model_name_or_path jhu-clsp/ettin-decoder-17m \
|
| 819 |
+
--dataset_name trl-lib/Capybara \
|
| 820 |
+
--learning_rate 2.0e-5 \
|
| 821 |
+
--num_train_epochs 1 \
|
| 822 |
+
--packing \
|
| 823 |
+
--per_device_train_batch_size 2 \
|
| 824 |
+
--gradient_accumulation_steps 8 \
|
| 825 |
+
--gradient_checkpointing \
|
| 826 |
+
--eos_token '<|im_end|>' \
|
| 827 |
+
--eval_strategy steps \
|
| 828 |
+
--eval_steps 100 \
|
| 829 |
+
--output_dir ettin-decoder-17m \
|
| 830 |
+
--push_to_hub
|
| 831 |
+
```
|
| 832 |
+
|
| 833 |
+
# LoRA
|
| 834 |
+
```bash
|
| 835 |
+
python trl/scripts/sft.py \
|
| 836 |
+
--model_name_or_path jhu-clsp/ettin-decoder-17m \
|
| 837 |
+
--dataset_name trl-lib/Capybara \
|
| 838 |
+
--learning_rate 2.0e-4 \
|
| 839 |
+
--num_train_epochs 1 \
|
| 840 |
+
--packing \
|
| 841 |
+
--per_device_train_batch_size 2 \
|
| 842 |
+
--gradient_accumulation_steps 8 \
|
| 843 |
+
--gradient_checkpointing \
|
| 844 |
+
--eos_token '<|im_end|>' \
|
| 845 |
+
--eval_strategy steps \
|
| 846 |
+
--eval_steps 100 \
|
| 847 |
+
--use_peft \
|
| 848 |
+
--lora_r 32 \
|
| 849 |
+
--lora_alpha 16 \
|
| 850 |
+
--output_dir ettin-decoder-17m \
|
| 851 |
+
--push_to_hub
|
| 852 |
+
```
|
| 853 |
+
|
| 854 |
+
with `sft.py`:
|
| 855 |
+
```python
|
| 856 |
+
import argparse
|
| 857 |
+
|
| 858 |
+
from datasets import load_dataset
|
| 859 |
+
from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
|
| 860 |
+
from transformers.models.auto.modeling_auto import MODEL_FOR_IMAGE_TEXT_TO_TEXT_MAPPING_NAMES
|
| 861 |
+
|
| 862 |
+
from trl import (
|
| 863 |
+
ModelConfig,
|
| 864 |
+
ScriptArguments,
|
| 865 |
+
SFTConfig,
|
| 866 |
+
SFTTrainer,
|
| 867 |
+
TrlParser,
|
| 868 |
+
clone_chat_template,
|
| 869 |
+
get_kbit_device_map,
|
| 870 |
+
get_peft_config,
|
| 871 |
+
get_quantization_config,
|
| 872 |
+
)
|
| 873 |
+
|
| 874 |
+
|
| 875 |
+
def main(script_args, training_args, model_args):
|
| 876 |
+
################
|
| 877 |
+
# Model init kwargs & Tokenizer
|
| 878 |
+
################
|
| 879 |
+
quantization_config = get_quantization_config(model_args)
|
| 880 |
+
model_kwargs = dict(
|
| 881 |
+
revision=model_args.model_revision,
|
| 882 |
+
trust_remote_code=model_args.trust_remote_code,
|
| 883 |
+
attn_implementation=model_args.attn_implementation,
|
| 884 |
+
torch_dtype=model_args.torch_dtype,
|
| 885 |
+
use_cache=False if training_args.gradient_checkpointing else True,
|
| 886 |
+
device_map=get_kbit_device_map() if quantization_config is not None else None,
|
| 887 |
+
quantization_config=quantization_config,
|
| 888 |
+
)
|
| 889 |
+
|
| 890 |
+
# Create model
|
| 891 |
+
config = AutoConfig.from_pretrained(model_args.model_name_or_path)
|
| 892 |
+
valid_image_text_architectures = MODEL_FOR_IMAGE_TEXT_TO_TEXT_MAPPING_NAMES.values()
|
| 893 |
+
|
| 894 |
+
if config.architectures and any(arch in valid_image_text_architectures for arch in config.architectures):
|
| 895 |
+
from transformers import AutoModelForImageTextToText
|
| 896 |
+
|
| 897 |
+
model_kwargs.pop("use_cache", None) # Image models do not support cache
|
| 898 |
+
model = AutoModelForImageTextToText.from_pretrained(model_args.model_name_or_path, **model_kwargs)
|
| 899 |
+
else:
|
| 900 |
+
model = AutoModelForCausalLM.from_pretrained(model_args.model_name_or_path, **model_kwargs)
|
| 901 |
+
|
| 902 |
+
# Create tokenizer
|
| 903 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 904 |
+
model_args.model_name_or_path, trust_remote_code=model_args.trust_remote_code, use_fast=True
|
| 905 |
+
)
|
| 906 |
+
|
| 907 |
+
# Set default chat template if needed
|
| 908 |
+
if tokenizer.chat_template is None:
|
| 909 |
+
# TODO: source should be passed as an argument
|
| 910 |
+
model, tokenizer = clone_chat_template(model, tokenizer, "Qwen/Qwen3-0.6B")
|
| 911 |
+
|
| 912 |
+
################
|
| 913 |
+
# Dataset
|
| 914 |
+
################
|
| 915 |
+
dataset = load_dataset(script_args.dataset_name, name=script_args.dataset_config)
|
| 916 |
+
|
| 917 |
+
################
|
| 918 |
+
# Training
|
| 919 |
+
################
|
| 920 |
+
trainer = SFTTrainer(
|
| 921 |
+
model=model,
|
| 922 |
+
args=training_args,
|
| 923 |
+
train_dataset=dataset[script_args.dataset_train_split],
|
| 924 |
+
eval_dataset=dataset[script_args.dataset_test_split] if training_args.eval_strategy != "no" else None,
|
| 925 |
+
processing_class=tokenizer,
|
| 926 |
+
peft_config=get_peft_config(model_args),
|
| 927 |
+
)
|
| 928 |
+
|
| 929 |
+
trainer.train()
|
| 930 |
+
|
| 931 |
+
# Save and push to hub
|
| 932 |
+
trainer.save_model(training_args.output_dir)
|
| 933 |
+
if training_args.push_to_hub:
|
| 934 |
+
trainer.push_to_hub(dataset_name=script_args.dataset_name)
|
| 935 |
+
|
| 936 |
+
|
| 937 |
+
def make_parser(subparsers: argparse._SubParsersAction = None):
|
| 938 |
+
dataclass_types = (ScriptArguments, SFTConfig, ModelConfig)
|
| 939 |
+
if subparsers is not None:
|
| 940 |
+
parser = subparsers.add_parser("sft", help="Run the SFT training script", dataclass_types=dataclass_types)
|
| 941 |
+
else:
|
| 942 |
+
parser = TrlParser(dataclass_types)
|
| 943 |
+
return parser
|
| 944 |
+
|
| 945 |
+
|
| 946 |
+
if __name__ == "__main__":
|
| 947 |
+
parser = make_parser()
|
| 948 |
+
# When using the trl cli, this script may be run with additional arguments, corresponding accelerate arguments.
|
| 949 |
+
# To ensure that their parsing does not interfere with the script arguments, parse the arguments with
|
| 950 |
+
# `return_remaining_strings=True`, then ignore the remaining strings.
|
| 951 |
+
script_args, training_args, model_args, _ = parser.parse_args_and_config(return_remaining_strings=True)
|
| 952 |
+
main(script_args, training_args, model_args)
|
| 953 |
+
|
| 954 |
+
```
|
| 955 |
+
</details>
|
| 956 |
+
|
| 957 |
+
## Citation
|
| 958 |
+
|
| 959 |
+
If you use Ettin models in your research, please cite our work:
|
| 960 |
+
|
| 961 |
+
```bibtex
|
| 962 |
+
@misc{weller2025seqvsseqopen,
|
| 963 |
+
title={Seq vs Seq: An Open Suite of Paired Encoders and Decoders},
|
| 964 |
+
author={Orion Weller and Kathryn Ricci and Marc Marone and Antoine Chaffin and Dawn Lawrie and Benjamin Van Durme},
|
| 965 |
+
year={2025},
|
| 966 |
+
eprint={2507.11412},
|
| 967 |
+
archivePrefix={arXiv},
|
| 968 |
+
primaryClass={cs.CL},
|
| 969 |
+
url={https://arxiv.org/abs/2507.11412},
|
| 970 |
+
}
|
| 971 |
+
```
|
text_encoder/config.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_name_or_path": "ettin-encoder-150m",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"ModernBertForMaskedLM"
|
| 5 |
+
],
|
| 6 |
+
"attention_bias": false,
|
| 7 |
+
"attention_dropout": 0.0,
|
| 8 |
+
"bos_token_id": 50281,
|
| 9 |
+
"classifier_activation": "gelu",
|
| 10 |
+
"classifier_bias": false,
|
| 11 |
+
"classifier_dropout": 0.0,
|
| 12 |
+
"classifier_pooling": "mean",
|
| 13 |
+
"cls_token_id": 50281,
|
| 14 |
+
"decoder_bias": true,
|
| 15 |
+
"deterministic_flash_attn": false,
|
| 16 |
+
"embedding_dropout": 0.0,
|
| 17 |
+
"eos_token_id": 50282,
|
| 18 |
+
"global_attn_every_n_layers": 3,
|
| 19 |
+
"global_rope_theta": 160000.0,
|
| 20 |
+
"gradient_checkpointing": false,
|
| 21 |
+
"hidden_activation": "gelu",
|
| 22 |
+
"hidden_size": 768,
|
| 23 |
+
"initializer_cutoff_factor": 2.0,
|
| 24 |
+
"initializer_range": 0.02,
|
| 25 |
+
"intermediate_size": 1152,
|
| 26 |
+
"layer_norm_eps": 1e-05,
|
| 27 |
+
"local_attention": 128,
|
| 28 |
+
"local_rope_theta": 160000.0,
|
| 29 |
+
"max_position_embeddings": 7999,
|
| 30 |
+
"mlp_bias": false,
|
| 31 |
+
"mlp_dropout": 0.0,
|
| 32 |
+
"model_type": "modernbert",
|
| 33 |
+
"norm_bias": false,
|
| 34 |
+
"norm_eps": 1e-05,
|
| 35 |
+
"num_attention_heads": 12,
|
| 36 |
+
"num_hidden_layers": 22,
|
| 37 |
+
"pad_token_id": 50283,
|
| 38 |
+
"position_embedding_type": "sans_pos",
|
| 39 |
+
"sep_token_id": 50282,
|
| 40 |
+
"tie_word_embeddings": true,
|
| 41 |
+
"torch_dtype": "float32",
|
| 42 |
+
"transformers_version": "4.47.0.dev0",
|
| 43 |
+
"vocab_size": 50368,
|
| 44 |
+
"is_causal": false,
|
| 45 |
+
"causal_mask": false
|
| 46 |
+
}
|
text_encoder/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f95f652c607616a076c9e05c82f00f7372ce332de764dd016d230be6ab0d5864
|
| 3 |
+
size 298042336
|
text_encoder/special_tokens_map.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cls_token": {
|
| 3 |
+
"content": "[CLS]",
|
| 4 |
+
"lstrip": false,
|
| 5 |
+
"normalized": false,
|
| 6 |
+
"rstrip": false,
|
| 7 |
+
"single_word": false
|
| 8 |
+
},
|
| 9 |
+
"mask_token": {
|
| 10 |
+
"content": "[MASK]",
|
| 11 |
+
"lstrip": true,
|
| 12 |
+
"normalized": false,
|
| 13 |
+
"rstrip": false,
|
| 14 |
+
"single_word": false
|
| 15 |
+
},
|
| 16 |
+
"pad_token": {
|
| 17 |
+
"content": "[PAD]",
|
| 18 |
+
"lstrip": false,
|
| 19 |
+
"normalized": false,
|
| 20 |
+
"rstrip": false,
|
| 21 |
+
"single_word": false
|
| 22 |
+
},
|
| 23 |
+
"sep_token": {
|
| 24 |
+
"content": "[SEP]",
|
| 25 |
+
"lstrip": false,
|
| 26 |
+
"normalized": false,
|
| 27 |
+
"rstrip": false,
|
| 28 |
+
"single_word": false
|
| 29 |
+
},
|
| 30 |
+
"unk_token": {
|
| 31 |
+
"content": "[UNK]",
|
| 32 |
+
"lstrip": false,
|
| 33 |
+
"normalized": false,
|
| 34 |
+
"rstrip": false,
|
| 35 |
+
"single_word": false
|
| 36 |
+
}
|
| 37 |
+
}
|
text_encoder/tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
text_encoder/tokenizer_config.json
ADDED
|
@@ -0,0 +1,944 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"added_tokens_decoder": {
|
| 3 |
+
"0": {
|
| 4 |
+
"content": "|||IP_ADDRESS|||",
|
| 5 |
+
"lstrip": false,
|
| 6 |
+
"normalized": true,
|
| 7 |
+
"rstrip": false,
|
| 8 |
+
"single_word": false,
|
| 9 |
+
"special": false
|
| 10 |
+
},
|
| 11 |
+
"1": {
|
| 12 |
+
"content": "<|padding|>",
|
| 13 |
+
"lstrip": false,
|
| 14 |
+
"normalized": false,
|
| 15 |
+
"rstrip": false,
|
| 16 |
+
"single_word": false,
|
| 17 |
+
"special": true
|
| 18 |
+
},
|
| 19 |
+
"50254": {
|
| 20 |
+
"content": " ",
|
| 21 |
+
"lstrip": false,
|
| 22 |
+
"normalized": true,
|
| 23 |
+
"rstrip": false,
|
| 24 |
+
"single_word": false,
|
| 25 |
+
"special": false
|
| 26 |
+
},
|
| 27 |
+
"50255": {
|
| 28 |
+
"content": " ",
|
| 29 |
+
"lstrip": false,
|
| 30 |
+
"normalized": true,
|
| 31 |
+
"rstrip": false,
|
| 32 |
+
"single_word": false,
|
| 33 |
+
"special": false
|
| 34 |
+
},
|
| 35 |
+
"50256": {
|
| 36 |
+
"content": " ",
|
| 37 |
+
"lstrip": false,
|
| 38 |
+
"normalized": true,
|
| 39 |
+
"rstrip": false,
|
| 40 |
+
"single_word": false,
|
| 41 |
+
"special": false
|
| 42 |
+
},
|
| 43 |
+
"50257": {
|
| 44 |
+
"content": " ",
|
| 45 |
+
"lstrip": false,
|
| 46 |
+
"normalized": true,
|
| 47 |
+
"rstrip": false,
|
| 48 |
+
"single_word": false,
|
| 49 |
+
"special": false
|
| 50 |
+
},
|
| 51 |
+
"50258": {
|
| 52 |
+
"content": " ",
|
| 53 |
+
"lstrip": false,
|
| 54 |
+
"normalized": true,
|
| 55 |
+
"rstrip": false,
|
| 56 |
+
"single_word": false,
|
| 57 |
+
"special": false
|
| 58 |
+
},
|
| 59 |
+
"50259": {
|
| 60 |
+
"content": " ",
|
| 61 |
+
"lstrip": false,
|
| 62 |
+
"normalized": true,
|
| 63 |
+
"rstrip": false,
|
| 64 |
+
"single_word": false,
|
| 65 |
+
"special": false
|
| 66 |
+
},
|
| 67 |
+
"50260": {
|
| 68 |
+
"content": " ",
|
| 69 |
+
"lstrip": false,
|
| 70 |
+
"normalized": true,
|
| 71 |
+
"rstrip": false,
|
| 72 |
+
"single_word": false,
|
| 73 |
+
"special": false
|
| 74 |
+
},
|
| 75 |
+
"50261": {
|
| 76 |
+
"content": " ",
|
| 77 |
+
"lstrip": false,
|
| 78 |
+
"normalized": true,
|
| 79 |
+
"rstrip": false,
|
| 80 |
+
"single_word": false,
|
| 81 |
+
"special": false
|
| 82 |
+
},
|
| 83 |
+
"50262": {
|
| 84 |
+
"content": " ",
|
| 85 |
+
"lstrip": false,
|
| 86 |
+
"normalized": true,
|
| 87 |
+
"rstrip": false,
|
| 88 |
+
"single_word": false,
|
| 89 |
+
"special": false
|
| 90 |
+
},
|
| 91 |
+
"50263": {
|
| 92 |
+
"content": " ",
|
| 93 |
+
"lstrip": false,
|
| 94 |
+
"normalized": true,
|
| 95 |
+
"rstrip": false,
|
| 96 |
+
"single_word": false,
|
| 97 |
+
"special": false
|
| 98 |
+
},
|
| 99 |
+
"50264": {
|
| 100 |
+
"content": " ",
|
| 101 |
+
"lstrip": false,
|
| 102 |
+
"normalized": true,
|
| 103 |
+
"rstrip": false,
|
| 104 |
+
"single_word": false,
|
| 105 |
+
"special": false
|
| 106 |
+
},
|
| 107 |
+
"50265": {
|
| 108 |
+
"content": " ",
|
| 109 |
+
"lstrip": false,
|
| 110 |
+
"normalized": true,
|
| 111 |
+
"rstrip": false,
|
| 112 |
+
"single_word": false,
|
| 113 |
+
"special": false
|
| 114 |
+
},
|
| 115 |
+
"50266": {
|
| 116 |
+
"content": " ",
|
| 117 |
+
"lstrip": false,
|
| 118 |
+
"normalized": true,
|
| 119 |
+
"rstrip": false,
|
| 120 |
+
"single_word": false,
|
| 121 |
+
"special": false
|
| 122 |
+
},
|
| 123 |
+
"50267": {
|
| 124 |
+
"content": " ",
|
| 125 |
+
"lstrip": false,
|
| 126 |
+
"normalized": true,
|
| 127 |
+
"rstrip": false,
|
| 128 |
+
"single_word": false,
|
| 129 |
+
"special": false
|
| 130 |
+
},
|
| 131 |
+
"50268": {
|
| 132 |
+
"content": " ",
|
| 133 |
+
"lstrip": false,
|
| 134 |
+
"normalized": true,
|
| 135 |
+
"rstrip": false,
|
| 136 |
+
"single_word": false,
|
| 137 |
+
"special": false
|
| 138 |
+
},
|
| 139 |
+
"50269": {
|
| 140 |
+
"content": " ",
|
| 141 |
+
"lstrip": false,
|
| 142 |
+
"normalized": true,
|
| 143 |
+
"rstrip": false,
|
| 144 |
+
"single_word": false,
|
| 145 |
+
"special": false
|
| 146 |
+
},
|
| 147 |
+
"50270": {
|
| 148 |
+
"content": " ",
|
| 149 |
+
"lstrip": false,
|
| 150 |
+
"normalized": true,
|
| 151 |
+
"rstrip": false,
|
| 152 |
+
"single_word": false,
|
| 153 |
+
"special": false
|
| 154 |
+
},
|
| 155 |
+
"50271": {
|
| 156 |
+
"content": " ",
|
| 157 |
+
"lstrip": false,
|
| 158 |
+
"normalized": true,
|
| 159 |
+
"rstrip": false,
|
| 160 |
+
"single_word": false,
|
| 161 |
+
"special": false
|
| 162 |
+
},
|
| 163 |
+
"50272": {
|
| 164 |
+
"content": " ",
|
| 165 |
+
"lstrip": false,
|
| 166 |
+
"normalized": true,
|
| 167 |
+
"rstrip": false,
|
| 168 |
+
"single_word": false,
|
| 169 |
+
"special": false
|
| 170 |
+
},
|
| 171 |
+
"50273": {
|
| 172 |
+
"content": " ",
|
| 173 |
+
"lstrip": false,
|
| 174 |
+
"normalized": true,
|
| 175 |
+
"rstrip": false,
|
| 176 |
+
"single_word": false,
|
| 177 |
+
"special": false
|
| 178 |
+
},
|
| 179 |
+
"50274": {
|
| 180 |
+
"content": " ",
|
| 181 |
+
"lstrip": false,
|
| 182 |
+
"normalized": true,
|
| 183 |
+
"rstrip": false,
|
| 184 |
+
"single_word": false,
|
| 185 |
+
"special": false
|
| 186 |
+
},
|
| 187 |
+
"50275": {
|
| 188 |
+
"content": " ",
|
| 189 |
+
"lstrip": false,
|
| 190 |
+
"normalized": true,
|
| 191 |
+
"rstrip": false,
|
| 192 |
+
"single_word": false,
|
| 193 |
+
"special": false
|
| 194 |
+
},
|
| 195 |
+
"50276": {
|
| 196 |
+
"content": " ",
|
| 197 |
+
"lstrip": false,
|
| 198 |
+
"normalized": true,
|
| 199 |
+
"rstrip": false,
|
| 200 |
+
"single_word": false,
|
| 201 |
+
"special": false
|
| 202 |
+
},
|
| 203 |
+
"50277": {
|
| 204 |
+
"content": "|||EMAIL_ADDRESS|||",
|
| 205 |
+
"lstrip": false,
|
| 206 |
+
"normalized": true,
|
| 207 |
+
"rstrip": false,
|
| 208 |
+
"single_word": false,
|
| 209 |
+
"special": false
|
| 210 |
+
},
|
| 211 |
+
"50278": {
|
| 212 |
+
"content": "|||PHONE_NUMBER|||",
|
| 213 |
+
"lstrip": false,
|
| 214 |
+
"normalized": true,
|
| 215 |
+
"rstrip": false,
|
| 216 |
+
"single_word": false,
|
| 217 |
+
"special": false
|
| 218 |
+
},
|
| 219 |
+
"50279": {
|
| 220 |
+
"content": "<|endoftext|>",
|
| 221 |
+
"lstrip": false,
|
| 222 |
+
"normalized": false,
|
| 223 |
+
"rstrip": false,
|
| 224 |
+
"single_word": false,
|
| 225 |
+
"special": true
|
| 226 |
+
},
|
| 227 |
+
"50280": {
|
| 228 |
+
"content": "[UNK]",
|
| 229 |
+
"lstrip": false,
|
| 230 |
+
"normalized": false,
|
| 231 |
+
"rstrip": false,
|
| 232 |
+
"single_word": false,
|
| 233 |
+
"special": true
|
| 234 |
+
},
|
| 235 |
+
"50281": {
|
| 236 |
+
"content": "[CLS]",
|
| 237 |
+
"lstrip": false,
|
| 238 |
+
"normalized": false,
|
| 239 |
+
"rstrip": false,
|
| 240 |
+
"single_word": false,
|
| 241 |
+
"special": true
|
| 242 |
+
},
|
| 243 |
+
"50282": {
|
| 244 |
+
"content": "[SEP]",
|
| 245 |
+
"lstrip": false,
|
| 246 |
+
"normalized": false,
|
| 247 |
+
"rstrip": false,
|
| 248 |
+
"single_word": false,
|
| 249 |
+
"special": true
|
| 250 |
+
},
|
| 251 |
+
"50283": {
|
| 252 |
+
"content": "[PAD]",
|
| 253 |
+
"lstrip": false,
|
| 254 |
+
"normalized": false,
|
| 255 |
+
"rstrip": false,
|
| 256 |
+
"single_word": false,
|
| 257 |
+
"special": true
|
| 258 |
+
},
|
| 259 |
+
"50284": {
|
| 260 |
+
"content": "[MASK]",
|
| 261 |
+
"lstrip": true,
|
| 262 |
+
"normalized": false,
|
| 263 |
+
"rstrip": false,
|
| 264 |
+
"single_word": false,
|
| 265 |
+
"special": true
|
| 266 |
+
},
|
| 267 |
+
"50285": {
|
| 268 |
+
"content": "[unused0]",
|
| 269 |
+
"lstrip": false,
|
| 270 |
+
"normalized": true,
|
| 271 |
+
"rstrip": false,
|
| 272 |
+
"single_word": false,
|
| 273 |
+
"special": false
|
| 274 |
+
},
|
| 275 |
+
"50286": {
|
| 276 |
+
"content": "[unused1]",
|
| 277 |
+
"lstrip": false,
|
| 278 |
+
"normalized": true,
|
| 279 |
+
"rstrip": false,
|
| 280 |
+
"single_word": false,
|
| 281 |
+
"special": false
|
| 282 |
+
},
|
| 283 |
+
"50287": {
|
| 284 |
+
"content": "[unused2]",
|
| 285 |
+
"lstrip": false,
|
| 286 |
+
"normalized": true,
|
| 287 |
+
"rstrip": false,
|
| 288 |
+
"single_word": false,
|
| 289 |
+
"special": false
|
| 290 |
+
},
|
| 291 |
+
"50288": {
|
| 292 |
+
"content": "[unused3]",
|
| 293 |
+
"lstrip": false,
|
| 294 |
+
"normalized": true,
|
| 295 |
+
"rstrip": false,
|
| 296 |
+
"single_word": false,
|
| 297 |
+
"special": false
|
| 298 |
+
},
|
| 299 |
+
"50289": {
|
| 300 |
+
"content": "[unused4]",
|
| 301 |
+
"lstrip": false,
|
| 302 |
+
"normalized": true,
|
| 303 |
+
"rstrip": false,
|
| 304 |
+
"single_word": false,
|
| 305 |
+
"special": false
|
| 306 |
+
},
|
| 307 |
+
"50290": {
|
| 308 |
+
"content": "[unused5]",
|
| 309 |
+
"lstrip": false,
|
| 310 |
+
"normalized": true,
|
| 311 |
+
"rstrip": false,
|
| 312 |
+
"single_word": false,
|
| 313 |
+
"special": false
|
| 314 |
+
},
|
| 315 |
+
"50291": {
|
| 316 |
+
"content": "[unused6]",
|
| 317 |
+
"lstrip": false,
|
| 318 |
+
"normalized": true,
|
| 319 |
+
"rstrip": false,
|
| 320 |
+
"single_word": false,
|
| 321 |
+
"special": false
|
| 322 |
+
},
|
| 323 |
+
"50292": {
|
| 324 |
+
"content": "[unused7]",
|
| 325 |
+
"lstrip": false,
|
| 326 |
+
"normalized": true,
|
| 327 |
+
"rstrip": false,
|
| 328 |
+
"single_word": false,
|
| 329 |
+
"special": false
|
| 330 |
+
},
|
| 331 |
+
"50293": {
|
| 332 |
+
"content": "[unused8]",
|
| 333 |
+
"lstrip": false,
|
| 334 |
+
"normalized": true,
|
| 335 |
+
"rstrip": false,
|
| 336 |
+
"single_word": false,
|
| 337 |
+
"special": false
|
| 338 |
+
},
|
| 339 |
+
"50294": {
|
| 340 |
+
"content": "[unused9]",
|
| 341 |
+
"lstrip": false,
|
| 342 |
+
"normalized": true,
|
| 343 |
+
"rstrip": false,
|
| 344 |
+
"single_word": false,
|
| 345 |
+
"special": false
|
| 346 |
+
},
|
| 347 |
+
"50295": {
|
| 348 |
+
"content": "[unused10]",
|
| 349 |
+
"lstrip": false,
|
| 350 |
+
"normalized": true,
|
| 351 |
+
"rstrip": false,
|
| 352 |
+
"single_word": false,
|
| 353 |
+
"special": false
|
| 354 |
+
},
|
| 355 |
+
"50296": {
|
| 356 |
+
"content": "[unused11]",
|
| 357 |
+
"lstrip": false,
|
| 358 |
+
"normalized": true,
|
| 359 |
+
"rstrip": false,
|
| 360 |
+
"single_word": false,
|
| 361 |
+
"special": false
|
| 362 |
+
},
|
| 363 |
+
"50297": {
|
| 364 |
+
"content": "[unused12]",
|
| 365 |
+
"lstrip": false,
|
| 366 |
+
"normalized": true,
|
| 367 |
+
"rstrip": false,
|
| 368 |
+
"single_word": false,
|
| 369 |
+
"special": false
|
| 370 |
+
},
|
| 371 |
+
"50298": {
|
| 372 |
+
"content": "[unused13]",
|
| 373 |
+
"lstrip": false,
|
| 374 |
+
"normalized": true,
|
| 375 |
+
"rstrip": false,
|
| 376 |
+
"single_word": false,
|
| 377 |
+
"special": false
|
| 378 |
+
},
|
| 379 |
+
"50299": {
|
| 380 |
+
"content": "[unused14]",
|
| 381 |
+
"lstrip": false,
|
| 382 |
+
"normalized": true,
|
| 383 |
+
"rstrip": false,
|
| 384 |
+
"single_word": false,
|
| 385 |
+
"special": false
|
| 386 |
+
},
|
| 387 |
+
"50300": {
|
| 388 |
+
"content": "[unused15]",
|
| 389 |
+
"lstrip": false,
|
| 390 |
+
"normalized": true,
|
| 391 |
+
"rstrip": false,
|
| 392 |
+
"single_word": false,
|
| 393 |
+
"special": false
|
| 394 |
+
},
|
| 395 |
+
"50301": {
|
| 396 |
+
"content": "[unused16]",
|
| 397 |
+
"lstrip": false,
|
| 398 |
+
"normalized": true,
|
| 399 |
+
"rstrip": false,
|
| 400 |
+
"single_word": false,
|
| 401 |
+
"special": false
|
| 402 |
+
},
|
| 403 |
+
"50302": {
|
| 404 |
+
"content": "[unused17]",
|
| 405 |
+
"lstrip": false,
|
| 406 |
+
"normalized": true,
|
| 407 |
+
"rstrip": false,
|
| 408 |
+
"single_word": false,
|
| 409 |
+
"special": false
|
| 410 |
+
},
|
| 411 |
+
"50303": {
|
| 412 |
+
"content": "[unused18]",
|
| 413 |
+
"lstrip": false,
|
| 414 |
+
"normalized": true,
|
| 415 |
+
"rstrip": false,
|
| 416 |
+
"single_word": false,
|
| 417 |
+
"special": false
|
| 418 |
+
},
|
| 419 |
+
"50304": {
|
| 420 |
+
"content": "[unused19]",
|
| 421 |
+
"lstrip": false,
|
| 422 |
+
"normalized": true,
|
| 423 |
+
"rstrip": false,
|
| 424 |
+
"single_word": false,
|
| 425 |
+
"special": false
|
| 426 |
+
},
|
| 427 |
+
"50305": {
|
| 428 |
+
"content": "[unused20]",
|
| 429 |
+
"lstrip": false,
|
| 430 |
+
"normalized": true,
|
| 431 |
+
"rstrip": false,
|
| 432 |
+
"single_word": false,
|
| 433 |
+
"special": false
|
| 434 |
+
},
|
| 435 |
+
"50306": {
|
| 436 |
+
"content": "[unused21]",
|
| 437 |
+
"lstrip": false,
|
| 438 |
+
"normalized": true,
|
| 439 |
+
"rstrip": false,
|
| 440 |
+
"single_word": false,
|
| 441 |
+
"special": false
|
| 442 |
+
},
|
| 443 |
+
"50307": {
|
| 444 |
+
"content": "[unused22]",
|
| 445 |
+
"lstrip": false,
|
| 446 |
+
"normalized": true,
|
| 447 |
+
"rstrip": false,
|
| 448 |
+
"single_word": false,
|
| 449 |
+
"special": false
|
| 450 |
+
},
|
| 451 |
+
"50308": {
|
| 452 |
+
"content": "[unused23]",
|
| 453 |
+
"lstrip": false,
|
| 454 |
+
"normalized": true,
|
| 455 |
+
"rstrip": false,
|
| 456 |
+
"single_word": false,
|
| 457 |
+
"special": false
|
| 458 |
+
},
|
| 459 |
+
"50309": {
|
| 460 |
+
"content": "[unused24]",
|
| 461 |
+
"lstrip": false,
|
| 462 |
+
"normalized": true,
|
| 463 |
+
"rstrip": false,
|
| 464 |
+
"single_word": false,
|
| 465 |
+
"special": false
|
| 466 |
+
},
|
| 467 |
+
"50310": {
|
| 468 |
+
"content": "[unused25]",
|
| 469 |
+
"lstrip": false,
|
| 470 |
+
"normalized": true,
|
| 471 |
+
"rstrip": false,
|
| 472 |
+
"single_word": false,
|
| 473 |
+
"special": false
|
| 474 |
+
},
|
| 475 |
+
"50311": {
|
| 476 |
+
"content": "[unused26]",
|
| 477 |
+
"lstrip": false,
|
| 478 |
+
"normalized": true,
|
| 479 |
+
"rstrip": false,
|
| 480 |
+
"single_word": false,
|
| 481 |
+
"special": false
|
| 482 |
+
},
|
| 483 |
+
"50312": {
|
| 484 |
+
"content": "[unused27]",
|
| 485 |
+
"lstrip": false,
|
| 486 |
+
"normalized": true,
|
| 487 |
+
"rstrip": false,
|
| 488 |
+
"single_word": false,
|
| 489 |
+
"special": false
|
| 490 |
+
},
|
| 491 |
+
"50313": {
|
| 492 |
+
"content": "[unused28]",
|
| 493 |
+
"lstrip": false,
|
| 494 |
+
"normalized": true,
|
| 495 |
+
"rstrip": false,
|
| 496 |
+
"single_word": false,
|
| 497 |
+
"special": false
|
| 498 |
+
},
|
| 499 |
+
"50314": {
|
| 500 |
+
"content": "[unused29]",
|
| 501 |
+
"lstrip": false,
|
| 502 |
+
"normalized": true,
|
| 503 |
+
"rstrip": false,
|
| 504 |
+
"single_word": false,
|
| 505 |
+
"special": false
|
| 506 |
+
},
|
| 507 |
+
"50315": {
|
| 508 |
+
"content": "[unused30]",
|
| 509 |
+
"lstrip": false,
|
| 510 |
+
"normalized": true,
|
| 511 |
+
"rstrip": false,
|
| 512 |
+
"single_word": false,
|
| 513 |
+
"special": false
|
| 514 |
+
},
|
| 515 |
+
"50316": {
|
| 516 |
+
"content": "[unused31]",
|
| 517 |
+
"lstrip": false,
|
| 518 |
+
"normalized": true,
|
| 519 |
+
"rstrip": false,
|
| 520 |
+
"single_word": false,
|
| 521 |
+
"special": false
|
| 522 |
+
},
|
| 523 |
+
"50317": {
|
| 524 |
+
"content": "[unused32]",
|
| 525 |
+
"lstrip": false,
|
| 526 |
+
"normalized": true,
|
| 527 |
+
"rstrip": false,
|
| 528 |
+
"single_word": false,
|
| 529 |
+
"special": false
|
| 530 |
+
},
|
| 531 |
+
"50318": {
|
| 532 |
+
"content": "[unused33]",
|
| 533 |
+
"lstrip": false,
|
| 534 |
+
"normalized": true,
|
| 535 |
+
"rstrip": false,
|
| 536 |
+
"single_word": false,
|
| 537 |
+
"special": false
|
| 538 |
+
},
|
| 539 |
+
"50319": {
|
| 540 |
+
"content": "[unused34]",
|
| 541 |
+
"lstrip": false,
|
| 542 |
+
"normalized": true,
|
| 543 |
+
"rstrip": false,
|
| 544 |
+
"single_word": false,
|
| 545 |
+
"special": false
|
| 546 |
+
},
|
| 547 |
+
"50320": {
|
| 548 |
+
"content": "[unused35]",
|
| 549 |
+
"lstrip": false,
|
| 550 |
+
"normalized": true,
|
| 551 |
+
"rstrip": false,
|
| 552 |
+
"single_word": false,
|
| 553 |
+
"special": false
|
| 554 |
+
},
|
| 555 |
+
"50321": {
|
| 556 |
+
"content": "[unused36]",
|
| 557 |
+
"lstrip": false,
|
| 558 |
+
"normalized": true,
|
| 559 |
+
"rstrip": false,
|
| 560 |
+
"single_word": false,
|
| 561 |
+
"special": false
|
| 562 |
+
},
|
| 563 |
+
"50322": {
|
| 564 |
+
"content": "[unused37]",
|
| 565 |
+
"lstrip": false,
|
| 566 |
+
"normalized": true,
|
| 567 |
+
"rstrip": false,
|
| 568 |
+
"single_word": false,
|
| 569 |
+
"special": false
|
| 570 |
+
},
|
| 571 |
+
"50323": {
|
| 572 |
+
"content": "[unused38]",
|
| 573 |
+
"lstrip": false,
|
| 574 |
+
"normalized": true,
|
| 575 |
+
"rstrip": false,
|
| 576 |
+
"single_word": false,
|
| 577 |
+
"special": false
|
| 578 |
+
},
|
| 579 |
+
"50324": {
|
| 580 |
+
"content": "[unused39]",
|
| 581 |
+
"lstrip": false,
|
| 582 |
+
"normalized": true,
|
| 583 |
+
"rstrip": false,
|
| 584 |
+
"single_word": false,
|
| 585 |
+
"special": false
|
| 586 |
+
},
|
| 587 |
+
"50325": {
|
| 588 |
+
"content": "[unused40]",
|
| 589 |
+
"lstrip": false,
|
| 590 |
+
"normalized": true,
|
| 591 |
+
"rstrip": false,
|
| 592 |
+
"single_word": false,
|
| 593 |
+
"special": false
|
| 594 |
+
},
|
| 595 |
+
"50326": {
|
| 596 |
+
"content": "[unused41]",
|
| 597 |
+
"lstrip": false,
|
| 598 |
+
"normalized": true,
|
| 599 |
+
"rstrip": false,
|
| 600 |
+
"single_word": false,
|
| 601 |
+
"special": false
|
| 602 |
+
},
|
| 603 |
+
"50327": {
|
| 604 |
+
"content": "[unused42]",
|
| 605 |
+
"lstrip": false,
|
| 606 |
+
"normalized": true,
|
| 607 |
+
"rstrip": false,
|
| 608 |
+
"single_word": false,
|
| 609 |
+
"special": false
|
| 610 |
+
},
|
| 611 |
+
"50328": {
|
| 612 |
+
"content": "[unused43]",
|
| 613 |
+
"lstrip": false,
|
| 614 |
+
"normalized": true,
|
| 615 |
+
"rstrip": false,
|
| 616 |
+
"single_word": false,
|
| 617 |
+
"special": false
|
| 618 |
+
},
|
| 619 |
+
"50329": {
|
| 620 |
+
"content": "[unused44]",
|
| 621 |
+
"lstrip": false,
|
| 622 |
+
"normalized": true,
|
| 623 |
+
"rstrip": false,
|
| 624 |
+
"single_word": false,
|
| 625 |
+
"special": false
|
| 626 |
+
},
|
| 627 |
+
"50330": {
|
| 628 |
+
"content": "[unused45]",
|
| 629 |
+
"lstrip": false,
|
| 630 |
+
"normalized": true,
|
| 631 |
+
"rstrip": false,
|
| 632 |
+
"single_word": false,
|
| 633 |
+
"special": false
|
| 634 |
+
},
|
| 635 |
+
"50331": {
|
| 636 |
+
"content": "[unused46]",
|
| 637 |
+
"lstrip": false,
|
| 638 |
+
"normalized": true,
|
| 639 |
+
"rstrip": false,
|
| 640 |
+
"single_word": false,
|
| 641 |
+
"special": false
|
| 642 |
+
},
|
| 643 |
+
"50332": {
|
| 644 |
+
"content": "[unused47]",
|
| 645 |
+
"lstrip": false,
|
| 646 |
+
"normalized": true,
|
| 647 |
+
"rstrip": false,
|
| 648 |
+
"single_word": false,
|
| 649 |
+
"special": false
|
| 650 |
+
},
|
| 651 |
+
"50333": {
|
| 652 |
+
"content": "[unused48]",
|
| 653 |
+
"lstrip": false,
|
| 654 |
+
"normalized": true,
|
| 655 |
+
"rstrip": false,
|
| 656 |
+
"single_word": false,
|
| 657 |
+
"special": false
|
| 658 |
+
},
|
| 659 |
+
"50334": {
|
| 660 |
+
"content": "[unused49]",
|
| 661 |
+
"lstrip": false,
|
| 662 |
+
"normalized": true,
|
| 663 |
+
"rstrip": false,
|
| 664 |
+
"single_word": false,
|
| 665 |
+
"special": false
|
| 666 |
+
},
|
| 667 |
+
"50335": {
|
| 668 |
+
"content": "[unused50]",
|
| 669 |
+
"lstrip": false,
|
| 670 |
+
"normalized": true,
|
| 671 |
+
"rstrip": false,
|
| 672 |
+
"single_word": false,
|
| 673 |
+
"special": false
|
| 674 |
+
},
|
| 675 |
+
"50336": {
|
| 676 |
+
"content": "[unused51]",
|
| 677 |
+
"lstrip": false,
|
| 678 |
+
"normalized": true,
|
| 679 |
+
"rstrip": false,
|
| 680 |
+
"single_word": false,
|
| 681 |
+
"special": false
|
| 682 |
+
},
|
| 683 |
+
"50337": {
|
| 684 |
+
"content": "[unused52]",
|
| 685 |
+
"lstrip": false,
|
| 686 |
+
"normalized": true,
|
| 687 |
+
"rstrip": false,
|
| 688 |
+
"single_word": false,
|
| 689 |
+
"special": false
|
| 690 |
+
},
|
| 691 |
+
"50338": {
|
| 692 |
+
"content": "[unused53]",
|
| 693 |
+
"lstrip": false,
|
| 694 |
+
"normalized": true,
|
| 695 |
+
"rstrip": false,
|
| 696 |
+
"single_word": false,
|
| 697 |
+
"special": false
|
| 698 |
+
},
|
| 699 |
+
"50339": {
|
| 700 |
+
"content": "[unused54]",
|
| 701 |
+
"lstrip": false,
|
| 702 |
+
"normalized": true,
|
| 703 |
+
"rstrip": false,
|
| 704 |
+
"single_word": false,
|
| 705 |
+
"special": false
|
| 706 |
+
},
|
| 707 |
+
"50340": {
|
| 708 |
+
"content": "[unused55]",
|
| 709 |
+
"lstrip": false,
|
| 710 |
+
"normalized": true,
|
| 711 |
+
"rstrip": false,
|
| 712 |
+
"single_word": false,
|
| 713 |
+
"special": false
|
| 714 |
+
},
|
| 715 |
+
"50341": {
|
| 716 |
+
"content": "[unused56]",
|
| 717 |
+
"lstrip": false,
|
| 718 |
+
"normalized": true,
|
| 719 |
+
"rstrip": false,
|
| 720 |
+
"single_word": false,
|
| 721 |
+
"special": false
|
| 722 |
+
},
|
| 723 |
+
"50342": {
|
| 724 |
+
"content": "[unused57]",
|
| 725 |
+
"lstrip": false,
|
| 726 |
+
"normalized": true,
|
| 727 |
+
"rstrip": false,
|
| 728 |
+
"single_word": false,
|
| 729 |
+
"special": false
|
| 730 |
+
},
|
| 731 |
+
"50343": {
|
| 732 |
+
"content": "[unused58]",
|
| 733 |
+
"lstrip": false,
|
| 734 |
+
"normalized": true,
|
| 735 |
+
"rstrip": false,
|
| 736 |
+
"single_word": false,
|
| 737 |
+
"special": false
|
| 738 |
+
},
|
| 739 |
+
"50344": {
|
| 740 |
+
"content": "[unused59]",
|
| 741 |
+
"lstrip": false,
|
| 742 |
+
"normalized": true,
|
| 743 |
+
"rstrip": false,
|
| 744 |
+
"single_word": false,
|
| 745 |
+
"special": false
|
| 746 |
+
},
|
| 747 |
+
"50345": {
|
| 748 |
+
"content": "[unused60]",
|
| 749 |
+
"lstrip": false,
|
| 750 |
+
"normalized": true,
|
| 751 |
+
"rstrip": false,
|
| 752 |
+
"single_word": false,
|
| 753 |
+
"special": false
|
| 754 |
+
},
|
| 755 |
+
"50346": {
|
| 756 |
+
"content": "[unused61]",
|
| 757 |
+
"lstrip": false,
|
| 758 |
+
"normalized": true,
|
| 759 |
+
"rstrip": false,
|
| 760 |
+
"single_word": false,
|
| 761 |
+
"special": false
|
| 762 |
+
},
|
| 763 |
+
"50347": {
|
| 764 |
+
"content": "[unused62]",
|
| 765 |
+
"lstrip": false,
|
| 766 |
+
"normalized": true,
|
| 767 |
+
"rstrip": false,
|
| 768 |
+
"single_word": false,
|
| 769 |
+
"special": false
|
| 770 |
+
},
|
| 771 |
+
"50348": {
|
| 772 |
+
"content": "[unused63]",
|
| 773 |
+
"lstrip": false,
|
| 774 |
+
"normalized": true,
|
| 775 |
+
"rstrip": false,
|
| 776 |
+
"single_word": false,
|
| 777 |
+
"special": false
|
| 778 |
+
},
|
| 779 |
+
"50349": {
|
| 780 |
+
"content": "[unused64]",
|
| 781 |
+
"lstrip": false,
|
| 782 |
+
"normalized": true,
|
| 783 |
+
"rstrip": false,
|
| 784 |
+
"single_word": false,
|
| 785 |
+
"special": false
|
| 786 |
+
},
|
| 787 |
+
"50350": {
|
| 788 |
+
"content": "[unused65]",
|
| 789 |
+
"lstrip": false,
|
| 790 |
+
"normalized": true,
|
| 791 |
+
"rstrip": false,
|
| 792 |
+
"single_word": false,
|
| 793 |
+
"special": false
|
| 794 |
+
},
|
| 795 |
+
"50351": {
|
| 796 |
+
"content": "[unused66]",
|
| 797 |
+
"lstrip": false,
|
| 798 |
+
"normalized": true,
|
| 799 |
+
"rstrip": false,
|
| 800 |
+
"single_word": false,
|
| 801 |
+
"special": false
|
| 802 |
+
},
|
| 803 |
+
"50352": {
|
| 804 |
+
"content": "[unused67]",
|
| 805 |
+
"lstrip": false,
|
| 806 |
+
"normalized": true,
|
| 807 |
+
"rstrip": false,
|
| 808 |
+
"single_word": false,
|
| 809 |
+
"special": false
|
| 810 |
+
},
|
| 811 |
+
"50353": {
|
| 812 |
+
"content": "[unused68]",
|
| 813 |
+
"lstrip": false,
|
| 814 |
+
"normalized": true,
|
| 815 |
+
"rstrip": false,
|
| 816 |
+
"single_word": false,
|
| 817 |
+
"special": false
|
| 818 |
+
},
|
| 819 |
+
"50354": {
|
| 820 |
+
"content": "[unused69]",
|
| 821 |
+
"lstrip": false,
|
| 822 |
+
"normalized": true,
|
| 823 |
+
"rstrip": false,
|
| 824 |
+
"single_word": false,
|
| 825 |
+
"special": false
|
| 826 |
+
},
|
| 827 |
+
"50355": {
|
| 828 |
+
"content": "[unused70]",
|
| 829 |
+
"lstrip": false,
|
| 830 |
+
"normalized": true,
|
| 831 |
+
"rstrip": false,
|
| 832 |
+
"single_word": false,
|
| 833 |
+
"special": false
|
| 834 |
+
},
|
| 835 |
+
"50356": {
|
| 836 |
+
"content": "[unused71]",
|
| 837 |
+
"lstrip": false,
|
| 838 |
+
"normalized": true,
|
| 839 |
+
"rstrip": false,
|
| 840 |
+
"single_word": false,
|
| 841 |
+
"special": false
|
| 842 |
+
},
|
| 843 |
+
"50357": {
|
| 844 |
+
"content": "[unused72]",
|
| 845 |
+
"lstrip": false,
|
| 846 |
+
"normalized": true,
|
| 847 |
+
"rstrip": false,
|
| 848 |
+
"single_word": false,
|
| 849 |
+
"special": false
|
| 850 |
+
},
|
| 851 |
+
"50358": {
|
| 852 |
+
"content": "[unused73]",
|
| 853 |
+
"lstrip": false,
|
| 854 |
+
"normalized": true,
|
| 855 |
+
"rstrip": false,
|
| 856 |
+
"single_word": false,
|
| 857 |
+
"special": false
|
| 858 |
+
},
|
| 859 |
+
"50359": {
|
| 860 |
+
"content": "[unused74]",
|
| 861 |
+
"lstrip": false,
|
| 862 |
+
"normalized": true,
|
| 863 |
+
"rstrip": false,
|
| 864 |
+
"single_word": false,
|
| 865 |
+
"special": false
|
| 866 |
+
},
|
| 867 |
+
"50360": {
|
| 868 |
+
"content": "[unused75]",
|
| 869 |
+
"lstrip": false,
|
| 870 |
+
"normalized": true,
|
| 871 |
+
"rstrip": false,
|
| 872 |
+
"single_word": false,
|
| 873 |
+
"special": false
|
| 874 |
+
},
|
| 875 |
+
"50361": {
|
| 876 |
+
"content": "[unused76]",
|
| 877 |
+
"lstrip": false,
|
| 878 |
+
"normalized": true,
|
| 879 |
+
"rstrip": false,
|
| 880 |
+
"single_word": false,
|
| 881 |
+
"special": false
|
| 882 |
+
},
|
| 883 |
+
"50362": {
|
| 884 |
+
"content": "[unused77]",
|
| 885 |
+
"lstrip": false,
|
| 886 |
+
"normalized": true,
|
| 887 |
+
"rstrip": false,
|
| 888 |
+
"single_word": false,
|
| 889 |
+
"special": false
|
| 890 |
+
},
|
| 891 |
+
"50363": {
|
| 892 |
+
"content": "[unused78]",
|
| 893 |
+
"lstrip": false,
|
| 894 |
+
"normalized": true,
|
| 895 |
+
"rstrip": false,
|
| 896 |
+
"single_word": false,
|
| 897 |
+
"special": false
|
| 898 |
+
},
|
| 899 |
+
"50364": {
|
| 900 |
+
"content": "[unused79]",
|
| 901 |
+
"lstrip": false,
|
| 902 |
+
"normalized": true,
|
| 903 |
+
"rstrip": false,
|
| 904 |
+
"single_word": false,
|
| 905 |
+
"special": false
|
| 906 |
+
},
|
| 907 |
+
"50365": {
|
| 908 |
+
"content": "[unused80]",
|
| 909 |
+
"lstrip": false,
|
| 910 |
+
"normalized": true,
|
| 911 |
+
"rstrip": false,
|
| 912 |
+
"single_word": false,
|
| 913 |
+
"special": false
|
| 914 |
+
},
|
| 915 |
+
"50366": {
|
| 916 |
+
"content": "[unused81]",
|
| 917 |
+
"lstrip": false,
|
| 918 |
+
"normalized": true,
|
| 919 |
+
"rstrip": false,
|
| 920 |
+
"single_word": false,
|
| 921 |
+
"special": false
|
| 922 |
+
},
|
| 923 |
+
"50367": {
|
| 924 |
+
"content": "[unused82]",
|
| 925 |
+
"lstrip": false,
|
| 926 |
+
"normalized": true,
|
| 927 |
+
"rstrip": false,
|
| 928 |
+
"single_word": false,
|
| 929 |
+
"special": false
|
| 930 |
+
}
|
| 931 |
+
},
|
| 932 |
+
"clean_up_tokenization_spaces": true,
|
| 933 |
+
"cls_token": "[CLS]",
|
| 934 |
+
"mask_token": "[MASK]",
|
| 935 |
+
"model_max_length": 8192,
|
| 936 |
+
"pad_token": "[PAD]",
|
| 937 |
+
"sep_token": "[SEP]",
|
| 938 |
+
"tokenizer_class": "PreTrainedTokenizerFast",
|
| 939 |
+
"model_input_names": [
|
| 940 |
+
"input_ids",
|
| 941 |
+
"attention_mask"
|
| 942 |
+
],
|
| 943 |
+
"unk_token": "[UNK]"
|
| 944 |
+
}
|
transformer/config.json
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "UDiT",
|
| 3 |
+
"name": "Ra",
|
| 4 |
+
"format": "udm/v1",
|
| 5 |
+
"step": 212000,
|
| 6 |
+
"latent": {
|
| 7 |
+
"channels": 32,
|
| 8 |
+
"spatial_factor": 8,
|
| 9 |
+
"mean": [
|
| 10 |
+
-4.137261,
|
| 11 |
+
2.725146,
|
| 12 |
+
-1.435803,
|
| 13 |
+
-2.187023,
|
| 14 |
+
-4.161275,
|
| 15 |
+
-1.58918,
|
| 16 |
+
-2.527523,
|
| 17 |
+
3.002302,
|
| 18 |
+
2.793705,
|
| 19 |
+
-2.091501,
|
| 20 |
+
-2.632896,
|
| 21 |
+
-3.59589,
|
| 22 |
+
-3.069669,
|
| 23 |
+
-4.568713,
|
| 24 |
+
3.26668,
|
| 25 |
+
-0.897648,
|
| 26 |
+
2.072541,
|
| 27 |
+
5.451158,
|
| 28 |
+
1.747871,
|
| 29 |
+
-2.113284,
|
| 30 |
+
2.03444,
|
| 31 |
+
-1.379274,
|
| 32 |
+
0.66369,
|
| 33 |
+
2.712183,
|
| 34 |
+
-0.111181,
|
| 35 |
+
6.054838,
|
| 36 |
+
1.235881,
|
| 37 |
+
-2.02777,
|
| 38 |
+
3.663243,
|
| 39 |
+
-2.454632,
|
| 40 |
+
1.141335,
|
| 41 |
+
1.786136
|
| 42 |
+
],
|
| 43 |
+
"std": [
|
| 44 |
+
1.030738,
|
| 45 |
+
0.951682,
|
| 46 |
+
1.079687,
|
| 47 |
+
0.957871,
|
| 48 |
+
1.198266,
|
| 49 |
+
1.102266,
|
| 50 |
+
1.213605,
|
| 51 |
+
0.947544,
|
| 52 |
+
1.085991,
|
| 53 |
+
1.093223,
|
| 54 |
+
0.9027,
|
| 55 |
+
1.010205,
|
| 56 |
+
0.920466,
|
| 57 |
+
1.044107,
|
| 58 |
+
1.023179,
|
| 59 |
+
0.992978,
|
| 60 |
+
0.991627,
|
| 61 |
+
1.073585,
|
| 62 |
+
1.114629,
|
| 63 |
+
1.056403,
|
| 64 |
+
1.093012,
|
| 65 |
+
1.015109,
|
| 66 |
+
1.007069,
|
| 67 |
+
0.976026,
|
| 68 |
+
0.982324,
|
| 69 |
+
1.093805,
|
| 70 |
+
1.036086,
|
| 71 |
+
1.005727,
|
| 72 |
+
1.104078,
|
| 73 |
+
1.042298,
|
| 74 |
+
1.095522,
|
| 75 |
+
0.960255
|
| 76 |
+
],
|
| 77 |
+
"source": "vae/ (Muinez/f8c32-ae)"
|
| 78 |
+
},
|
| 79 |
+
"text": {
|
| 80 |
+
"encoder": "text_encoder/ (jhu-clsp/ettin-encoder-150m, 768-dim, 22 слоя)",
|
| 81 |
+
"max_len": 192,
|
| 82 |
+
"slices": [
|
| 83 |
+
2,
|
| 84 |
+
5,
|
| 85 |
+
8,
|
| 86 |
+
11,
|
| 87 |
+
14,
|
| 88 |
+
17,
|
| 89 |
+
20
|
| 90 |
+
],
|
| 91 |
+
"slice_blocks": 2,
|
| 92 |
+
"trainable": "text_mixer.*"
|
| 93 |
+
},
|
| 94 |
+
"body": {
|
| 95 |
+
"kind": "пирамидальный U: ширина растёт к низу",
|
| 96 |
+
"levels": [
|
| 97 |
+
{
|
| 98 |
+
"level": 0,
|
| 99 |
+
"tokens": "÷1",
|
| 100 |
+
"width": 192,
|
| 101 |
+
"blocks": "conv 6 enc / 4 dec + 1 self-attn на сторону",
|
| 102 |
+
"text": "кросс-внимание в каждом conv-блоке и в self-attn"
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"level": 1,
|
| 106 |
+
"tokens": "÷2",
|
| 107 |
+
"width": 512,
|
| 108 |
+
"blocks": "attn 4 enc / 3 dec",
|
| 109 |
+
"text": "кросс-внимание"
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"level": 2,
|
| 113 |
+
"tokens": "÷4",
|
| 114 |
+
"width": 1024,
|
| 115 |
+
"blocks": "attn 3 enc / 2 dec",
|
| 116 |
+
"text": "кросс-внимание"
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"level": 3,
|
| 120 |
+
"tokens": "÷8",
|
| 121 |
+
"width": 1536,
|
| 122 |
+
"blocks": "attn 2 (боттлнек)",
|
| 123 |
+
"text": "кросс-внимание"
|
| 124 |
+
}
|
| 125 |
+
],
|
| 126 |
+
"skips": "аддитивные (SD-стиль): e0/e1/e2 прибавляются в декодере",
|
| 127 |
+
"down": "mean-pool 2× + Linear",
|
| 128 |
+
"up": "Linear + nearest 2×",
|
| 129 |
+
"rope": "2D RoPE, base 100, фаза в латентных пикселях на каждом уровне",
|
| 130 |
+
"parametrization": "x0 латента"
|
| 131 |
+
},
|
| 132 |
+
"tail": {
|
| 133 |
+
"source": "декодер AE, 6 блоков @512, PixelShuffle ×3",
|
| 134 |
+
"trained_as": "flow по пикселям, условие z0 + (ошибка тела).detach()",
|
| 135 |
+
"sample_steps": 4
|
| 136 |
+
},
|
| 137 |
+
"params": {
|
| 138 |
+
"body": 143.33,
|
| 139 |
+
"text_mixer": 10.04,
|
| 140 |
+
"tail": 23.87,
|
| 141 |
+
"total": 177.24
|
| 142 |
+
},
|
| 143 |
+
"training": {
|
| 144 |
+
"dataset": "LucasFang/FLUX-Reason-6M",
|
| 145 |
+
"parts": "Aesthetics-Part01,Aesthetics-Part02,Imaginative,Text",
|
| 146 |
+
"size": 256,
|
| 147 |
+
"steps": 212000,
|
| 148 |
+
"batch": 16,
|
| 149 |
+
"accum": 1,
|
| 150 |
+
"lr": 0.0001,
|
| 151 |
+
"lr_tail": 3e-05,
|
| 152 |
+
"drop_text": 0.1,
|
| 153 |
+
"init": "tmp/flux2/ckpt/step_0192000.pt",
|
| 154 |
+
"metrics": "FLUX-Reason Part01 on top of the 20k butterfly run; continued on Parts 01+02+Imaginative+Text from step 192000, mse_lat 0.244 (ema 0.245), low 0.026, high 0.57, mse_pix 0.019"
|
| 155 |
+
},
|
| 156 |
+
"storage_dtype": "float16"
|
| 157 |
+
}
|
transformer/diffusion_pytorch_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f288233710ddf86c9d50099d59fa5d1f19438ff9a55b418bcc27ff2a23405658
|
| 3 |
+
size 354543188
|
vae/.gitattributes
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
vae/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright [yyyy] [name of copyright owner]
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
vae/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
# Image Autoencoder (8x, 32 channels, flow-matching decoder)
|
| 6 |
+
|
| 7 |
+
Inference-only package for an image autoencoder intended as a latent space for diffusion models.
|
| 8 |
+
|
| 9 |
+
- **Compression:** 8x spatially, 32 latent channels — an `H x W` RGB image becomes a `32 x H/8 x W/8` latent.
|
| 10 |
+
- **Encoder:** deterministic transformer with local window attention (2D RoPE), works at any resolution
|
| 11 |
+
whose sides are multiples of 8.
|
| 12 |
+
- **Decoder:** conditional flow-matching model (x0-prediction). It starts from Gaussian noise and is
|
| 13 |
+
integrated in `N` steps (default **4**) conditioned on the latent.
|
| 14 |
+
- **Normalization built in:** `encode()` returns latents already normalized per channel
|
| 15 |
+
(`(z - mean) / std`, roughly zero mean / unit variance); `decode()` undoes it. Use
|
| 16 |
+
`encode_raw()` / `decode_raw()` for unnormalized latents.
|
| 17 |
+
|
| 18 |
+
## Install
|
| 19 |
+
|
| 20 |
+
```bash
|
| 21 |
+
pip install torch safetensors huggingface_hub numpy pillow
|
| 22 |
+
# optional: triton (CUDA) for the fused window-attention kernel; otherwise PyTorch SDPA is used
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
The model code lives in the `ae/` folder of this repo:
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
huggingface-cli download Muinez/f8c32-ae --local-dir f8c32-ae
|
| 29 |
+
export PYTHONPATH=$PWD/f8c32-ae:$PYTHONPATH
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
## Usage
|
| 33 |
+
|
| 34 |
+
```python
|
| 35 |
+
import torch
|
| 36 |
+
from ae import AutoEncoder
|
| 37 |
+
|
| 38 |
+
model = AutoEncoder.from_pretrained("Muinez/f8c32-ae", device="cuda") # bf16 compute on CUDA
|
| 39 |
+
|
| 40 |
+
x = ... # (B, 3, H, W) float in [-1, 1], H and W multiples of 8
|
| 41 |
+
z = model.encode(x) # (B, 32, H/8, W/8), normalized latents
|
| 42 |
+
y = model.decode(z, steps=4) # (B, 3, H, W), approximately in [-1, 1]
|
| 43 |
+
y = y.clamp(-1, 1)
|
| 44 |
+
|
| 45 |
+
# reproducible decoding
|
| 46 |
+
g = torch.Generator("cuda").manual_seed(0)
|
| 47 |
+
y = model.decode(z, steps=4, generator=g)
|
| 48 |
+
|
| 49 |
+
# raw (unnormalized) latents
|
| 50 |
+
z_raw = model.encode_raw(x)
|
| 51 |
+
y = model.decode_raw(z_raw, steps=4)
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
### Batch of different resolutions / aspect ratios
|
| 55 |
+
|
| 56 |
+
`encode` / `decode` (and the `_raw` variants) also take a **list** of tensors of different
|
| 57 |
+
sizes and return a list. The whole list goes through the model in one forward pass: each
|
| 58 |
+
image keeps its own size (no resizing or cropping), is cut into its own grid of 8x8 patch
|
| 59 |
+
tokens, and the grids are packed into one sequence batch padded only up to the largest image
|
| 60 |
+
area. Attention never crosses into other images or padding, so results match encoding each
|
| 61 |
+
image on its own. Only the decoder's small convolutional upsampling head runs per group of
|
| 62 |
+
equal-shape images.
|
| 63 |
+
|
| 64 |
+
```python
|
| 65 |
+
from PIL import Image
|
| 66 |
+
import numpy as np
|
| 67 |
+
|
| 68 |
+
def load(path, max_side=1024):
|
| 69 |
+
im = Image.open(path).convert("RGB")
|
| 70 |
+
s = min(1.0, max_side / max(im.size))
|
| 71 |
+
w, h = (int(im.width * s) // 8 * 8, int(im.height * s) // 8 * 8) # sides: multiples of 8
|
| 72 |
+
im = im.resize((w, h), Image.LANCZOS)
|
| 73 |
+
return torch.from_numpy(np.asarray(im)).permute(2, 0, 1) # uint8 (3, H, W)
|
| 74 |
+
|
| 75 |
+
images = [load("portrait.jpg"), load("landscape.png"), load("square.webp")] # any sizes
|
| 76 |
+
latents = model.encode(images) # list of (32, H_i/8, W_i/8), normalized
|
| 77 |
+
recons = model.decode(latents, steps=4) # list of (3, H_i, W_i)
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
Images may be uint8 `[0, 255]` or float `[-1, 1]`.
|
| 81 |
+
|
| 82 |
+
See `example.py` for a complete round trip (`python example.py input.png recon.png`).
|
| 83 |
+
|
| 84 |
+
### Notes
|
| 85 |
+
|
| 86 |
+
- `steps` trades speed for detail: 1 step is fastest and slightly softer, 4 is the default.
|
| 87 |
+
Because the decoder samples from noise, different seeds give slightly different fine detail.
|
| 88 |
+
- `dtype` in `from_pretrained` sets the compute dtype (default: bfloat16 on CUDA, float32 on CPU).
|
| 89 |
+
Encoder weights are cast to it; the decoder keeps float32 weights and runs under autocast.
|
| 90 |
+
- On CUDA with Triton installed, a fused window-attention kernel is used for any image sizes;
|
| 91 |
+
otherwise (CPU, no Triton) an equivalent masked SDPA path is used. Force SDPA with
|
| 92 |
+
`import ae.window_attention as wa; wa.BACKEND = "sdpa"`.
|
| 93 |
+
|
| 94 |
+
## Files
|
| 95 |
+
|
| 96 |
+
- `config.json` — architecture and latent normalization statistics
|
| 97 |
+
- `model.safetensors` — encoder and decoder weights (float32)
|
| 98 |
+
- `ae/` — model code (`AutoEncoder`, `AEConfig`)
|
| 99 |
+
- `example.py` — round-trip example
|
| 100 |
+
- `LICENSE` — Apache License 2.0
|
| 101 |
+
|
| 102 |
+
## License
|
| 103 |
+
|
| 104 |
+
Apache License 2.0 — see `LICENSE`.
|
vae/ae/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .model import AEConfig, AutoEncoder, Encoder, FlowDecoder, Packing
|
| 2 |
+
|
| 3 |
+
__all__ = ["AEConfig", "AutoEncoder", "Encoder", "FlowDecoder", "Packing"]
|
vae/ae/model.py
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Image autoencoder: deterministic transformer encoder + flow-matching decoder.
|
| 2 |
+
|
| 3 |
+
* Encoder: 8x8 patches -> windowed-attention transformer blocks -> 32-channel latent
|
| 4 |
+
on a grid 8x smaller than the image.
|
| 5 |
+
* Decoder: a conditional flow-matching model. Given the latent, a noisy image ``x_t`` and
|
| 6 |
+
the time ``t``, it predicts the clean image ``x_0``. Images are produced by integrating
|
| 7 |
+
from pure noise (t=1) to the image (t=0) in a small number of steps (default 4).
|
| 8 |
+
|
| 9 |
+
Everything except the decoder's upsampling head works on token sequences, so a batch may
|
| 10 |
+
mix images of any sizes and aspect ratios: each image is cut into its own grid of patch
|
| 11 |
+
tokens, the grids are packed into one padded sequence batch (:class:`Packing`), and
|
| 12 |
+
attention is restricted to each image's own grid. The convolutional upsampling head runs
|
| 13 |
+
per group of equal-shape images.
|
| 14 |
+
|
| 15 |
+
All architecture hyper-parameters come from :class:`AEConfig`.
|
| 16 |
+
"""
|
| 17 |
+
import json
|
| 18 |
+
import math
|
| 19 |
+
import os
|
| 20 |
+
from dataclasses import asdict, dataclass
|
| 21 |
+
from typing import List, Optional, Sequence, Tuple
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
import torch.nn as nn
|
| 25 |
+
import torch.nn.functional as F
|
| 26 |
+
|
| 27 |
+
from .window_attention import _tile_width, packed_window_attention
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@dataclass
|
| 31 |
+
class AEConfig:
|
| 32 |
+
in_channels: int = 3
|
| 33 |
+
patch_size: int = 8 # spatial compression factor
|
| 34 |
+
latent_channels: int = 32
|
| 35 |
+
# encoder
|
| 36 |
+
enc_width: int = 1024
|
| 37 |
+
enc_depth: int = 8
|
| 38 |
+
enc_mlp_ratio: float = 2.0
|
| 39 |
+
# decoder
|
| 40 |
+
dec_width: int = 512
|
| 41 |
+
dec_depth: int = 6
|
| 42 |
+
dec_mlp_ratio: float = 4.0
|
| 43 |
+
dec_adaln_rank: int = 128 # rank of the per-block low-rank modulation deltas
|
| 44 |
+
dec_skip_blocks: int = 2 # long skip: output of block (n-1) feeds the last n blocks
|
| 45 |
+
dec_head_min_channels: int = 64
|
| 46 |
+
time_embed_dim: int = 256
|
| 47 |
+
# shared
|
| 48 |
+
head_dim: int = 64
|
| 49 |
+
window_radius: int = 8 # attention window: |dy| <= r and |dx| <= r tokens
|
| 50 |
+
rope_base: float = 100.0
|
| 51 |
+
# per-channel statistics of raw latents (used by encode()/decode())
|
| 52 |
+
latent_mean: Optional[List[float]] = None
|
| 53 |
+
latent_std: Optional[List[float]] = None
|
| 54 |
+
|
| 55 |
+
@classmethod
|
| 56 |
+
def from_json(cls, path):
|
| 57 |
+
with open(path) as f:
|
| 58 |
+
d = json.load(f)
|
| 59 |
+
known = {k: v for k, v in d.items() if k in cls.__dataclass_fields__}
|
| 60 |
+
return cls(**known)
|
| 61 |
+
|
| 62 |
+
def to_json(self, path):
|
| 63 |
+
with open(path, "w") as f:
|
| 64 |
+
json.dump(asdict(self), f, indent=2)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# --------------------------------------------------------------------------- packing
|
| 68 |
+
|
| 69 |
+
class Packing:
|
| 70 |
+
"""Layout of a batch of token grids of sizes ``hw = [(h, w), ...]``.
|
| 71 |
+
|
| 72 |
+
Sample ``b`` is stored row-major in its own padded ``gyp x gxp`` grid at the start of a
|
| 73 |
+
sequence of length ``N`` (the largest padded area); its real tokens are the top-left
|
| 74 |
+
``h x w`` corner. ``pos[b]`` are the sequence indices of its real tokens.
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
def __init__(self, hw: Sequence[Tuple[int, int]], radius: int, rope_base: float,
|
| 78 |
+
head_dim: int, device):
|
| 79 |
+
self.hw = [(int(h), int(w)) for h, w in hw]
|
| 80 |
+
WY, WX = 8, _tile_width(radius)
|
| 81 |
+
B = len(self.hw)
|
| 82 |
+
geo = [(math.ceil(h / WY) * WY, math.ceil(w / WX) * WX) for h, w in self.hw]
|
| 83 |
+
self.N = max(a * b for a, b in geo)
|
| 84 |
+
self.max_tiles = max((a // WY) * (b // WX) for a, b in geo)
|
| 85 |
+
self.dims = torch.tensor([[a, b, h, w] for (a, b), (h, w) in zip(geo, self.hw)],
|
| 86 |
+
dtype=torch.int32, device=device)
|
| 87 |
+
self.valid = torch.zeros(B, self.N, 1, device=device)
|
| 88 |
+
pos_y = torch.zeros(B, self.N, device=device)
|
| 89 |
+
pos_x = torch.zeros(B, self.N, device=device)
|
| 90 |
+
self.pos = []
|
| 91 |
+
for b, ((_, gxp), (h, w)) in enumerate(zip(geo, self.hw)):
|
| 92 |
+
yy = torch.arange(h, device=device).repeat_interleave(w)
|
| 93 |
+
xx = torch.arange(w, device=device).repeat(h)
|
| 94 |
+
p = yy * gxp + xx
|
| 95 |
+
self.pos.append(p)
|
| 96 |
+
self.valid[b, p] = 1
|
| 97 |
+
pos_y[b, p], pos_x[b, p] = yy.float(), xx.float()
|
| 98 |
+
# 2D axial RoPE tables: the head dim is split into four quarters,
|
| 99 |
+
# two rotate with the x coordinate, two with the y coordinate.
|
| 100 |
+
dq = head_dim // 4
|
| 101 |
+
fr = torch.exp(-math.log(rope_base) * torch.arange(dq, device=device, dtype=torch.float32) / dq)
|
| 102 |
+
ax, ay = pos_x[..., None] * fr, pos_y[..., None] * fr # (B, N, dq)
|
| 103 |
+
self._rope = [t[:, None] for t in (ax.cos(), ax.sin(), ay.cos(), ay.sin())]
|
| 104 |
+
self._dq = dq
|
| 105 |
+
self.radius = radius
|
| 106 |
+
|
| 107 |
+
def pack(self, seqs):
|
| 108 |
+
"""list of (n_b, C) -> (B, N, C), zero padded."""
|
| 109 |
+
out = seqs[0].new_zeros(len(seqs), self.N, seqs[0].shape[-1])
|
| 110 |
+
for b, s in enumerate(seqs):
|
| 111 |
+
out[b, self.pos[b]] = s
|
| 112 |
+
return out
|
| 113 |
+
|
| 114 |
+
def unpack(self, x):
|
| 115 |
+
"""(B, N, C) -> list of (C, h, w)."""
|
| 116 |
+
return [x[b, self.pos[b]].transpose(0, 1).reshape(-1, h, w) for b, (h, w) in enumerate(self.hw)]
|
| 117 |
+
|
| 118 |
+
def rope(self, q, k):
|
| 119 |
+
dq = self._dq
|
| 120 |
+
cx, sx, cy, sy = (c.to(q.dtype) for c in self._rope)
|
| 121 |
+
|
| 122 |
+
def rot(t):
|
| 123 |
+
a, b, c, e = t[..., :dq], t[..., dq:2 * dq], t[..., 2 * dq:3 * dq], t[..., 3 * dq:]
|
| 124 |
+
return torch.cat([a * cx - b * sx, a * sx + b * cx, c * cy - e * sy, c * sy + e * cy], dim=-1)
|
| 125 |
+
|
| 126 |
+
return rot(q), rot(k)
|
| 127 |
+
|
| 128 |
+
def attention(self, q, k, v):
|
| 129 |
+
return packed_window_attention(q, k, v, self.radius, self.dims, self.max_tiles)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def patchify(img, p):
|
| 133 |
+
"""(C, H, W) -> (H/p * W/p, C*p*p) row-major patch tokens."""
|
| 134 |
+
C, H, W = img.shape
|
| 135 |
+
return img.reshape(C, H // p, p, W // p, p).permute(1, 3, 0, 2, 4).reshape(-1, C * p * p)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
# --------------------------------------------------------------------------- encoder
|
| 139 |
+
|
| 140 |
+
class RMSNorm(nn.Module):
|
| 141 |
+
def __init__(self, ch, eps=1e-8):
|
| 142 |
+
super().__init__()
|
| 143 |
+
self.eps = eps
|
| 144 |
+
self.weight = nn.Parameter(torch.ones(ch))
|
| 145 |
+
|
| 146 |
+
def forward(self, x):
|
| 147 |
+
return x * x.pow(2).mean(-1, keepdim=True).add(self.eps).rsqrt() * self.weight
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
class Mlp(nn.Module):
|
| 151 |
+
def __init__(self, i, h, o):
|
| 152 |
+
super().__init__()
|
| 153 |
+
self.m = nn.Sequential(nn.Linear(i, h), nn.GELU(), nn.Linear(h, o))
|
| 154 |
+
|
| 155 |
+
def forward(self, x):
|
| 156 |
+
return self.m(x)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
class PlainMlp(nn.Module):
|
| 160 |
+
def __init__(self, d, ratio):
|
| 161 |
+
super().__init__()
|
| 162 |
+
h = int(d * ratio)
|
| 163 |
+
self.up = nn.Linear(d, h)
|
| 164 |
+
self.down = nn.Linear(h, d)
|
| 165 |
+
self.act = nn.GELU(approximate="tanh")
|
| 166 |
+
|
| 167 |
+
def forward(self, x):
|
| 168 |
+
return self.down(self.act(self.up(x)))
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
class EncoderBlock(nn.Module):
|
| 172 |
+
"""Pre-norm transformer block with 2D-RoPE windowed self-attention and an MLP."""
|
| 173 |
+
|
| 174 |
+
def __init__(self, ch, head_dim, mlp_ratio):
|
| 175 |
+
super().__init__()
|
| 176 |
+
self.h = max(1, ch // head_dim)
|
| 177 |
+
self.norm1 = RMSNorm(ch)
|
| 178 |
+
self.qkv = nn.Linear(ch, ch * 3, bias=False)
|
| 179 |
+
self.proj = nn.Linear(ch, ch, bias=False)
|
| 180 |
+
self.norm2 = RMSNorm(ch)
|
| 181 |
+
self.mlp = PlainMlp(ch, mlp_ratio)
|
| 182 |
+
|
| 183 |
+
def forward(self, x, pk: Packing):
|
| 184 |
+
B, N, C = x.shape
|
| 185 |
+
q, k, v = self.qkv(self.norm1(x)).reshape(B, N, 3, self.h, C // self.h).permute(2, 0, 3, 1, 4)
|
| 186 |
+
q, k = pk.rope(q, k)
|
| 187 |
+
a = pk.attention(q, k, v).transpose(1, 2).reshape(B, N, C).to(x.dtype)
|
| 188 |
+
x = x + self.proj(a)
|
| 189 |
+
return x + self.mlp(self.norm2(x))
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
class Encoder(nn.Module):
|
| 193 |
+
"""patch tokens (B, N, 3*p*p) -> raw latent tokens (B, N, latent_channels)."""
|
| 194 |
+
|
| 195 |
+
def __init__(self, cfg: AEConfig):
|
| 196 |
+
super().__init__()
|
| 197 |
+
w = cfg.enc_width
|
| 198 |
+
self.initial = nn.Linear(cfg.in_channels * cfg.patch_size ** 2, w)
|
| 199 |
+
self.blocks = nn.ModuleList(EncoderBlock(w, cfg.head_dim, cfg.enc_mlp_ratio)
|
| 200 |
+
for _ in range(cfg.enc_depth))
|
| 201 |
+
self.out = Mlp(w, w, cfg.latent_channels)
|
| 202 |
+
|
| 203 |
+
def forward(self, x, pk: Packing):
|
| 204 |
+
valid = pk.valid.to(x.dtype)
|
| 205 |
+
x = self.initial(x)
|
| 206 |
+
for b in self.blocks:
|
| 207 |
+
x = b(x, pk) * valid
|
| 208 |
+
return self.out(x)
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
# --------------------------------------------------------------------------- decoder
|
| 212 |
+
|
| 213 |
+
class DecoderBlock(nn.Module):
|
| 214 |
+
"""Transformer block with windowed 2D-RoPE attention, modulated by the timestep:
|
| 215 |
+
the modulation vector carries (scale, gate) for the attention and the MLP sub-layers."""
|
| 216 |
+
|
| 217 |
+
def __init__(self, ch, head_dim, mlp_ratio):
|
| 218 |
+
super().__init__()
|
| 219 |
+
self.h = max(1, ch // head_dim)
|
| 220 |
+
self.norm1 = nn.RMSNorm(ch)
|
| 221 |
+
self.qkv = nn.Linear(ch, 3 * ch)
|
| 222 |
+
self.proj = nn.Linear(ch, ch)
|
| 223 |
+
self.norm2 = nn.RMSNorm(ch)
|
| 224 |
+
hid = int(ch * mlp_ratio)
|
| 225 |
+
self.fc1 = nn.Linear(ch, hid)
|
| 226 |
+
self.fc2 = nn.Linear(hid, ch)
|
| 227 |
+
|
| 228 |
+
def forward(self, x, mod, pk: Packing):
|
| 229 |
+
B, N, C = x.shape
|
| 230 |
+
s1, g1, s2, g2 = mod[:, None, :].chunk(4, dim=-1)
|
| 231 |
+
h = self.norm1(x) * (1 + s1)
|
| 232 |
+
q, k, v = self.qkv(h).reshape(B, N, 3, self.h, C // self.h).permute(2, 0, 3, 1, 4)
|
| 233 |
+
q, k = pk.rope(q, k)
|
| 234 |
+
a = pk.attention(q, k, v).transpose(1, 2).reshape(B, N, C).to(h.dtype)
|
| 235 |
+
x = x + g1 * self.proj(a)
|
| 236 |
+
h = self.norm2(x) * (1 + s2)
|
| 237 |
+
return x + g2 * self.fc2(F.gelu(self.fc1(h)))
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
class FlowDecoder(nn.Module):
|
| 241 |
+
"""x0-prediction network: (x_t, t, latent) -> predicted clean image.
|
| 242 |
+
|
| 243 |
+
The transformer works on packed tokens; the upsampling head (PixelShuffle + 3x3 convs)
|
| 244 |
+
runs per group of equal-shape samples.
|
| 245 |
+
"""
|
| 246 |
+
|
| 247 |
+
def __init__(self, cfg: AEConfig):
|
| 248 |
+
super().__init__()
|
| 249 |
+
D, p = cfg.dec_width, cfg.patch_size
|
| 250 |
+
self.depth = cfg.dec_depth
|
| 251 |
+
self.n_skip = cfg.dec_skip_blocks
|
| 252 |
+
self.time_embed_dim = cfg.time_embed_dim
|
| 253 |
+
self.patchify = nn.Linear(cfg.in_channels * p * p, D)
|
| 254 |
+
self.latent_up = nn.Linear(cfg.latent_channels, D)
|
| 255 |
+
self.fuse_in = nn.Linear(2 * D, D)
|
| 256 |
+
self.tmlp = nn.Sequential(nn.Linear(cfg.time_embed_dim, D), nn.SiLU(), nn.Linear(D, D))
|
| 257 |
+
self.adaln_act = nn.SiLU()
|
| 258 |
+
mw = 4 * D
|
| 259 |
+
self.adaln_base = nn.Linear(D, mw)
|
| 260 |
+
self.d_down = nn.ModuleList(nn.Linear(D, cfg.dec_adaln_rank, bias=False) for _ in range(self.depth))
|
| 261 |
+
self.d_up = nn.ModuleList(nn.Linear(cfg.dec_adaln_rank, mw, bias=False) for _ in range(self.depth))
|
| 262 |
+
self.blocks = nn.ModuleList(DecoderBlock(D, cfg.head_dim, cfg.dec_mlp_ratio)
|
| 263 |
+
for _ in range(self.depth))
|
| 264 |
+
self.fuse_skip = nn.Linear(2 * D, D)
|
| 265 |
+
# progressive upsampling head: [PixelShuffle(2) -> conv3x3 -> GELU] x log2(p)
|
| 266 |
+
n = int(round(math.log2(p)))
|
| 267 |
+
ch, stages = D, []
|
| 268 |
+
for _ in range(n):
|
| 269 |
+
nxt = max(ch // 2, cfg.dec_head_min_channels)
|
| 270 |
+
stages.append(nn.Sequential(nn.PixelShuffle(2), nn.Conv2d(ch // 4, nxt, 3, padding=1), nn.GELU()))
|
| 271 |
+
ch = nxt
|
| 272 |
+
self.up_stages = nn.ModuleList(stages)
|
| 273 |
+
self.up_final = nn.Conv2d(ch, cfg.in_channels, 3, padding=1)
|
| 274 |
+
|
| 275 |
+
def time_embed(self, t):
|
| 276 |
+
half = self.time_embed_dim // 2
|
| 277 |
+
freqs = torch.exp(-math.log(10000.0) * torch.arange(half, device=t.device, dtype=torch.float32) / half)
|
| 278 |
+
a = t.float()[:, None] * 1000.0 * freqs[None]
|
| 279 |
+
return self.tmlp(torch.cat([a.sin(), a.cos()], dim=-1).to(self.tmlp[0].weight.dtype))
|
| 280 |
+
|
| 281 |
+
def head(self, feats):
|
| 282 |
+
"""feats: (B, D, h, w) -> (B, 3, h*p, w*p)."""
|
| 283 |
+
for st in self.up_stages:
|
| 284 |
+
feats = st(feats)
|
| 285 |
+
return self.up_final(feats)
|
| 286 |
+
|
| 287 |
+
def forward(self, xt, t, z, pk: Packing):
|
| 288 |
+
"""xt: packed noisy patch tokens (B, N, 3*p*p); t: (B,); z: packed latents (B, N, C).
|
| 289 |
+
Returns a list of per-sample x0 predictions (3, H_b, W_b)."""
|
| 290 |
+
valid = pk.valid
|
| 291 |
+
cond = self.adaln_act(self.time_embed(t))
|
| 292 |
+
base = self.adaln_base(cond)
|
| 293 |
+
x = self.fuse_in(torch.cat([self.patchify(xt), self.latent_up(z)], dim=-1))
|
| 294 |
+
valid = valid.to(x.dtype)
|
| 295 |
+
x = x * valid
|
| 296 |
+
outs = []
|
| 297 |
+
for i, b in enumerate(self.blocks):
|
| 298 |
+
if i == self.depth - self.n_skip:
|
| 299 |
+
x = self.fuse_skip(torch.cat([outs[self.n_skip - 1], x], dim=-1))
|
| 300 |
+
x = b(x, base + self.d_up[i](self.d_down[i](cond)), pk) * valid
|
| 301 |
+
outs.append(x)
|
| 302 |
+
feats = pk.unpack(x)
|
| 303 |
+
preds = [None] * len(feats)
|
| 304 |
+
groups = {}
|
| 305 |
+
for i, f in enumerate(feats):
|
| 306 |
+
groups.setdefault(tuple(f.shape), []).append(i)
|
| 307 |
+
for idx in groups.values():
|
| 308 |
+
y = self.head(torch.stack([feats[i] for i in idx]))
|
| 309 |
+
for j, i in enumerate(idx):
|
| 310 |
+
preds[i] = y[j]
|
| 311 |
+
return preds
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
# --------------------------------------------------------------------------- wrapper
|
| 315 |
+
|
| 316 |
+
def _as_list(x):
|
| 317 |
+
"""Tensor (B, C, H, W) or list of (C, H, W) -> (list, was_tensor)."""
|
| 318 |
+
if torch.is_tensor(x):
|
| 319 |
+
assert x.dim() == 4, f"expected (B, C, H, W), got {tuple(x.shape)}"
|
| 320 |
+
return list(x.unbind(0)), True
|
| 321 |
+
return list(x), False
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
class AutoEncoder(nn.Module):
|
| 325 |
+
"""Encoder + flow-matching decoder with built-in latent normalization.
|
| 326 |
+
|
| 327 |
+
``encode`` returns latents normalized per channel ((z - mean) / std), ready to be used
|
| 328 |
+
as a diffusion target; ``decode`` expects such latents. ``encode_raw`` / ``decode_raw``
|
| 329 |
+
skip the normalization.
|
| 330 |
+
|
| 331 |
+
All four methods take either a batch tensor ``(B, C, H, W)`` (returns a tensor) or a
|
| 332 |
+
list of ``(C, H_i, W_i)`` tensors of different sizes (returns a list), processed in one
|
| 333 |
+
forward pass.
|
| 334 |
+
"""
|
| 335 |
+
|
| 336 |
+
def __init__(self, cfg: AEConfig):
|
| 337 |
+
super().__init__()
|
| 338 |
+
self.config = cfg
|
| 339 |
+
self.encoder = Encoder(cfg)
|
| 340 |
+
self.decoder = FlowDecoder(cfg)
|
| 341 |
+
C = cfg.latent_channels
|
| 342 |
+
mean = torch.tensor(cfg.latent_mean, dtype=torch.float32) if cfg.latent_mean else torch.zeros(C)
|
| 343 |
+
std = torch.tensor(cfg.latent_std, dtype=torch.float32) if cfg.latent_std else torch.ones(C)
|
| 344 |
+
self.register_buffer("latent_mean", mean.view(C, 1, 1), persistent=False)
|
| 345 |
+
self.register_buffer("latent_std", std.view(C, 1, 1), persistent=False)
|
| 346 |
+
# compute dtype: encoder weights are stored in it; the decoder keeps fp32 weights and
|
| 347 |
+
# runs under autocast with this dtype (when it is not float32).
|
| 348 |
+
self.compute_dtype = torch.float32
|
| 349 |
+
|
| 350 |
+
# ---------------------------------------------------------------- loading
|
| 351 |
+
@classmethod
|
| 352 |
+
def from_pretrained(cls, path_or_repo, device=None, dtype=None, **hub_kwargs):
|
| 353 |
+
"""Load from a local directory or a Hugging Face repo id containing
|
| 354 |
+
``config.json`` and ``model.safetensors``.
|
| 355 |
+
|
| 356 |
+
dtype: compute dtype (default: bfloat16 on CUDA, float32 on CPU).
|
| 357 |
+
"""
|
| 358 |
+
from safetensors.torch import load_file
|
| 359 |
+
|
| 360 |
+
if os.path.isdir(path_or_repo):
|
| 361 |
+
cfg_path = os.path.join(path_or_repo, "config.json")
|
| 362 |
+
w_path = os.path.join(path_or_repo, "model.safetensors")
|
| 363 |
+
else:
|
| 364 |
+
from huggingface_hub import hf_hub_download
|
| 365 |
+
cfg_path = hf_hub_download(path_or_repo, "config.json", **hub_kwargs)
|
| 366 |
+
w_path = hf_hub_download(path_or_repo, "model.safetensors", **hub_kwargs)
|
| 367 |
+
device = torch.device(device or ("cuda" if torch.cuda.is_available() else "cpu"))
|
| 368 |
+
if dtype is None:
|
| 369 |
+
dtype = torch.bfloat16 if device.type == "cuda" else torch.float32
|
| 370 |
+
model = cls(AEConfig.from_json(cfg_path))
|
| 371 |
+
model.load_state_dict(load_file(w_path), strict=True)
|
| 372 |
+
model.to(device).eval().requires_grad_(False)
|
| 373 |
+
model.set_dtype(dtype)
|
| 374 |
+
return model
|
| 375 |
+
|
| 376 |
+
def set_dtype(self, dtype):
|
| 377 |
+
self.compute_dtype = dtype
|
| 378 |
+
self.encoder.to(dtype)
|
| 379 |
+
self.decoder.float()
|
| 380 |
+
return self
|
| 381 |
+
|
| 382 |
+
@property
|
| 383 |
+
def device(self):
|
| 384 |
+
return self.latent_mean.device
|
| 385 |
+
|
| 386 |
+
def _autocast(self):
|
| 387 |
+
return torch.autocast(self.device.type, dtype=self.compute_dtype,
|
| 388 |
+
enabled=self.compute_dtype != torch.float32)
|
| 389 |
+
|
| 390 |
+
def _packing(self, hw):
|
| 391 |
+
c = self.config
|
| 392 |
+
return Packing(hw, c.window_radius, c.rope_base, c.head_dim, self.device)
|
| 393 |
+
|
| 394 |
+
# ---------------------------------------------------------------- encoder side
|
| 395 |
+
@torch.no_grad()
|
| 396 |
+
def encode_raw(self, images):
|
| 397 |
+
"""images: (B, 3, H, W) or a list of (3, H_i, W_i); float in [-1, 1] or uint8 in
|
| 398 |
+
[0, 255]; sides multiples of the patch size.
|
| 399 |
+
Returns raw (unnormalized) float32 latents: (B, C, H/p, W/p) or a list of (C, H_i/p, W_i/p)."""
|
| 400 |
+
p = self.config.patch_size
|
| 401 |
+
imgs, was_tensor = _as_list(images)
|
| 402 |
+
for im in imgs:
|
| 403 |
+
assert im.dim() == 3 and im.shape[-2] % p == 0 and im.shape[-1] % p == 0, \
|
| 404 |
+
f"each image must be (3, H, W) with H, W multiples of {p}, got {tuple(im.shape)}"
|
| 405 |
+
pk = self._packing([(im.shape[-2] // p, im.shape[-1] // p) for im in imgs])
|
| 406 |
+
toks = []
|
| 407 |
+
for im in imgs:
|
| 408 |
+
im = im.to(self.device)
|
| 409 |
+
im = im.float() / 127.5 - 1.0 if im.dtype == torch.uint8 else im
|
| 410 |
+
toks.append(patchify(im.to(self.compute_dtype), p))
|
| 411 |
+
z = [t.float() for t in pk.unpack(self.encoder(pk.pack(toks), pk))]
|
| 412 |
+
return torch.stack(z) if was_tensor else z
|
| 413 |
+
|
| 414 |
+
@torch.no_grad()
|
| 415 |
+
def encode(self, images):
|
| 416 |
+
"""Images -> normalized latents (zero mean / unit std per channel)."""
|
| 417 |
+
z = self.encode_raw(images)
|
| 418 |
+
if torch.is_tensor(z):
|
| 419 |
+
return (z - self.latent_mean) / self.latent_std
|
| 420 |
+
return [(t - self.latent_mean) / self.latent_std for t in z]
|
| 421 |
+
|
| 422 |
+
# ---------------------------------------------------------------- decoder side
|
| 423 |
+
@torch.no_grad()
|
| 424 |
+
def decode_raw(self, latents, steps=4, generator=None, noise=None):
|
| 425 |
+
"""Raw latents -> images (approximately in [-1, 1], not clamped).
|
| 426 |
+
|
| 427 |
+
latents: (B, C, h, w) or a list of (C, h_i, w_i).
|
| 428 |
+
steps: number of flow-matching integration steps (default 4; 1 gives a fast,
|
| 429 |
+
softer result, more steps add a bit of detail).
|
| 430 |
+
generator: optional torch.Generator for the initial noise (on the model device).
|
| 431 |
+
noise: optional explicit initial noise, (B, 3, H, W) or a list of (3, H_i, W_i).
|
| 432 |
+
"""
|
| 433 |
+
p, C = self.config.patch_size, self.config.in_channels
|
| 434 |
+
zs, was_tensor = _as_list(latents)
|
| 435 |
+
zs = [z.to(self.device, torch.float32) for z in zs]
|
| 436 |
+
pk = self._packing([tuple(z.shape[-2:]) for z in zs])
|
| 437 |
+
zp = pk.pack([z.flatten(1).transpose(0, 1) for z in zs])
|
| 438 |
+
if noise is not None:
|
| 439 |
+
xs = [n.to(self.device, torch.float32) for n in _as_list(noise)[0]]
|
| 440 |
+
elif was_tensor:
|
| 441 |
+
B, _, h, w = latents.shape
|
| 442 |
+
xs = list(torch.randn((B, C, h * p, w * p), generator=generator, device=self.device).unbind(0))
|
| 443 |
+
else:
|
| 444 |
+
xs = [torch.randn((C, z.shape[1] * p, z.shape[2] * p), generator=generator, device=self.device)
|
| 445 |
+
for z in zs]
|
| 446 |
+
ts = torch.linspace(1.0, 0.0, steps + 1, device=self.device)
|
| 447 |
+
B = len(zs)
|
| 448 |
+
for i in range(steps):
|
| 449 |
+
with self._autocast():
|
| 450 |
+
xt = pk.pack([patchify(x.to(self.compute_dtype), p) for x in xs])
|
| 451 |
+
preds = self.decoder(xt, ts[i].expand(B), zp, pk)
|
| 452 |
+
# x0 prediction -> implied noise -> move along the straight path to ts[i+1]
|
| 453 |
+
xs = [ts[i + 1] * (x - (1 - ts[i]) * pr.float()) / ts[i] + (1 - ts[i + 1]) * pr.float()
|
| 454 |
+
for x, pr in zip(xs, preds)]
|
| 455 |
+
return torch.stack(xs) if was_tensor else xs
|
| 456 |
+
|
| 457 |
+
@torch.no_grad()
|
| 458 |
+
def decode(self, latents, steps=4, generator=None, noise=None):
|
| 459 |
+
"""Normalized latents (as returned by ``encode``) -> images in about [-1, 1]."""
|
| 460 |
+
if torch.is_tensor(latents):
|
| 461 |
+
z = latents.to(self.device, torch.float32) * self.latent_std + self.latent_mean
|
| 462 |
+
else:
|
| 463 |
+
z = [t.to(self.device, torch.float32) * self.latent_std + self.latent_mean for t in latents]
|
| 464 |
+
return self.decode_raw(z, steps=steps, generator=generator, noise=noise)
|
| 465 |
+
|
| 466 |
+
@torch.no_grad()
|
| 467 |
+
def forward(self, images, steps=4, generator=None):
|
| 468 |
+
"""Round trip: images -> latents -> images."""
|
| 469 |
+
return self.decode(self.encode(images), steps=steps, generator=generator)
|
vae/ae/window_attention.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Local 2D window attention.
|
| 2 |
+
|
| 3 |
+
Every token attends to all tokens within a square window of Chebyshev radius ``r``
|
| 4 |
+
(i.e. ``|dy| <= r and |dx| <= r``) on its 2D grid.
|
| 5 |
+
|
| 6 |
+
Two numerically equivalent implementations are provided:
|
| 7 |
+
|
| 8 |
+
* a fused Triton kernel (CUDA only, forward pass only);
|
| 9 |
+
* PyTorch ``scaled_dot_product_attention`` with a boolean window mask (CPU, or when
|
| 10 |
+
Triton is unavailable).
|
| 11 |
+
|
| 12 |
+
Both work on a packed batch: every sample is its own token grid of any size.
|
| 13 |
+
"""
|
| 14 |
+
import torch
|
| 15 |
+
import torch.nn.functional as F
|
| 16 |
+
|
| 17 |
+
try: # Triton is optional; without it the SDPA path is used.
|
| 18 |
+
import triton
|
| 19 |
+
import triton.language as tl
|
| 20 |
+
|
| 21 |
+
_HAS_TRITON = True
|
| 22 |
+
except Exception: # pragma: no cover
|
| 23 |
+
_HAS_TRITON = False
|
| 24 |
+
|
| 25 |
+
_LOG2E = 1.4426950408889634
|
| 26 |
+
|
| 27 |
+
# Global switch: "auto" (kernel when possible), "sdpa" (always masked SDPA).
|
| 28 |
+
BACKEND = "auto"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
if _HAS_TRITON:
|
| 32 |
+
|
| 33 |
+
@triton.jit
|
| 34 |
+
def _window_attn_fwd(Q, K, V, O, DIMS, NH: tl.constexpr, NMAX, WY: tl.constexpr,
|
| 35 |
+
WX: tl.constexpr, R: tl.constexpr, D: tl.constexpr,
|
| 36 |
+
BR: tl.constexpr, qk_scale):
|
| 37 |
+
# One program = one WY x WX tile of queries for one (batch, head).
|
| 38 |
+
# Keys are streamed from the tile extended by R on every side ("halo").
|
| 39 |
+
t, bh = tl.program_id(0), tl.program_id(1)
|
| 40 |
+
b = bh // NH
|
| 41 |
+
gyp = tl.load(DIMS + b * 4 + 0)
|
| 42 |
+
gxp = tl.load(DIMS + b * 4 + 1)
|
| 43 |
+
gyr = tl.load(DIMS + b * 4 + 2)
|
| 44 |
+
gxr = tl.load(DIMS + b * 4 + 3)
|
| 45 |
+
ntx = gxp // WX
|
| 46 |
+
if t >= (gyp // WY) * ntx:
|
| 47 |
+
return
|
| 48 |
+
ty, tx = t // ntx, t % ntx
|
| 49 |
+
base = bh * NMAX * D
|
| 50 |
+
SX: tl.constexpr = WX + 2 * R
|
| 51 |
+
SY: tl.constexpr = WY + 2 * R
|
| 52 |
+
|
| 53 |
+
qi = tl.arange(0, WY * WX)
|
| 54 |
+
qyr, qxr = qi // WX, qi % WX
|
| 55 |
+
qy, qx = ty * WY + qyr, tx * WX + qxr
|
| 56 |
+
d = tl.arange(0, D)
|
| 57 |
+
qo = base + (qy * gxp + qx)[:, None] * D + d[None, :]
|
| 58 |
+
q = tl.load(Q + qo)
|
| 59 |
+
okq = (qy < gyr) & (qx < gxr)
|
| 60 |
+
|
| 61 |
+
kxr = tl.arange(0, BR * SX) % SX
|
| 62 |
+
okx = okq[:, None] & (tl.abs(qxr[:, None] - (kxr[None, :] - R)) <= R)
|
| 63 |
+
kxg = tx * WX - R + kxr
|
| 64 |
+
okg = (kxg >= 0) & (kxg < gxr)
|
| 65 |
+
|
| 66 |
+
m = tl.full((WY * WX,), -1e30, tl.float32)
|
| 67 |
+
l = tl.zeros((WY * WX,), tl.float32)
|
| 68 |
+
acc = tl.zeros((WY * WX, D), tl.float32)
|
| 69 |
+
|
| 70 |
+
for r0 in range(0, SY, BR):
|
| 71 |
+
kyr = r0 + tl.arange(0, BR * SX) // SX
|
| 72 |
+
kyg = ty * WY - R + kyr
|
| 73 |
+
ok = okg & (kyg >= 0) & (kyg < gyr) & (kyr < SY)
|
| 74 |
+
off = base + (kyg * gxp + kxg) * D
|
| 75 |
+
k = tl.load(K + off[:, None] + d[None, :], mask=ok[:, None], other=0.0)
|
| 76 |
+
v = tl.load(V + off[:, None] + d[None, :], mask=ok[:, None], other=0.0)
|
| 77 |
+
|
| 78 |
+
s = tl.dot(q, tl.trans(k)) * qk_scale
|
| 79 |
+
good = okx & (tl.abs(qyr[:, None] - (kyr[None, :] - R)) <= R) & ok[None, :]
|
| 80 |
+
s = tl.where(good, s, -1e30)
|
| 81 |
+
|
| 82 |
+
mn = tl.maximum(m, tl.max(s, 1))
|
| 83 |
+
p = tl.where(good, tl.math.exp2(s - mn[:, None]), 0.0)
|
| 84 |
+
a = tl.math.exp2(m - mn)
|
| 85 |
+
l = l * a + tl.sum(p, 1)
|
| 86 |
+
acc = acc * a[:, None] + tl.dot(p.to(v.dtype), v).to(tl.float32)
|
| 87 |
+
m = mn
|
| 88 |
+
|
| 89 |
+
lc = tl.maximum(l, 1e-30)
|
| 90 |
+
tl.store(O + qo, (acc / lc[:, None]).to(O.dtype.element_ty))
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _tile_width(r):
|
| 94 |
+
# The kernel needs a power-of-two key strip width (WX + 2R).
|
| 95 |
+
for wx in (16, 8):
|
| 96 |
+
sx = wx + 2 * r
|
| 97 |
+
if sx & (sx - 1) == 0:
|
| 98 |
+
return wx
|
| 99 |
+
raise ValueError(f"window radius {r}: no supported tile width")
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
@torch._dynamo.disable
|
| 103 |
+
def packed_window_attention(q, k, v, r, dims, max_tiles):
|
| 104 |
+
"""Windowed attention for a packed batch of differently shaped token grids.
|
| 105 |
+
|
| 106 |
+
Sample ``b`` is stored row-major in a padded ``gyp x gxp`` grid at the start of the
|
| 107 |
+
sequence, with its real ``gyr x gxr`` tokens in the top-left corner; the rest is padding.
|
| 108 |
+
dims: (B, 4) int32 ``[gyp, gxp, gyr, gxr]`` (gyp a multiple of 8, gxp of the tile width).
|
| 109 |
+
q, k, v: (B, heads, Nmax, head_dim). Padding outputs are zero (kernel) or unused (SDPA).
|
| 110 |
+
"""
|
| 111 |
+
B, nh, N, D = q.shape
|
| 112 |
+
if BACKEND == "auto" and _HAS_TRITON and q.is_cuda and D in (16, 32, 64, 128):
|
| 113 |
+
q, k, v = (t.to(torch.bfloat16).contiguous() for t in (q, k, v))
|
| 114 |
+
o = torch.zeros_like(q)
|
| 115 |
+
_window_attn_fwd[(max_tiles, B * nh)](
|
| 116 |
+
q, k, v, o, dims.to(q.device, torch.int32).contiguous(), NH=nh, NMAX=N, WY=8,
|
| 117 |
+
WX=_tile_width(r), R=r, D=D, BR=4, qk_scale=D ** -0.5 * _LOG2E, num_warps=8, num_stages=3)
|
| 118 |
+
return o
|
| 119 |
+
# reference path: per-sample block mask over the packed sequence
|
| 120 |
+
n = torch.arange(N, device=q.device)
|
| 121 |
+
d = dims.to(q.device).long()
|
| 122 |
+
yy, xx = n[None] // d[:, 1:2], n[None] % d[:, 1:2] # (B, N)
|
| 123 |
+
valid = (yy < d[:, 2:3]) & (xx < d[:, 3:4])
|
| 124 |
+
win = ((yy[:, :, None] - yy[:, None]).abs() <= r) & ((xx[:, :, None] - xx[:, None]).abs() <= r)
|
| 125 |
+
m = win & valid[:, None, :]
|
| 126 |
+
m = m | (~valid[:, :, None] & torch.eye(N, dtype=torch.bool, device=q.device))
|
| 127 |
+
return F.scaled_dot_product_attention(q.contiguous(), k.contiguous(), v.contiguous(),
|
| 128 |
+
attn_mask=m[:, None])
|
| 129 |
+
|
vae/config.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"in_channels": 3,
|
| 3 |
+
"patch_size": 8,
|
| 4 |
+
"latent_channels": 32,
|
| 5 |
+
"enc_width": 1024,
|
| 6 |
+
"enc_depth": 8,
|
| 7 |
+
"enc_mlp_ratio": 2.0,
|
| 8 |
+
"dec_width": 512,
|
| 9 |
+
"dec_depth": 6,
|
| 10 |
+
"dec_mlp_ratio": 4.0,
|
| 11 |
+
"dec_adaln_rank": 128,
|
| 12 |
+
"dec_skip_blocks": 2,
|
| 13 |
+
"dec_head_min_channels": 64,
|
| 14 |
+
"time_embed_dim": 256,
|
| 15 |
+
"head_dim": 64,
|
| 16 |
+
"window_radius": 8,
|
| 17 |
+
"rope_base": 100.0,
|
| 18 |
+
"latent_mean": [
|
| 19 |
+
-4.137261,
|
| 20 |
+
2.725146,
|
| 21 |
+
-1.435803,
|
| 22 |
+
-2.187023,
|
| 23 |
+
-4.161275,
|
| 24 |
+
-1.58918,
|
| 25 |
+
-2.527523,
|
| 26 |
+
3.002302,
|
| 27 |
+
2.793705,
|
| 28 |
+
-2.091501,
|
| 29 |
+
-2.632896,
|
| 30 |
+
-3.59589,
|
| 31 |
+
-3.069669,
|
| 32 |
+
-4.568713,
|
| 33 |
+
3.26668,
|
| 34 |
+
-0.897648,
|
| 35 |
+
2.072541,
|
| 36 |
+
5.451158,
|
| 37 |
+
1.747871,
|
| 38 |
+
-2.113284,
|
| 39 |
+
2.03444,
|
| 40 |
+
-1.379274,
|
| 41 |
+
0.66369,
|
| 42 |
+
2.712183,
|
| 43 |
+
-0.111181,
|
| 44 |
+
6.054838,
|
| 45 |
+
1.235881,
|
| 46 |
+
-2.02777,
|
| 47 |
+
3.663243,
|
| 48 |
+
-2.454632,
|
| 49 |
+
1.141335,
|
| 50 |
+
1.786136
|
| 51 |
+
],
|
| 52 |
+
"latent_std": [
|
| 53 |
+
1.030738,
|
| 54 |
+
0.951682,
|
| 55 |
+
1.079687,
|
| 56 |
+
0.957871,
|
| 57 |
+
1.198266,
|
| 58 |
+
1.102266,
|
| 59 |
+
1.213605,
|
| 60 |
+
0.947544,
|
| 61 |
+
1.085991,
|
| 62 |
+
1.093223,
|
| 63 |
+
0.9027,
|
| 64 |
+
1.010205,
|
| 65 |
+
0.920466,
|
| 66 |
+
1.044107,
|
| 67 |
+
1.023179,
|
| 68 |
+
0.992978,
|
| 69 |
+
0.991627,
|
| 70 |
+
1.073585,
|
| 71 |
+
1.114629,
|
| 72 |
+
1.056403,
|
| 73 |
+
1.093012,
|
| 74 |
+
1.015109,
|
| 75 |
+
1.007069,
|
| 76 |
+
0.976026,
|
| 77 |
+
0.982324,
|
| 78 |
+
1.093805,
|
| 79 |
+
1.036086,
|
| 80 |
+
1.005727,
|
| 81 |
+
1.104078,
|
| 82 |
+
1.042298,
|
| 83 |
+
1.095522,
|
| 84 |
+
0.960255
|
| 85 |
+
]
|
| 86 |
+
}
|
vae/example.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Minimal round trip: image -> latents -> image."""
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from PIL import Image
|
| 7 |
+
|
| 8 |
+
from ae import AutoEncoder
|
| 9 |
+
|
| 10 |
+
src = sys.argv[1] if len(sys.argv) > 1 else "input.png"
|
| 11 |
+
dst = sys.argv[2] if len(sys.argv) > 2 else "recon.png"
|
| 12 |
+
|
| 13 |
+
model = AutoEncoder.from_pretrained("Muinez/f8c32-ae") # HF repo id or local dir
|
| 14 |
+
|
| 15 |
+
img = Image.open(src).convert("RGB")
|
| 16 |
+
w, h = (img.width // 8) * 8, (img.height // 8) * 8 # sides must be multiples of 8
|
| 17 |
+
img = img.crop((0, 0, w, h))
|
| 18 |
+
x = torch.from_numpy(np.asarray(img, dtype=np.float32)).permute(2, 0, 1)[None] / 127.5 - 1
|
| 19 |
+
|
| 20 |
+
z = model.encode(x) # (1, 32, h/8, w/8), normalized
|
| 21 |
+
print("latent", tuple(z.shape), f"mean {z.mean():.3f} std {z.std():.3f}")
|
| 22 |
+
|
| 23 |
+
gen = torch.Generator(model.device).manual_seed(0)
|
| 24 |
+
y = model.decode(z, steps=4, generator=gen).clamp(-1, 1)
|
| 25 |
+
|
| 26 |
+
mse = (y.cpu() - x).pow(2).mean()
|
| 27 |
+
print(f"PSNR {10 * torch.log10(4 / mse):.2f} dB")
|
| 28 |
+
Image.fromarray(((y[0] + 1) * 127.5).round().byte().permute(1, 2, 0).cpu().numpy()).save(dst)
|
| 29 |
+
print("saved", dst)
|
vae/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a484ad128d0837630dd16248af9b2265d2721d7434dc696bf7451fcbf1809665
|
| 3 |
+
size 184623110
|