Text-to-Image
Diffusers
Safetensors
flow-matching
pyramidal-unet
latent-diffusion
asym-vae
from-scratch
ra
udit
Instructions to use AiArtLab/ra2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use AiArtLab/ra2 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/ra2", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
Ra sdxs-VAE (step 61000)
Browse files- .gitattributes +5 -31
- README.md +80 -0
- docs/ARCH.md +79 -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
- requirements.txt +27 -0
- src/export.py +105 -0
- src/make_dataset.py +94 -0
- src/make_dataset_people.py +174 -0
- src/model.py +344 -0
- src/publish.py +113 -0
- src/sample.py +74 -0
- src/text.py +132 -0
- src/tfrecord_stream.py +208 -0
- src/train.py +604 -0
- src/vae_adapter.py +61 -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 +77 -0
- transformer/diffusion_pytorch_model.safetensors +3 -0
- vae/config.json +108 -0
- vae/diffusion_pytorch_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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
pipeline_tag: text-to-image
|
| 4 |
+
tags: [text-to-image, flow-matching, pyramidal-unet, latent-diffusion, asym-vae, from-scratch, ra, udit]
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
# Ra2
|
| 8 |
+
|
| 9 |
+
**Ra2** — second-generation layout of Ra: the transformer no longer carries a pixel-space refiner, so the
|
| 10 |
+
model is a plain latent diffusion model and the image is decoded by a **separate asymmetric VAE**
|
| 11 |
+
(f8 encoder / f16 decoder → 512×512 from a 32×32 latent).
|
| 12 |
+
|
| 13 |
+
It is a 256→512 text-to-image model trained from scratch: a pyramidal U-transformer (240M, 40 blocks)
|
| 14 |
+
flow-matching in an 8×/32-channel latent, a frozen Ettin-150M text encoder with a learnable layer-slice
|
| 15 |
+
mixer (10.0M), and a **separate asymmetric VAE** that decodes the latent into pixels.
|
| 16 |
+
|
| 17 |
+
| | |
|
| 18 |
+
|---|---|
|
| 19 |
+
| output | 512×512 from a 32×32 latent (encoder f8, decoder f16) |
|
| 20 |
+
| sampler | 16 Euler steps, cfg 2.5 |
|
| 21 |
+
| latent | 32 channels from the bundled `vae/` (AsymmetricAutoencoderKL) |
|
| 22 |
+
| text encoder | [`jhu-clsp/ettin-encoder-150m`](https://huggingface.co/jhu-clsp/ettin-encoder-150m), frozen, bundled in `text_encoder/` |
|
| 23 |
+
| body | 40 blocks — L0: 6 conv + 8 attention, L1: 9, L2: 9, L3: 4 (widths 192→512→1024→1536) |
|
| 24 |
+
| training | latent flow matching only (MSE in latent space); the VAE decodes, no pixel-space refiner |
|
| 25 |
+
| licence | MIT |
|
| 26 |
+
|
| 27 |
+
## Design
|
| 28 |
+
|
| 29 |
+
The body is a U-stack: width grows as tokens shrink, so expensive attention runs on 16–64 tokens while
|
| 30 |
+
the 1024-token grid keeps 3×3 convolutions, and skip connections add every level back in the decoder.
|
| 31 |
+
One L0-attention block costs 0.86 GMAC (3.9% of the body) at 0.37M parameters, which is why extra blocks
|
| 32 |
+
were added at full resolution rather than in depth.
|
| 33 |
+
|
| 34 |
+
Positions: 2D RoPE inside attention plus an absolute 2D sin/cos embedding projected into the level width
|
| 35 |
+
by a zero-initialised linear layer, so new blocks can be grafted onto a trained model without changing
|
| 36 |
+
its output (verified bit-identical: `Body.graft`).
|
| 37 |
+
|
| 38 |
+
Decoding is plain: the latent goes through the VAE decoder once. That keeps the objective a clean latent
|
| 39 |
+
MSE — the price is that fine detail must live in the 32×32 latent.
|
| 40 |
+
|
| 41 |
+
## Training
|
| 42 |
+
|
| 43 |
+
| stage | what | steps |
|
| 44 |
+
|---|---|---|
|
| 45 |
+
| 1 | FLUX-Reason-6M, 256², old f8c32 AE + pixel-space refiner (23.9M) | 560 142 |
|
| 46 |
+
| 2 | same data, capacity graft 26 → 36 blocks, absolute-PE branch | 404 000 |
|
| 47 |
+
| 3 | switch to the asymmetric VAE (32×32 latent → 512²), latent-only loss, MJ shard | 5 000 |
|
| 48 |
+
| 4 | butterfly fine-tune, 966 images, zero-init graft 36 → 40 blocks (3+3 L0 attention) + EMA | 25 000 |
|
| 49 |
+
|
| 50 |
+
The bundled `transformer/` is the stage-4 checkpoint (step 61 000 of the current counter). It is a
|
| 51 |
+
**research stage**, not a general-purpose model: it was trained to fit one narrow dataset.
|
| 52 |
+
|
| 53 |
+
## Examples
|
| 54 |
+
|
| 55 |
+
The current checkpoint on its training domain (butterfly specimens, 512² downscaled to 256²), and the
|
| 56 |
+
same species in the source data:
|
| 57 |
+
|
| 58 |
+
<img src="docs/img/samples.png" width="100%"/>
|
| 59 |
+
|
| 60 |
+
## Usage
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
python3 src/sample.py --prompt "butterfly, Troides hypolitus" --out out.png
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
## Training / fine-tuning
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
bash run.sh /path/to/tfrecord_shards 10 # 10 эпох: TFRecord-шарды i1 (PNG 256² + подписи)
|
| 70 |
+
python3 src/train.py # дефолты: --init transformer, --out tmp/run, --target 708000
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
## Limitations
|
| 74 |
+
|
| 75 |
+
- Butterflies only, right now: 966 training images, 3-word captions.
|
| 76 |
+
- Colour drifts towards saturated species stereotypes; vein pattern is approximate, not faithful.
|
| 77 |
+
- No text rendering, no faces, no general scenes. Research artifact — no safety tuning.
|
| 78 |
+
|
| 79 |
+
MIT for our code and weights (`src/`, `transformer/`). The bundled VAE is `AsymmetricAutoencoderKL`
|
| 80 |
+
(32 latent channels, encoder f8 / decoder f16); the text encoder is MIT.
|
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 |
+
Размеры: тело **240.0M** (40 блоков), миксер 10.0M, VAE отдельно (асимметричный: энкодер f8 / декодер f16). Шаг обучения 97 мс при bs8 на 256² (8.1 ГБ). Для сравнения, прежний
|
| 29 |
+
плоский стек ширины 1024 на всех уровнях: 196 мс и hf 0.18 против 0.54 у пирамиды (код и чекпойнт
|
| 30 |
+
той архитектуры удалены как промежуточный артефакт).
|
| 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 |
+
| тело | 240.0M (40 блоков) |
|
| 67 |
+
| выбор слоёв текста | 10.04M |
|
| 68 |
+
| VAE | отдельно, `vae/` (AsymmetricAutoencoderKL) |
|
| 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/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>
|
requirements.txt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ra — что нужно, чтобы обучать и сэмплить.
|
| 2 |
+
# Версии не прибиты: нижние границы — то, с чем реально работает код.
|
| 3 |
+
# (проверено на torch 2.6 / transformers 5.5 / diffusers 0.41 / hf_hub 1.33)
|
| 4 |
+
|
| 5 |
+
# ядро: модель, обучение, сэмплинг
|
| 6 |
+
torch>=2.4
|
| 7 |
+
numpy>=1.24
|
| 8 |
+
Pillow>=10.0
|
| 9 |
+
safetensors>=0.4
|
| 10 |
+
huggingface_hub>=0.27 # hf download / HfFileSystem (поток шардов и датасетов)
|
| 11 |
+
transformers>=4.48 # ModernBertModel (текстовый энкодер Ettin)
|
| 12 |
+
|
| 13 |
+
# поток данных FLUX-Reason-6M (parquet)
|
| 14 |
+
datasets>=2.19
|
| 15 |
+
pyarrow>=14.0
|
| 16 |
+
|
| 17 |
+
# 8-битный AdamW; без него код автоматически перейдёт на обычный AdamW
|
| 18 |
+
bitsandbytes>=0.43
|
| 19 |
+
|
| 20 |
+
# необязательно: triton ускоряет оконное внимание внутри нашего VAE (vae/ae/window_attention.py).
|
| 21 |
+
# Без него работает путь на SDPA — тише, но немного медленнее. Обычно приходит вместе с torch.
|
| 22 |
+
# triton>=2.1
|
| 23 |
+
|
| 24 |
+
# ВАЖНО: сторонний VAE sdxs (vae_sdxs/, AsymmetricAutoencoderKL) требует diffusers >= 0.37.
|
| 25 |
+
# Наш собственный VAE (vae/, f8c32) идёт своим кодом в vae/ae/ и diffusers не требует.
|
| 26 |
+
# Если ставишь diffusers отдельно и не нужен sdxs — эту строку можно убрать.
|
| 27 |
+
diffusers>=0.37
|
src/export.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Экспорт обученного UDM в `transformer/`: safetensors + config.json.
|
| 2 |
+
|
| 3 |
+
Три части в одном файле, по префиксам:
|
| 4 |
+
body.* — пирамидальное тело, поток в латентном пространстве VAE
|
| 5 |
+
text_mixer.* — обучаемый выбор слоёв Ettin (SliceMixer)
|
| 6 |
+
|
| 7 |
+
python3 src/export.py --ckpt tmp/run256/last.pt --out transformer
|
| 8 |
+
"""
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import argparse
|
| 12 |
+
import json
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
from safetensors.torch import save_file
|
| 17 |
+
|
| 18 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def main():
|
| 22 |
+
ap = argparse.ArgumentParser()
|
| 23 |
+
ap.add_argument("--ckpt", default=str(ROOT / "tmp" / "run256" / "last.pt"))
|
| 24 |
+
ap.add_argument("--out", default=str(ROOT / "transformer"))
|
| 25 |
+
ap.add_argument("--metrics", default="", help="дописать метрики в config.json (строка)")
|
| 26 |
+
ap.add_argument("--half", action="store_true", help="хранить веса в fp16 (вдвое меньше, для публикации)")
|
| 27 |
+
args = ap.parse_args()
|
| 28 |
+
|
| 29 |
+
ck = torch.load(args.ckpt, map_location="cpu")
|
| 30 |
+
tensors = {}
|
| 31 |
+
body_sd = ck.get("body_ema") or ck["body"] # для публикации берём EMA, если обучен
|
| 32 |
+
mixer_sd = ck.get("mixer_ema") or ck["mixer"]
|
| 33 |
+
for name, sd in (("body", body_sd), ("text_mixer", mixer_sd)):
|
| 34 |
+
for k, v in sd.items():
|
| 35 |
+
tensors[f"{name}.{k}"] = (v.half() if args.half else v).contiguous()
|
| 36 |
+
out = Path(args.out)
|
| 37 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 38 |
+
save_file(tensors, str(out / "diffusion_pytorch_model.safetensors"))
|
| 39 |
+
|
| 40 |
+
ae_cfg = json.loads((ROOT / "vae" / "config.json").read_text()) # AsymmetricAutoencoderKL
|
| 41 |
+
ta = ck.get("args") or {}
|
| 42 |
+
counts = {} # сколько блоков в каждом списке — по ключам тензоров
|
| 43 |
+
for k in ck["body"]:
|
| 44 |
+
g = k.split(".")[0]
|
| 45 |
+
if g[0] in "ed" and "." in k:
|
| 46 |
+
idx = k.split(".")[1]
|
| 47 |
+
if idx.isdigit():
|
| 48 |
+
counts.setdefault(g, set()).add(idx)
|
| 49 |
+
nblk = {g: len(v) for g, v in counts.items()}
|
| 50 |
+
# конфиг описывает ТОЛЬКО тело; статистики латента — в vae/config.json, шаг — параметр запуска
|
| 51 |
+
cfg = {
|
| 52 |
+
"_class_name": "UDiT",
|
| 53 |
+
"format": "udm/v1",
|
| 54 |
+
"storage_dtype": "float16" if args.half else "float32",
|
| 55 |
+
"latent_channels": ae_cfg["latent_channels"],
|
| 56 |
+
"text_dim": ta.get("text_dim", 768),
|
| 57 |
+
"body": {
|
| 58 |
+
"kind": "пирамидальный U: ширина растёт к низу",
|
| 59 |
+
"widths": [192, 512, 1024, 1536],
|
| 60 |
+
"heads": [6, 8, 8, 8],
|
| 61 |
+
"blocks": nblk,
|
| 62 |
+
"levels": [
|
| 63 |
+
{"level": 0, "tokens": "÷1", "width": 192,
|
| 64 |
+
"blocks": "conv 6 enc / 4 dec + 2 self-attn на сторону"},
|
| 65 |
+
{"level": 1, "tokens": "÷2", "width": 512, "blocks": "attn 5 enc / 4 dec"},
|
| 66 |
+
{"level": 2, "tokens": "÷4", "width": 1024, "blocks": "attn 5 enc / 4 dec"},
|
| 67 |
+
{"level": 3, "tokens": "÷8", "width": 1536, "blocks": "attn 4 (боттлнек)"},
|
| 68 |
+
],
|
| 69 |
+
"skips": "аддитивные (SD-стиль): e0/e1/e2 прибавляются в декодере",
|
| 70 |
+
"down": "mean-pool 2x + Linear",
|
| 71 |
+
"up": "Linear + nearest 2x",
|
| 72 |
+
"positions": {
|
| 73 |
+
"rope": {"kind": "2d", "base": 100, "phase": "в латентных пикселях на каждом уровне"},
|
| 74 |
+
"absolute": {"kind": "2d sincos", "dim": 64,
|
| 75 |
+
"into": "zero-init Linear(64 -> width) к выходу stem, в паре с RoPE"},
|
| 76 |
+
},
|
| 77 |
+
"text": "кросс-внимание на всех уровнях",
|
| 78 |
+
"parametrization": "x0 латента",
|
| 79 |
+
"params": round(sum(v.numel() for k, v in tensors.items() if k.startswith("body.")) / 1e6, 2),
|
| 80 |
+
},
|
| 81 |
+
}
|
| 82 |
+
(out / "config.json").write_text(json.dumps(cfg, indent=2, ensure_ascii=False))
|
| 83 |
+
|
| 84 |
+
print(f"сохранено: {out}/diffusion_pytorch_model.safetensors "
|
| 85 |
+
f"({sum(v.numel() for v in tensors.values())/1e6:.1f}M параметров, "
|
| 86 |
+
f"{(out / 'diffusion_pytorch_model.safetensors').stat().st_size/1e6:.0f} МБ)")
|
| 87 |
+
print(f"тело {cfg['body']['params']}M, блоки {nblk}")
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def load_exported(model, enc, ae, path, device=None):
|
| 91 |
+
"""Загрузить из `transformer/` (config.json + safetensors с префиксами body./text_mixer.)."""
|
| 92 |
+
import json
|
| 93 |
+
from safetensors.torch import load_file
|
| 94 |
+
path = Path(path)
|
| 95 |
+
sd = load_file(str(path / "diffusion_pytorch_model.safetensors"), device=str(device) if device else None)
|
| 96 |
+
def sub(prefix):
|
| 97 |
+
return {k[len(prefix) + 1:]: v for k, v in sd.items() if k.startswith(prefix + ".")}
|
| 98 |
+
model.body.load_state_dict(sub("body"), strict=False)
|
| 99 |
+
enc.mixer.load_state_dict(sub("text_mixer"))
|
| 100 |
+
cfg_path = path / "config.json"
|
| 101 |
+
return json.loads(cfg_path.read_text()) if cfg_path.exists() else {}
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
if __name__ == "__main__":
|
| 105 |
+
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,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 32 |
+
DEV = "cuda" if torch.cuda.is_available() else "cpu"
|
| 33 |
+
_ROPE_CACHE: dict = {}
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def t_embed(t: torch.Tensor, dim: int = 256) -> torch.Tensor:
|
| 37 |
+
"""Синусоидальный эмбеддинг времени (t в [0,1] умножается на 1000)."""
|
| 38 |
+
half = dim // 2
|
| 39 |
+
f = torch.exp(-math.log(10000.0) * torch.arange(half, device=t.device, dtype=torch.float32) / half)
|
| 40 |
+
a = (t.float()[:, None] * 1000.0) * f[None]
|
| 41 |
+
return torch.cat([a.sin(), a.cos()], -1)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def body_rope(q, k, gh, gw, base: float = 100.0):
|
| 45 |
+
"""2D RoPE: 4 четверти head_dim, две крутятся по x, две по y. Фаза — в латентных пикселях."""
|
| 46 |
+
key = (gh, gw, q.shape[-1], q.device, base)
|
| 47 |
+
tabs = _ROPE_CACHE.get(key)
|
| 48 |
+
if tabs is None:
|
| 49 |
+
dq = q.shape[-1] // 4
|
| 50 |
+
fr = torch.exp(-math.log(base) * torch.arange(dq, device=q.device, dtype=torch.float32) / dq)
|
| 51 |
+
yy = torch.arange(gh, device=q.device).repeat_interleave(gw).float()
|
| 52 |
+
xx = torch.arange(gw, device=q.device).repeat(gh).float()
|
| 53 |
+
ax, ay = xx[:, None] * fr, yy[:, None] * fr
|
| 54 |
+
tabs = (ax.cos()[None, None], ax.sin()[None, None], ay.cos()[None, None], ay.sin()[None, None])
|
| 55 |
+
_ROPE_CACHE[key] = tabs
|
| 56 |
+
cx, sx, cy, sy = tabs
|
| 57 |
+
dq = q.shape[-1] // 4
|
| 58 |
+
|
| 59 |
+
def rot(t):
|
| 60 |
+
a, b, c, e = t[..., :dq], t[..., dq:2 * dq], t[..., 2 * dq:3 * dq], t[..., 3 * dq:]
|
| 61 |
+
return torch.cat([a * cx - b * sx, a * sx + b * cx, c * cy - e * sy, c * sy + e * cy], dim=-1)
|
| 62 |
+
|
| 63 |
+
return rot(q), rot(k)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class RMSNorm(nn.Module):
|
| 67 |
+
def __init__(self, d: int, eps: float = 1e-6):
|
| 68 |
+
super().__init__()
|
| 69 |
+
self.d, self.eps = d, eps
|
| 70 |
+
self.scale = nn.Parameter(torch.zeros(d))
|
| 71 |
+
|
| 72 |
+
def forward(self, x):
|
| 73 |
+
return F.rms_norm(x.float(), (self.d,), weight=(self.scale.float() + 1.0), eps=self.eps).to(x.dtype)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class ConvBlock(nn.Module):
|
| 77 |
+
"""ConvNeXt-ish: RMSNorm → depthwise 3×3 → expand 4× → GELU → project, всё под adaLN-гейтом."""
|
| 78 |
+
|
| 79 |
+
def __init__(self, c: int, expand: int = 4, text_dim: int | None = None, heads: int = 6):
|
| 80 |
+
super().__init__()
|
| 81 |
+
self.c, self.hid, self.h, self.hd = c, c * expand, heads, c // heads
|
| 82 |
+
self.norm = RMSNorm(c)
|
| 83 |
+
self.norm_x = RMSNorm(c)
|
| 84 |
+
self.dw = nn.Conv2d(c, c, 3, padding=1, groups=c)
|
| 85 |
+
self.up = nn.Linear(c, self.hid)
|
| 86 |
+
self.down = nn.Linear(self.hid, c)
|
| 87 |
+
self.mod = nn.Parameter(torch.zeros(5, c)) # shift, scale, gate | shift_x, scale_x
|
| 88 |
+
self.xatt = text_dim is not None
|
| 89 |
+
if self.xatt: # текст на мелком уровне: не экономим
|
| 90 |
+
self.xq, self.xo = nn.Linear(c, c, bias=False), nn.Linear(c, c, bias=False)
|
| 91 |
+
nn.init.zeros_(self.down.weight); nn.init.zeros_(self.down.bias)
|
| 92 |
+
|
| 93 |
+
def forward(self, x, vec, gh, gw, text=None, text_mask=None):
|
| 94 |
+
B = x.shape[0]
|
| 95 |
+
shift, scale, gate, shift_x, scale_x = (self.mod + vec).unbind(1)
|
| 96 |
+
h = self.norm(x) * (1 + scale[:, None]) + shift[:, None]
|
| 97 |
+
h = self.dw(h.transpose(1, 2).reshape(B, self.c, gh, gw)).flatten(2).transpose(1, 2)
|
| 98 |
+
x = x + gate[:, None] * self.down(F.gelu(self.up(h)))
|
| 99 |
+
if self.xatt and text is not None:
|
| 100 |
+
N = x.shape[1]
|
| 101 |
+
qq = self.xq(self.norm_x(x) * (1 + scale_x[:, None]) + shift_x[:, None])
|
| 102 |
+
qq = qq.reshape(B, N, self.h, self.hd).transpose(1, 2)
|
| 103 |
+
kk, vv = text
|
| 104 |
+
mask = text_mask[:, None, None, :] if text_mask is not None else None
|
| 105 |
+
xa = F.scaled_dot_product_attention(qq, kk, vv, attn_mask=mask)
|
| 106 |
+
x = x + self.xo(xa.transpose(1, 2).reshape(B, N, self.c))
|
| 107 |
+
return x
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
class AttnBlock(nn.Module):
|
| 111 |
+
"""Pre-norm трансформер: self-attention с 2D RoPE + (опц.) cross-attn к тексту + FFN."""
|
| 112 |
+
|
| 113 |
+
def __init__(self, c: int, heads: int, xatt_dim: int | None, mlp: float = 2.0):
|
| 114 |
+
super().__init__()
|
| 115 |
+
self.c, self.h, self.hd = c, heads, c // heads
|
| 116 |
+
self.n1, self.n2, self.n3 = RMSNorm(c), RMSNorm(c), RMSNorm(c)
|
| 117 |
+
self.qkv = nn.Linear(c, 3 * c, bias=False)
|
| 118 |
+
self.o = nn.Linear(c, c, bias=False)
|
| 119 |
+
self.up, self.down = nn.Linear(c, int(c * mlp)), nn.Linear(int(c * mlp), c)
|
| 120 |
+
self.mod = nn.Parameter(torch.zeros(6, c))
|
| 121 |
+
self.xatt = xatt_dim is not None
|
| 122 |
+
if self.xatt:
|
| 123 |
+
self.xq, self.xo = nn.Linear(c, c, bias=False), nn.Linear(c, c, bias=False)
|
| 124 |
+
nn.init.zeros_(self.down.weight); nn.init.zeros_(self.down.bias)
|
| 125 |
+
|
| 126 |
+
def forward(self, x, vec, gh, gw, text=None, text_mask=None):
|
| 127 |
+
B, N, C = x.shape
|
| 128 |
+
s1, g1, s2, g2, s3, g3 = (self.mod + vec).unbind(1)
|
| 129 |
+
h = self.n1(x) * (1 + s1[:, None])
|
| 130 |
+
q, k, v = self.qkv(h).reshape(B, N, 3, self.h, self.hd).permute(2, 0, 3, 1, 4)
|
| 131 |
+
q, k = body_rope(q, k, gh, gw)
|
| 132 |
+
a = F.scaled_dot_product_attention(q, k, v).transpose(1, 2).reshape(B, N, C)
|
| 133 |
+
x = x + g1[:, None] * self.o(a)
|
| 134 |
+
if self.xatt and text is not None:
|
| 135 |
+
qq = self.xq(self.n2(x)).reshape(B, N, self.h, self.hd).transpose(1, 2)
|
| 136 |
+
kk, vv = text
|
| 137 |
+
mask = text_mask[:, None, None, :] if text_mask is not None else None
|
| 138 |
+
xa = F.scaled_dot_product_attention(qq, kk, vv, attn_mask=mask)
|
| 139 |
+
x = x + g2[:, None] * self.xo(xa.transpose(1, 2).reshape(B, N, C))
|
| 140 |
+
h = self.n3(x) * (1 + s3[:, None])
|
| 141 |
+
return x + g3[:, None] * self.down(F.gelu(self.up(h)))
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def zero_out(blk):
|
| 145 |
+
"""Обнулить выходы внимания: новый блок на старте тождественен (FFN-выход и так zero-init)."""
|
| 146 |
+
for name in ("o", "xo"):
|
| 147 |
+
lin = getattr(blk, name, None)
|
| 148 |
+
if isinstance(lin, nn.Linear):
|
| 149 |
+
nn.init.zeros_(lin.weight)
|
| 150 |
+
if lin.bias is not None:
|
| 151 |
+
nn.init.zeros_(lin.bias)
|
| 152 |
+
return blk
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
GRAFT_DEFAULT = {"e0x": 3, "d0x": 3, "e1": 1, "d1": 1, "e2": 2, "d2": 2, "e3": 2}
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def sincos_2d(gh: int, gw: int, dim: int, device, dtype=None):
|
| 159 |
+
"""Абсолютные 2D-позиции (DiT-стиль sin/cos по каждой оси) — в паре с RoPE в attention."""
|
| 160 |
+
key = ("pe2d", gh, gw, dim, str(device))
|
| 161 |
+
tab = _ROPE_CACHE.get(key)
|
| 162 |
+
if tab is None:
|
| 163 |
+
half = dim // 2
|
| 164 |
+
f = 1.0 / (10000 ** (torch.arange(half // 2, device=device, dtype=torch.float32) / (half / 2)))
|
| 165 |
+
y = torch.arange(gh, device=device, dtype=torch.float32)[:, None] * f[None]
|
| 166 |
+
x = torch.arange(gw, device=device, dtype=torch.float32)[:, None] * f[None]
|
| 167 |
+
ey = torch.cat([y.sin(), y.cos()], -1)[:, None, :].expand(gh, gw, half)
|
| 168 |
+
ex = torch.cat([x.sin(), x.cos()], -1)[None, :, :].expand(gh, gw, half)
|
| 169 |
+
tab = torch.cat([ey, ex], -1).reshape(gh * gw, dim)
|
| 170 |
+
_ROPE_CACHE[key] = tab
|
| 171 |
+
return tab if dtype is None else tab.to(dtype)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
class Body(nn.Module):
|
| 175 |
+
"""U-стек: ширина 192 → 512 → 1024 → 1536 с аддитивными скипами.
|
| 176 |
+
|
| 177 |
+
Базовая архитектура = 36 блоков: L0 10 conv + 4 attn, L1 9, L2 9, L3 4 (238.5M, 44.4 GFLOP на 256 px).
|
| 178 |
+
Привитые блоки входят в GRAFT_DEFAULT и на конструкторе тождественны — так старые чекпойнты
|
| 179 |
+
(26 блоков) грузятся без сломки: лишние блоки остаются тождественными.
|
| 180 |
+
"""
|
| 181 |
+
|
| 182 |
+
def __init__(self, latent_ch: int = 32, text_dim: int = 768,
|
| 183 |
+
widths: tuple = (192, 512, 1024, 1536), enc: tuple = (6, 4, 3, 2),
|
| 184 |
+
dec: tuple = (4, 3, 2), xatt: bool = True):
|
| 185 |
+
super().__init__()
|
| 186 |
+
w0, w1, w2, w3 = widths
|
| 187 |
+
self.widths, self.enc, self.dec = widths, enc, dec
|
| 188 |
+
self.tdim = text_dim if xatt else None
|
| 189 |
+
heads = {0: 6, 1: 8, 2: 8, 3: 8}
|
| 190 |
+
self.stem = nn.Linear(latent_ch, w0)
|
| 191 |
+
self.pe_dim = 64
|
| 192 |
+
self.pe = nn.Linear(self.pe_dim, w0, bias=False) # zero-init: на старте не вмешивается
|
| 193 |
+
nn.init.zeros_(self.pe.weight)
|
| 194 |
+
self.e0 = nn.ModuleList([ConvBlock(w0, text_dim=text_dim if xatt else None) for _ in range(enc[0])])
|
| 195 |
+
self.e0a = AttnBlock(w0, 6, text_dim if xatt else None)
|
| 196 |
+
self.d0a = AttnBlock(w0, 6, text_dim if xatt else None)
|
| 197 |
+
self.e0x = nn.ModuleList() # привива��мые L0-attn (пусто = архитектура без изменений)
|
| 198 |
+
self.d0x = nn.ModuleList()
|
| 199 |
+
self.e1 = nn.ModuleList([AttnBlock(w1, heads[1], text_dim if xatt else None) for _ in range(enc[1])])
|
| 200 |
+
self.e2 = nn.ModuleList([AttnBlock(w2, heads[2], text_dim if xatt else None) for _ in range(enc[2])])
|
| 201 |
+
self.e3 = nn.ModuleList([AttnBlock(w3, heads[3], text_dim if xatt else None) for _ in range(enc[3])])
|
| 202 |
+
self.d2 = nn.ModuleList([AttnBlock(w2, heads[2], text_dim if xatt else None) for _ in range(dec[2])])
|
| 203 |
+
self.d1 = nn.ModuleList([AttnBlock(w1, heads[1], text_dim if xatt else None) for _ in range(dec[1])])
|
| 204 |
+
self.d0 = nn.ModuleList([ConvBlock(w0, text_dim=text_dim if xatt else None) for _ in range(dec[0])])
|
| 205 |
+
self.dn01, self.dn12, self.dn23 = nn.Linear(w0, w1), nn.Linear(w1, w2), nn.Linear(w2, w3)
|
| 206 |
+
self.up32, self.up21, self.up10 = nn.Linear(w3, w2), nn.Linear(w2, w1), nn.Linear(w1, w0)
|
| 207 |
+
self.tmlp = nn.Sequential(nn.Linear(256, 512), nn.SiLU(), nn.Linear(512, 512))
|
| 208 |
+
self.pmlp = nn.Linear(text_dim, 512)
|
| 209 |
+
self.ada = nn.ModuleDict({f"l{i}": nn.Sequential(nn.SiLU(), nn.Linear(512, (5 if i == 0 else 6) * w))
|
| 210 |
+
for i, w in enumerate(widths)})
|
| 211 |
+
self.ada["l0a"] = nn.Sequential(nn.SiLU(), nn.Linear(512, 6 * w0)) # для self-attn на L0
|
| 212 |
+
self.xk0 = nn.Linear(text_dim, 2 * w0)
|
| 213 |
+
self.xk1, self.xk2, self.xk3 = (nn.Linear(text_dim, 2 * w1), nn.Linear(text_dim, 2 * w2),
|
| 214 |
+
nn.Linear(text_dim, 2 * w3))
|
| 215 |
+
self.out_norm = RMSNorm(w0)
|
| 216 |
+
self.out = nn.Linear(w0, latent_ch)
|
| 217 |
+
nn.init.zeros_(self.out.weight); nn.init.zeros_(self.out.bias)
|
| 218 |
+
self.graft(GRAFT_DEFAULT) # базовая архитектура уже с прививкой (блоки тождественны)
|
| 219 |
+
|
| 220 |
+
# ---- прививка блоков
|
| 221 |
+
def graft(self, plan: dict) -> int:
|
| 222 |
+
"""Добавить блоки к уже обученной модели так, чтобы они стартовали тождеством.
|
| 223 |
+
|
| 224 |
+
plan: {"e1": 2, "d1": 2, "e3": 2}; ключи — имена списков блоков.
|
| 225 |
+
Тождество: FFN-выход (`down`) zero-init, выходы внимания (`o`, `xo`) обнуляем здесь.
|
| 226 |
+
"""
|
| 227 |
+
heads = {0: 6, 1: 8, 2: 8, 3: 8}
|
| 228 |
+
lvl = {"e0": 0, "d0": 0, "e0x": 0, "d0x": 0, "e1": 1, "d1": 1, "e2": 2, "d2": 2, "e3": 3}
|
| 229 |
+
dev = next(self.parameters()).device
|
| 230 |
+
added = 0
|
| 231 |
+
for key, n in plan.items():
|
| 232 |
+
seq = getattr(self, key)
|
| 233 |
+
for _ in range(int(n)):
|
| 234 |
+
if key in ("e0", "d0"):
|
| 235 |
+
blk = ConvBlock(self.widths[0], text_dim=self.tdim)
|
| 236 |
+
else:
|
| 237 |
+
blk = AttnBlock(self.widths[lvl[key]], heads[lvl[key]], self.tdim)
|
| 238 |
+
seq.append(zero_out(blk).to(dev))
|
| 239 |
+
added += 1
|
| 240 |
+
return added
|
| 241 |
+
|
| 242 |
+
# ---- переходы
|
| 243 |
+
@staticmethod
|
| 244 |
+
def downsample(x, gh, gw, proj):
|
| 245 |
+
B = x.shape[0]
|
| 246 |
+
d = x.view(B, gh // 2, 2, gw // 2, 2, -1).mean(dim=(2, 4))
|
| 247 |
+
return proj(d.reshape(B, (gh // 2) * (gw // 2), -1))
|
| 248 |
+
|
| 249 |
+
@staticmethod
|
| 250 |
+
def upsample(x, gh, gw, proj):
|
| 251 |
+
"""(B, gh*gw, C) -> (B, 4*gh*gw, C') в родительском row-major порядке."""
|
| 252 |
+
B = x.shape[0]
|
| 253 |
+
d = proj(x).view(B, gh, 1, gw, 1, -1).expand(B, gh, 2, gw, 2, proj.out_features)
|
| 254 |
+
return d.reshape(B, 4 * gh * gw, proj.out_features)
|
| 255 |
+
|
| 256 |
+
def _txt(self, proj, text, heads):
|
| 257 |
+
B, L, _ = text.shape
|
| 258 |
+
kk, vv = proj(text).chunk(2, dim=-1)
|
| 259 |
+
hd = kk.shape[-1] // heads
|
| 260 |
+
return (kk.reshape(B, L, heads, hd).transpose(1, 2), vv.reshape(B, L, heads, hd).transpose(1, 2))
|
| 261 |
+
|
| 262 |
+
def forward(self, z, t, text, text_mask, gh=None, gw=None):
|
| 263 |
+
"""gh/gw принимаются для совместимости с интерфейсом UDM и игнорируются."""
|
| 264 |
+
B, C, gh0, gw0 = z.shape
|
| 265 |
+
# pooled-текст в adaLN: среднее ТОЛЬКО по реальным токенам (паддинг иначе разбавляет вектор,
|
| 266 |
+
# и поведение модели начинает зависеть от длины паддинга, а не от подписи)
|
| 267 |
+
# pooled-текст для adaLN: среднее ТОЛЬКО по реальным токенам. Иначе вектор зависит от
|
| 268 |
+
# длины паддинга, а не от подписи (обучение паддит до длины подписи, инференс — до 192)
|
| 269 |
+
m = text_mask[..., None].to(text.dtype)
|
| 270 |
+
pooled = (text * m).sum(1) / m.sum(1).clamp(min=1.0)
|
| 271 |
+
cond = self.tmlp(t_embed(t, 256)) + self.pmlp(pooled)
|
| 272 |
+
v = [self.ada[f"l{i}"](cond).view(B, 5 if i == 0 else 6, -1) for i in range(4)]
|
| 273 |
+
v0a = self.ada["l0a"](cond).view(B, 6, -1)
|
| 274 |
+
tk0 = self._txt(self.xk0, text, 6)
|
| 275 |
+
tk1 = self._txt(self.xk1, text, 8)
|
| 276 |
+
tk2 = self._txt(self.xk2, text, 8)
|
| 277 |
+
tk3 = self._txt(self.xk3, text, 8)
|
| 278 |
+
|
| 279 |
+
x = self.stem(z.flatten(2).transpose(1, 2))
|
| 280 |
+
x = x + self.pe(sincos_2d(gh0, gw0, self.pe_dim, x.device, x.dtype))
|
| 281 |
+
for blk in self.e0:
|
| 282 |
+
x = blk(x, v[0], gh0, gw0, tk0, text_mask)
|
| 283 |
+
x = self.e0a(x, v0a, gh0, gw0, tk0, text_mask)
|
| 284 |
+
for blk in self.e0x:
|
| 285 |
+
x = blk(x, v0a, gh0, gw0, tk0, text_mask)
|
| 286 |
+
s0 = x
|
| 287 |
+
g1h, g1w = gh0 // 2, gw0 // 2
|
| 288 |
+
x = self.downsample(x, gh0, gw0, self.dn01)
|
| 289 |
+
for blk in self.e1:
|
| 290 |
+
x = blk(x, v[1], g1h, g1w, tk1, text_mask)
|
| 291 |
+
s1 = x
|
| 292 |
+
g2h, g2w = g1h // 2, g1w // 2
|
| 293 |
+
x = self.downsample(x, g1h, g1w, self.dn12)
|
| 294 |
+
for blk in self.e2:
|
| 295 |
+
x = blk(x, v[2], g2h, g2w, tk2, text_mask)
|
| 296 |
+
s2 = x
|
| 297 |
+
g3h, g3w = g2h // 2, g2w // 2
|
| 298 |
+
x = self.downsample(x, g2h, g2w, self.dn23)
|
| 299 |
+
for blk in self.e3:
|
| 300 |
+
x = blk(x, v[3], g3h, g3w, tk3, text_mask)
|
| 301 |
+
# декодер: скипы аддитивные
|
| 302 |
+
x = self.upsample(x, g3h, g3w, self.up32) + s2
|
| 303 |
+
for blk in self.d2:
|
| 304 |
+
x = blk(x, v[2], g2h, g2w, tk2, text_mask)
|
| 305 |
+
x = self.upsample(x, g2h, g2w, self.up21) + s1
|
| 306 |
+
for blk in self.d1:
|
| 307 |
+
x = blk(x, v[1], g1h, g1w, tk1, text_mask)
|
| 308 |
+
x = self.upsample(x, g1h, g1w, self.up10) + s0
|
| 309 |
+
x = self.d0a(x, v0a, gh0, gw0, tk0, text_mask)
|
| 310 |
+
for blk in self.d0x:
|
| 311 |
+
x = blk(x, v0a, gh0, gw0, tk0, text_mask)
|
| 312 |
+
for blk in self.d0:
|
| 313 |
+
x = blk(x, v[0], gh0, gw0, tk0, text_mask)
|
| 314 |
+
return self.out(self.out_norm(x)).transpose(1, 2).reshape(B, C, gh0, gw0)
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
class UDiT(nn.Module):
|
| 318 |
+
"""Пирамидальное тело + хвост-декодер AE; интерфейс как у UDM."""
|
| 319 |
+
|
| 320 |
+
def __init__(self, ae, text_dim: int = 768, **kw):
|
| 321 |
+
super().__init__()
|
| 322 |
+
self.ae = ae
|
| 323 |
+
self.body = Body(latent_ch=ae.config.latent_channels, text_dim=text_dim, **kw)
|
| 324 |
+
|
| 325 |
+
def to_raw(self, z):
|
| 326 |
+
return z * self.ae.latent_std + self.ae.latent_mean
|
| 327 |
+
|
| 328 |
+
@torch.no_grad()
|
| 329 |
+
def sample(self, text, text_mask, gh, gw, steps=4, tail_steps=4, generator=None,
|
| 330 |
+
param="x0", cfg=1.0, null_text=None, null_mask=None, clip_norm=0.0):
|
| 331 |
+
dev = self.ae.device
|
| 332 |
+
z = torch.randn(1, self.ae.config.latent_channels, gh, gw, device=dev, generator=generator)
|
| 333 |
+
with torch.autocast(dev.type, torch.bfloat16, enabled=dev.type == "cuda"):
|
| 334 |
+
for i in range(steps):
|
| 335 |
+
t = torch.full((1,), 1.0 - i / steps, device=dev)
|
| 336 |
+
o = self.body(z, t, text, text_mask).float()
|
| 337 |
+
if cfg > 1.0 and null_text is not None:
|
| 338 |
+
ou = self.body(z, t, null_text, null_mask).float()
|
| 339 |
+
o = ou + cfg * (o - ou)
|
| 340 |
+
v = o if param == "vel" else (z - o) / t.clamp(min=1e-2)[:, None, None, None]
|
| 341 |
+
z = z - v / steps
|
| 342 |
+
if clip_norm:
|
| 343 |
+
z = z.clamp(-clip_norm, clip_norm)
|
| 344 |
+
return self.ae.decode_raw(self.to_raw(z), steps=tail_steps, generator=generator)
|
src/publish.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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", "requirements.txt", "run_sdxs.sh", ".gitattributes", "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 |
+
]
|
| 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("--readme", default="README.md", help="какой README положить в корень")
|
| 53 |
+
ap.add_argument("--push", action="store_true", help="залить собранное на Hub (публичный репозиторий)")
|
| 54 |
+
ap.add_argument("--step", type=int, default=None, help="шаг чекпойнта — только для сообщения коммита")
|
| 55 |
+
args = ap.parse_args()
|
| 56 |
+
out = Path(args.out)
|
| 57 |
+
if out.exists():
|
| 58 |
+
shutil.rmtree(out)
|
| 59 |
+
out.mkdir(parents=True)
|
| 60 |
+
|
| 61 |
+
rels = list(COPY) + [args.readme, "vae/config.json", "transformer/config.json"]
|
| 62 |
+
for rel in rels:
|
| 63 |
+
src = ROOT / rel
|
| 64 |
+
if not src.exists():
|
| 65 |
+
continue
|
| 66 |
+
dst = out / rel
|
| 67 |
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
| 68 |
+
if src.is_dir():
|
| 69 |
+
shutil.copytree(src, dst, ignore=shutil.ignore_patterns("__pycache__", ".ipynb_checkpoints"))
|
| 70 |
+
else:
|
| 71 |
+
dst_rel = "README.md" if rel == args.readme else rel
|
| 72 |
+
shutil.copy2(src, (out / dst_rel))
|
| 73 |
+
|
| 74 |
+
tdir, vdir = ROOT / "transformer", ROOT / "vae"
|
| 75 |
+
half_file(tdir / "diffusion_pytorch_model.safetensors",
|
| 76 |
+
out / "transformer" / "diffusion_pytorch_model.safetensors")
|
| 77 |
+
vae_weights = vdir / "model.safetensors"
|
| 78 |
+
if not vae_weights.exists(): # AsymmetricAutoencoderKL кладёт так
|
| 79 |
+
vae_weights = vdir / "diffusion_pytorch_model.safetensors"
|
| 80 |
+
half_file(vae_weights, out / "vae" / vae_weights.name)
|
| 81 |
+
|
| 82 |
+
sd = torch.load(ROOT / "text_encoder" / "pytorch_model.bin", map_location="cpu", weights_only=True)
|
| 83 |
+
sd = {k: v for k, v in sd.items() if not k.startswith(("head.", "decoder."))} # MLM-голова не нужна
|
| 84 |
+
save_file({k: v.half().contiguous() for k, v in sd.items()},
|
| 85 |
+
str(out / "text_encoder" / "model.safetensors"))
|
| 86 |
+
print(f"[fp16] text_encoder/model.safetensors: {len(sd)} тензоров "
|
| 87 |
+
f"({sum(v.numel() for v in sd.values()) / 1e6:.1f}M), "
|
| 88 |
+
f"{(out / 'text_encoder' / 'model.safetensors').stat().st_size / 2**20:.0f} МиБ", flush=True)
|
| 89 |
+
|
| 90 |
+
cfg_path = out / "transformer" / "config.json"
|
| 91 |
+
cfg = json.loads(cfg_path.read_text())
|
| 92 |
+
cfg["storage_dtype"] = "float16"
|
| 93 |
+
cfg_path.write_text(json.dumps(cfg, indent=2, ensure_ascii=False))
|
| 94 |
+
|
| 95 |
+
total = sum(f.stat().st_size for f in out.rglob("*") if f.is_file())
|
| 96 |
+
print(f"[готово] {out}: {total / 2**30:.2f} ГиБ", flush=True)
|
| 97 |
+
|
| 98 |
+
if args.push:
|
| 99 |
+
from huggingface_hub import CommitOperationAdd, HfApi
|
| 100 |
+
api = HfApi()
|
| 101 |
+
api.create_repo(args.repo, repo_type="model", private=False, exist_ok=True)
|
| 102 |
+
# create_commit вместо upload_folder: upload_folder уважает .gitignore из бандла
|
| 103 |
+
# (а там шаблоны `*.safetensors`) и молча не зали��ает веса
|
| 104 |
+
ops = [CommitOperationAdd(path_in_repo=p.relative_to(out).as_posix(), path_or_fileobj=str(p))
|
| 105 |
+
for p in sorted(out.rglob("*")) if p.is_file()]
|
| 106 |
+
api.create_commit(repo_id=args.repo, repo_type="model", operations=ops,
|
| 107 |
+
commit_message=(f"Ra sdxs-VAE (step {args.step})" if args.step
|
| 108 |
+
else "Ra sdxs-VAE (weights)"))
|
| 109 |
+
print(f"[push] https://huggingface.co/{args.repo}: {len(ops)} файлов", flush=True)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
if __name__ == "__main__":
|
| 113 |
+
main()
|
src/sample.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 vae_adapter import VAE # 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("--cfg", type=float, default=2.5)
|
| 32 |
+
ap.add_argument("--seed", type=int, default=0)
|
| 33 |
+
ap.add_argument("--n", type=int, default=1, help="сколько картинок подряд от --seed")
|
| 34 |
+
ap.add_argument("--text-len", type=int, default=192)
|
| 35 |
+
args = ap.parse_args()
|
| 36 |
+
|
| 37 |
+
ae = VAE.from_pretrained(str(ROOT / "vae"), DEV)
|
| 38 |
+
enc = EttinText(str(ROOT / "text_encoder"), DEV, max_len=args.text_len)
|
| 39 |
+
m = UDiT(ae, text_dim=enc.dim).to(DEV)
|
| 40 |
+
|
| 41 |
+
src = Path(args.model)
|
| 42 |
+
if src.is_dir():
|
| 43 |
+
step = load_exported(m, enc, ae, src, DEV).get("step")
|
| 44 |
+
else:
|
| 45 |
+
sd = torch.load(src, map_location=DEV, weights_only=False)
|
| 46 |
+
own = m.body.state_dict()
|
| 47 |
+
m.body.load_state_dict({k: v for k, v in sd["body"].items() if k in own}, strict=False)
|
| 48 |
+
enc.mixer.load_state_dict(sd["mixer"])
|
| 49 |
+
if sd.get("decoder") is not None:
|
| 50 |
+
ae.decoder.load_state_dict(sd["decoder"])
|
| 51 |
+
step = sd.get("step")
|
| 52 |
+
m.eval()
|
| 53 |
+
print(f"[model] {src}" + (f", шаг {step}" if step else ""), flush=True)
|
| 54 |
+
|
| 55 |
+
null_hs, null_mk = enc.slices([""], pad_to_len=args.text_len)
|
| 56 |
+
null_t = enc.mix(null_hs.float())
|
| 57 |
+
hs, mk = enc.slices([args.prompt], pad_to_len=args.text_len)
|
| 58 |
+
txt = enc.mix(hs.float())
|
| 59 |
+
|
| 60 |
+
out = Path(args.out)
|
| 61 |
+
out.parent.mkdir(parents=True, exist_ok=True)
|
| 62 |
+
gh = gw = args.size // 8
|
| 63 |
+
for i in range(args.n):
|
| 64 |
+
y = m.sample(txt, mk, gh, gw, steps=args.steps, tail_steps=0, cfg=args.cfg,
|
| 65 |
+
null_text=null_t, null_mask=null_mk,
|
| 66 |
+
generator=torch.Generator(DEV).manual_seed(args.seed + i))
|
| 67 |
+
im = Image.fromarray(((y[0].clamp(-1, 1) + 1) * 127.5).byte().permute(1, 2, 0).cpu().numpy())
|
| 68 |
+
path = out if args.n == 1 else out.with_name(f"{out.stem}_{args.seed + i}{out.suffix}")
|
| 69 |
+
im.save(path)
|
| 70 |
+
print(f"[ok] {path} {im.size[0]}x{im.size[1]}, seed {args.seed + i}", flush=True)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
if __name__ == "__main__":
|
| 74 |
+
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/tfrecord_stream.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Поток картинок из TFRecord-шардов i1 (PNG 256×256 + 5 подписей) вместо parquet-потока FLUX.
|
| 2 |
+
|
| 3 |
+
Отличие от HFImageStream: запись TFRecord = [uint64 len][crc][payload][crc], внутри PNG-байты
|
| 4 |
+
и `caption` — пять длинных перефразировок одной картинки. Поэтому:
|
| 5 |
+
* из пяти подписей берём случайно самую короткую или следующую по длине (бюджета на все нет,
|
| 6 |
+
а короткая лучше влезает в --text-len);
|
| 7 |
+
* картинки уже 256×256 — ресайз только если сторона не совпала с --size;
|
| 8 |
+
* источник: локальная папка с шардами (быстро) или прямо репозиторий HF через range-запросы
|
| 9 |
+
(block_size 16 МБ даёт ~90 записей/с, ~9.7 МБ/с — на уровне нашего темпа обучения).
|
| 10 |
+
"""
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import io
|
| 14 |
+
import queue
|
| 15 |
+
import random
|
| 16 |
+
import struct
|
| 17 |
+
import threading
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
|
| 20 |
+
import numpy as np
|
| 21 |
+
import torch
|
| 22 |
+
from PIL import Image
|
| 23 |
+
|
| 24 |
+
SHARD_GLOB = "*.tfrecord-*"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# ---------------------------------------------------------------- разбор формата
|
| 28 |
+
def _varint(buf: bytes, i: int) -> tuple[int, int]:
|
| 29 |
+
shift = val = 0
|
| 30 |
+
while True:
|
| 31 |
+
b = buf[i]; i += 1
|
| 32 |
+
val |= (b & 0x7F) << shift
|
| 33 |
+
if not b & 0x80:
|
| 34 |
+
return val, i
|
| 35 |
+
shift += 7
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _fields(buf: bytes):
|
| 39 |
+
i = 0
|
| 40 |
+
while i < len(buf):
|
| 41 |
+
key, i = _varint(buf, i)
|
| 42 |
+
num, wire = key >> 3, key & 7
|
| 43 |
+
if wire == 2:
|
| 44 |
+
ln, i = _varint(buf, i)
|
| 45 |
+
yield num, buf[i:i + ln]
|
| 46 |
+
i += ln
|
| 47 |
+
elif wire == 0:
|
| 48 |
+
v, i = _varint(buf, i)
|
| 49 |
+
yield num, v
|
| 50 |
+
elif wire == 1:
|
| 51 |
+
yield num, buf[i:i + 8]; i += 8
|
| 52 |
+
elif wire == 5:
|
| 53 |
+
yield num, buf[i:i + 4]; i += 4
|
| 54 |
+
else:
|
| 55 |
+
raise ValueError(f"wire type {wire}")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _packed_i64(buf: bytes) -> list[int]:
|
| 59 |
+
out, i = [], 0
|
| 60 |
+
while i < len(buf):
|
| 61 |
+
v, i = _varint(buf, i)
|
| 62 |
+
out.append(v)
|
| 63 |
+
return out
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def parse_example(payload: bytes) -> dict:
|
| 67 |
+
"""tf.train.Example → {поле: список bytes | список int}"""
|
| 68 |
+
out = {}
|
| 69 |
+
for num, features in _fields(payload):
|
| 70 |
+
if num != 1:
|
| 71 |
+
continue
|
| 72 |
+
for fn, entry in _fields(features):
|
| 73 |
+
if fn != 1:
|
| 74 |
+
continue
|
| 75 |
+
key, val = None, {}
|
| 76 |
+
for en, ev in _fields(entry):
|
| 77 |
+
if en == 1:
|
| 78 |
+
key = ev.decode()
|
| 79 |
+
elif en == 2:
|
| 80 |
+
for fnum, fval in _fields(ev):
|
| 81 |
+
if fnum == 1:
|
| 82 |
+
val["bytes"] = [v for n, v in _fields(fval) if n == 1]
|
| 83 |
+
elif fnum == 3:
|
| 84 |
+
val["int64"] = [x for n, v in _fields(fval) if n == 1 for x in _packed_i64(v)]
|
| 85 |
+
out[key] = val.get("bytes", val.get("int64"))
|
| 86 |
+
return out
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def count_records(paths) -> int:
|
| 90 |
+
"""Сколько записей в шардах — читаем только заголовки (seek через payload), быстро."""
|
| 91 |
+
total = 0
|
| 92 |
+
for path in paths:
|
| 93 |
+
with open(path, "rb") as fh:
|
| 94 |
+
size = fh.seek(0, 2)
|
| 95 |
+
pos = 0
|
| 96 |
+
while pos + 12 <= size:
|
| 97 |
+
fh.seek(pos)
|
| 98 |
+
head = fh.read(8)
|
| 99 |
+
if len(head) < 8:
|
| 100 |
+
break
|
| 101 |
+
(n,) = struct.unpack("<Q", head)
|
| 102 |
+
pos += 8 + 4 + n + 4
|
| 103 |
+
total += 1
|
| 104 |
+
return total
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def count_shard_records(source: str) -> int | None:
|
| 108 |
+
"""Точное число записей для локальной папки; None, если источник удалённый."""
|
| 109 |
+
if not Path(source).is_dir():
|
| 110 |
+
return None
|
| 111 |
+
paths = sorted(p.as_posix() for p in Path(source).glob(SHARD_GLOB))
|
| 112 |
+
return count_records(paths) if paths else None
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def read_records(fh):
|
| 116 |
+
"""файловый объект (локальный или HfFileSystem) → поток payload'ов"""
|
| 117 |
+
while True:
|
| 118 |
+
head = fh.read(8)
|
| 119 |
+
if len(head) < 8:
|
| 120 |
+
return
|
| 121 |
+
(n,) = struct.unpack("<Q", head)
|
| 122 |
+
fh.read(4)
|
| 123 |
+
payload = fh.read(n)
|
| 124 |
+
fh.read(4)
|
| 125 |
+
yield payload
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
# ---------------------------------------------------------------- поток
|
| 129 |
+
class TFRecordStream:
|
| 130 |
+
def __init__(self, source: str, size: int = 256, workers: int = 8, prefetch: int = 256,
|
| 131 |
+
block_mb: int = 16, seed: int = 0, caption_short2: bool = True):
|
| 132 |
+
"""source — локальная папка с шардами или id датасета на HF (repo_type=dataset)."""
|
| 133 |
+
self.q: queue.Queue = queue.Queue(maxsize=prefetch)
|
| 134 |
+
self.stop = False
|
| 135 |
+
self.size, self.workers, self.seed = size, workers, seed
|
| 136 |
+
self.caption_short2 = caption_short2
|
| 137 |
+
self.local = Path(source).is_dir()
|
| 138 |
+
self.source = str(source)
|
| 139 |
+
self.block = block_mb * 2 ** 20
|
| 140 |
+
threading.Thread(target=self._loop, daemon=True).start()
|
| 141 |
+
|
| 142 |
+
# ---- список шардов
|
| 143 |
+
def _shards(self) -> list[str]:
|
| 144 |
+
if self.local:
|
| 145 |
+
files = sorted(p.as_posix() for p in Path(self.source).glob(SHARD_GLOB))
|
| 146 |
+
else:
|
| 147 |
+
from huggingface_hub import HfFileSystem
|
| 148 |
+
fs = HfFileSystem()
|
| 149 |
+
files = [f"datasets/{self.source}/{p}" for p in sorted(fs.ls(f"datasets/{self.source}"))
|
| 150 |
+
if ".tfrecord-" in p]
|
| 151 |
+
if not files:
|
| 152 |
+
raise FileNotFoundError(f"нет шардов в {self.source}")
|
| 153 |
+
random.Random(self.seed).shuffle(files)
|
| 154 |
+
return files
|
| 155 |
+
|
| 156 |
+
def _open(self, path: str):
|
| 157 |
+
if self.local:
|
| 158 |
+
return open(path, "rb")
|
| 159 |
+
from huggingface_hub import HfFileSystem
|
| 160 |
+
return HfFileSystem().open(path, "rb", block_size=self.block)
|
| 161 |
+
|
| 162 |
+
# ---- подготовка одной записи
|
| 163 |
+
def _prepare(self, payload: bytes):
|
| 164 |
+
d = parse_example(payload)
|
| 165 |
+
im = Image.open(io.BytesIO(d["image"][0])).convert("RGB")
|
| 166 |
+
w, h = im.size
|
| 167 |
+
if min(w, h) != self.size:
|
| 168 |
+
sc = self.size / min(w, h)
|
| 169 |
+
im = im.resize((max(self.size, round(w * sc)), max(self.size, round(h * sc))), Image.LANCZOS)
|
| 170 |
+
w, h = im.size
|
| 171 |
+
im = im.crop(((w - self.size) // 2, (h - self.size) // 2,
|
| 172 |
+
(w - self.size) // 2 + self.size, (h - self.size) // 2 + self.size))
|
| 173 |
+
caps = [c.decode() if isinstance(c, bytes) else str(c) for c in d.get("caption", [])]
|
| 174 |
+
caps = sorted(caps, key=len)
|
| 175 |
+
if not caps:
|
| 176 |
+
caps = [""]
|
| 177 |
+
cap = random.choice(caps[:2]) if self.caption_short2 else random.choice(caps)
|
| 178 |
+
arr = torch.from_numpy(np.asarray(im, dtype=np.float32)).permute(2, 0, 1) / 127.5 - 1.0
|
| 179 |
+
return arr, cap
|
| 180 |
+
|
| 181 |
+
def _loop(self):
|
| 182 |
+
import collections
|
| 183 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 184 |
+
with ThreadPoolExecutor(max_workers=self.workers) as pool:
|
| 185 |
+
while not self.stop:
|
| 186 |
+
try:
|
| 187 |
+
for shard in self._shards():
|
| 188 |
+
fh = self._open(shard)
|
| 189 |
+
try:
|
| 190 |
+
it = read_records(fh)
|
| 191 |
+
flying = collections.deque()
|
| 192 |
+
while not self.stop:
|
| 193 |
+
while len(flying) < self.workers:
|
| 194 |
+
flying.append(pool.submit(self._prepare, next(it)))
|
| 195 |
+
self.q.put(flying.popleft().result())
|
| 196 |
+
finally:
|
| 197 |
+
fh.close()
|
| 198 |
+
except Exception as e:
|
| 199 |
+
print(f"[tfrecord] сбой ({type(e).__name__}: {e}), переподключение через 5 с", flush=True)
|
| 200 |
+
import time
|
| 201 |
+
time.sleep(5)
|
| 202 |
+
|
| 203 |
+
def batch(self, n: int):
|
| 204 |
+
imgs, caps = [], []
|
| 205 |
+
while len(imgs) < n:
|
| 206 |
+
arr, cap = self.q.get()
|
| 207 |
+
imgs.append(arr); caps.append(cap)
|
| 208 |
+
return torch.stack(imgs), caps
|
src/train.py
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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), switch, 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 vae_adapter import VAE # noqa: E402
|
| 41 |
+
from tfrecord_stream import TFRecordStream, count_shard_records # noqa: E402 (шарды i1)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def load_ae(args):
|
| 45 |
+
"""VAE проекта: асимметричный, декодер отдаёт 2x. Хвоста и пиксельного лосса нет."""
|
| 46 |
+
return VAE.from_pretrained(str(ROOT / "vae"), DEV)
|
| 47 |
+
|
| 48 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# ------------------------------------------------------------------ расписание
|
| 52 |
+
def lr_at(step: int, base: float, min_lr: float, warmup: int, period: int, start: int = 0) -> float:
|
| 53 |
+
"""Прогрев `warmup` шагов от `start`, затем cosine до `min_lr` за `period` шагов."""
|
| 54 |
+
rel = step - start
|
| 55 |
+
if warmup and rel <= warmup:
|
| 56 |
+
return base * min(1.0, rel / max(warmup, 1))
|
| 57 |
+
if period <= warmup:
|
| 58 |
+
return base
|
| 59 |
+
p = min(1.0, (rel - warmup) / (period - warmup))
|
| 60 |
+
return min_lr + (base - min_lr) * 0.5 * (1 + math.cos(math.pi * p))
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class HFImageStream:
|
| 64 |
+
"""Поток parquet-шардов HF: декод/ресайз/кроп в фоновых потоках, обучение не ждёт диск.
|
| 65 |
+
|
| 66 |
+
Латент и текст НЕ кэшируются (каждая картинка встречается один раз), поэтому шаг дороже,
|
| 67 |
+
чем на папке: +3-4 мс на картинку у AE и +1.2 мс у Ettin.
|
| 68 |
+
"""
|
| 69 |
+
|
| 70 |
+
def __init__(self, dataset, parts, size, fields, lo, hi, shuffle_buffer=1000, prefetch=256,
|
| 71 |
+
workers=8, seed=0):
|
| 72 |
+
import queue
|
| 73 |
+
self.q, self.stop = queue.Queue(maxsize=prefetch), False
|
| 74 |
+
self.size, self.fields, self.lo, self.hi = size, fields, lo, hi
|
| 75 |
+
self.dataset, self.parts = dataset, parts
|
| 76 |
+
self.buffer, self.workers, self.seed = shuffle_buffer, workers, seed
|
| 77 |
+
import threading
|
| 78 |
+
threading.Thread(target=self._loop, daemon=True).start()
|
| 79 |
+
|
| 80 |
+
def _items(self):
|
| 81 |
+
"""Шарды всех частей перемешиваются между собой (иначе эпоха идёт блоками по частям)."""
|
| 82 |
+
import random
|
| 83 |
+
from datasets import load_dataset
|
| 84 |
+
from huggingface_hub import list_repo_files
|
| 85 |
+
repo = list_repo_files(self.dataset, repo_type="dataset")
|
| 86 |
+
files = [f for f in repo
|
| 87 |
+
if f.endswith(".parquet") and any(f.startswith(p + "/") for p in self.parts)]
|
| 88 |
+
if not files: # запасной путь, если листинг не отдал файлы
|
| 89 |
+
files = [f"hf://datasets/{self.dataset}/{p}/*.parquet" for p in self.parts]
|
| 90 |
+
else:
|
| 91 |
+
random.Random(self.seed).shuffle(files)
|
| 92 |
+
files = [f"hf://datasets/{self.dataset}/{f}" for f in files]
|
| 93 |
+
print(f"[stream] шардов: {len(files)} из частей {self.parts}", flush=True)
|
| 94 |
+
ds = load_dataset("parquet", data_files=files, split="train", streaming=True)
|
| 95 |
+
return ds.shuffle(buffer_size=self.buffer, seed=self.seed)
|
| 96 |
+
|
| 97 |
+
def _prepare(self, item):
|
| 98 |
+
from PIL import Image
|
| 99 |
+
import numpy as np
|
| 100 |
+
im = item["image"]
|
| 101 |
+
if im.mode != "RGB":
|
| 102 |
+
im = im.convert("RGB")
|
| 103 |
+
w, h = im.size
|
| 104 |
+
sc = self.size / min(w, h)
|
| 105 |
+
if abs(sc - 1.0) > 1e-3:
|
| 106 |
+
im = im.resize((max(self.size, round(w * sc)), max(self.size, round(h * sc))), Image.LANCZOS)
|
| 107 |
+
w, h = im.size
|
| 108 |
+
left, top = (w - self.size) // 2, (h - self.size) // 2
|
| 109 |
+
im = im.crop((left, top, left + self.size, top + self.size))
|
| 110 |
+
arr = torch.from_numpy(np.asarray(im, dtype=np.float32)).permute(2, 0, 1) / 127.5 - 1.0
|
| 111 |
+
return arr, self._caption(item)
|
| 112 |
+
|
| 113 |
+
def _caption(self, item):
|
| 114 |
+
clean = lambda x: (x or "").replace("\n", " ").strip() # noqa: E731
|
| 115 |
+
for f in self.fields:
|
| 116 |
+
t = clean(item.get(f))
|
| 117 |
+
if self.lo <= len(t) <= self.hi:
|
| 118 |
+
return t
|
| 119 |
+
return clean(item.get("caption_detail"))
|
| 120 |
+
|
| 121 |
+
def _loop(self):
|
| 122 |
+
import collections
|
| 123 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 124 |
+
with ThreadPoolExecutor(max_workers=self.workers) as pool:
|
| 125 |
+
while not self.stop:
|
| 126 |
+
try:
|
| 127 |
+
it = iter(self._items())
|
| 128 |
+
flying = collections.deque()
|
| 129 |
+
while not self.stop:
|
| 130 |
+
while len(flying) < self.workers:
|
| 131 |
+
flying.append(pool.submit(self._prepare, next(it)))
|
| 132 |
+
self.q.put(flying.popleft().result())
|
| 133 |
+
except Exception as e:
|
| 134 |
+
print(f"[stream] сбой ({type(e).__name__}: {e}), переподключение через 5 с", flush=True)
|
| 135 |
+
time.sleep(5)
|
| 136 |
+
|
| 137 |
+
def batch(self, n):
|
| 138 |
+
imgs, caps = [], []
|
| 139 |
+
while len(imgs) < n:
|
| 140 |
+
arr, cap = self.q.get()
|
| 141 |
+
imgs.append(arr); caps.append(cap)
|
| 142 |
+
return torch.stack(imgs), caps
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def hf_energy(t: torch.Tensor) -> float:
|
| 146 |
+
k = torch.tensor([[0.0, 1.0, 0.0], [1.0, -4.0, 1.0], [0.0, 1.0, 0.0]], device=t.device)
|
| 147 |
+
return F.conv2d(t.float().mean(1, keepdim=True), k.view(1, 1, 3, 3), padding=1).std().item()
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def psnr(a, b) -> float:
|
| 151 |
+
return 10 * math.log10(4 / max((a.clamp(-1, 1) - b).pow(2).mean().item(), 1e-12))
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def rotate(path: Path, keep: int, pattern: str):
|
| 155 |
+
"""Оставить `keep` последних файлов, совпадающих с pattern (по номеру шага в имени)."""
|
| 156 |
+
files = sorted(path.glob(pattern))
|
| 157 |
+
for f in files[:-keep]:
|
| 158 |
+
f.unlink()
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def main():
|
| 162 |
+
ap = argparse.ArgumentParser(description="UDiT: обучение на папке пар <имя>.jpg + <имя>.txt")
|
| 163 |
+
ap.add_argument("--data", default=str(Path.home() / "dataset" / "butterflies_256sq" / "train"))
|
| 164 |
+
ap.add_argument("--eval", default=str(Path.home() / "dataset" / "butterflies_256sq" / "eval"))
|
| 165 |
+
ap.add_argument("--limit", type=int, default=0, help="взять только первые N картинок (папка)")
|
| 166 |
+
ap.add_argument("--tfrecord", default="",
|
| 167 |
+
help="папка с TFRecord-шардами i1 или id датасета на HF; приоритетнее --hf-dataset")
|
| 168 |
+
ap.add_argument("--hf-dataset", default="LucasFang/FLUX-Reason-6M",
|
| 169 |
+
help="поток HF; пустая строка = папка --data")
|
| 170 |
+
ap.add_argument("--parts", default="Aesthetics-Part01,Aesthetics-Part02,Imaginative,Text")
|
| 171 |
+
ap.add_argument("--caption-fields", default="caption_entity,caption_composition",
|
| 172 |
+
help="короткие поля подписи по порядку предпочтения (иначе caption_detail)")
|
| 173 |
+
ap.add_argument("--caption-len", default=(40, 400), nargs=2, type=int, metavar=("MIN", "MAX"))
|
| 174 |
+
ap.add_argument("--shuffle-buffer", type=int, default=1000)
|
| 175 |
+
ap.add_argument("--prefetch", type=int, default=256)
|
| 176 |
+
ap.add_argument("--workers", type=int, default=8)
|
| 177 |
+
ap.add_argument("--images-per-epoch", type=int, default=5_890_279,
|
| 178 |
+
help="сколько картинок в эпохе потока (для пересчёта --epochs в шаги)")
|
| 179 |
+
ap.add_argument("--size", type=int, default=256, help="разрешение обучения (кадры квадратные)")
|
| 180 |
+
ap.add_argument("--text-len", type=int, default=192, help="предел длины подписи в токенах")
|
| 181 |
+
ap.add_argument("--epochs", type=float, default=None, help="бюджет в эпохах (приоритетнее --steps)")
|
| 182 |
+
ap.add_argument("--steps", type=int, default=None, help="бюджет в шагах (по умолчанию — до --target)")
|
| 183 |
+
ap.add_argument("--target", type=int, default=708000,
|
| 184 |
+
help="абсолютный шаг финиша, если не заданы --steps/--epochs")
|
| 185 |
+
ap.add_argument("--step", type=int, default=0,
|
| 186 |
+
help="стартовый шаг: от него считается остаток и расписание lr")
|
| 187 |
+
ap.add_argument("--batch", type=int, default=16)
|
| 188 |
+
ap.add_argument("--accum", type=int, default=1, help="накопление: эффективный батч = batch*accum")
|
| 189 |
+
ap.add_argument("--probe", action="store_true", help="подобрать максимальный батч под память и выйти")
|
| 190 |
+
ap.add_argument("--lr", type=float, default=4.5e-5)
|
| 191 |
+
ap.add_argument("--lr-warmup", type=int, default=0, help="шагов прогрева (0 — продолжаем с готовых весов)")
|
| 192 |
+
ap.add_argument("--lr-min", type=float, default=1e-5, help="нижняя точка cosine")
|
| 193 |
+
ap.add_argument("--lr-period", type=int, default=0, help="к какому шагу cosine доходит до --lr-min")
|
| 194 |
+
ap.add_argument("--drop-text", type=float, default=0.1, help="доля строк с пустой подписью (CFG)")
|
| 195 |
+
ap.add_argument("--drop-word", type=float, default=0.05, help="доля токенов подписи, скрытых в строке")
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
ap.add_argument("--graft", default="", help="привить блоки (стартуют тождеством), например 'e0=2,e1=2,d1=2'")
|
| 199 |
+
ap.add_argument("--cfg", type=float, default=2.5, help="CFG при показе сэмплов")
|
| 200 |
+
ap.add_argument("--sample-steps", type=int, default=16, help="шагов тела при сэмплинге")
|
| 201 |
+
ap.add_argument("--init", default=str(ROOT / "transformer"),
|
| 202 |
+
help="стартовые веса: чекпойнт или папка (по умолчанию transformer/)")
|
| 203 |
+
ap.add_argument("--resume", action="store_true", help="продолжить с <out>/last.pt")
|
| 204 |
+
ap.add_argument("--out", default=str(ROOT / "tmp" / "run"))
|
| 205 |
+
ap.add_argument("--log-every", type=int, default=50)
|
| 206 |
+
ap.add_argument("--sample-every", type=int, default=2000, help="0 = не сэмплировать")
|
| 207 |
+
ap.add_argument("--save-every", type=int, default=4000, help="0 = только в конце")
|
| 208 |
+
ap.add_argument("--keep", type=int, default=2, help="сколько чекпойнтов держать")
|
| 209 |
+
ap.add_argument("--samples-keep", type=int, default=4, help="сколько сеток сэмплов держать")
|
| 210 |
+
ap.add_argument("--ema-decay", type=float, default=0.995, help="сглаживание печатаемого лосса")
|
| 211 |
+
ap.add_argument("--ema", type=float, default=0.999, help="EMA весов для сэмплинга (0 = выкл)")
|
| 212 |
+
ap.add_argument("--seed", type=int, default=None)
|
| 213 |
+
args = ap.parse_args()
|
| 214 |
+
|
| 215 |
+
seed = args.seed if args.seed is not None else random.SystemRandom().randrange(2 ** 31)
|
| 216 |
+
torch.manual_seed(seed)
|
| 217 |
+
print(f"[seed] {seed}", flush=True)
|
| 218 |
+
|
| 219 |
+
out = Path(args.out); (out / "samples").mkdir(parents=True, exist_ok=True)
|
| 220 |
+
(out / "ckpt").mkdir(exist_ok=True)
|
| 221 |
+
log_path = out / "log.csv"
|
| 222 |
+
if not (args.resume and log_path.exists()):
|
| 223 |
+
log_path.write_text("step,mse_lat,low,high,mse_pix,switch,bright,contrast,lat_pc_min,"
|
| 224 |
+
"hf_gen,tail_z0,img_s,eta_h,sec\n")
|
| 225 |
+
|
| 226 |
+
from PIL import Image
|
| 227 |
+
import numpy as np
|
| 228 |
+
|
| 229 |
+
def load(path: Path):
|
| 230 |
+
im = Image.open(path).convert("RGB")
|
| 231 |
+
return torch.from_numpy(np.asarray(im, dtype=np.float32)).permute(2, 0, 1) / 127.5 - 1
|
| 232 |
+
|
| 233 |
+
# ---------------------------------------------------------------- данные
|
| 234 |
+
stream = None
|
| 235 |
+
if args.tfrecord:
|
| 236 |
+
stream = TFRecordStream(args.tfrecord, size=args.size, workers=args.workers,
|
| 237 |
+
prefetch=args.prefetch, seed=seed)
|
| 238 |
+
counted = count_shard_records(args.tfrecord)
|
| 239 |
+
n = counted or args.images_per_epoch # эпоха = картинки, реально лежащие в папке
|
| 240 |
+
per_epoch = max(1, n // (args.batch * args.accum))
|
| 241 |
+
steps = int(round(args.epochs * per_epoch)) if args.epochs else (args.steps or 0)
|
| 242 |
+
print(f"[data] TFRecord {args.tfrecord}: "
|
| 243 |
+
+ (f"{n} картинок в папке" if counted else f"~{n} картинок (оценка)")
|
| 244 |
+
+ f", эпоха = {per_epoch} шагов, подпись = случайная из двух самых коротких → "
|
| 245 |
+
+ (f"бюджет {steps} шагов (~{steps * args.batch / 140 / 3600:.1f} ч при 140 карт/с)"
|
| 246 |
+
if steps else f"цель шаг {args.target}"), flush=True)
|
| 247 |
+
ae = load_ae(args)
|
| 248 |
+
enc = EttinText(str(ROOT / "text_encoder"), DEV, max_len=args.text_len)
|
| 249 |
+
HS = MK = X = Z = None
|
| 250 |
+
HS_E, MK_E = enc.slices([""], pad_to_len=args.text_len)
|
| 251 |
+
HS_E = HS_E.to(torch.bfloat16)
|
| 252 |
+
elif args.hf_dataset:
|
| 253 |
+
stream = HFImageStream(args.hf_dataset, args.parts.split(","), args.size,
|
| 254 |
+
args.caption_fields.split(","), args.caption_len[0], args.caption_len[1],
|
| 255 |
+
args.shuffle_buffer, args.prefetch, args.workers, seed)
|
| 256 |
+
n = args.images_per_epoch
|
| 257 |
+
per_epoch = max(1, n // (args.batch * args.accum))
|
| 258 |
+
steps = int(round(args.epochs * per_epoch)) if args.epochs else (args.steps or 0)
|
| 259 |
+
print(f"[data] поток {args.hf_dataset} [{args.parts}], ~{n} картинок/эпоха, "
|
| 260 |
+
f"{per_epoch} шагов/эпоха → "
|
| 261 |
+
+ (f"бюджет {steps} шагов (~{steps * args.batch / 62 / 3600:.1f} ч при 62 картинках/с)"
|
| 262 |
+
if steps else f"цель шаг {args.target}"), flush=True)
|
| 263 |
+
ae = load_ae(args)
|
| 264 |
+
enc = EttinText(str(ROOT / "text_encoder"), DEV, max_len=args.text_len)
|
| 265 |
+
HS = MK = X = Z = None
|
| 266 |
+
HS_E, MK_E = enc.slices([""], pad_to_len=args.text_len)
|
| 267 |
+
HS_E = HS_E.to(torch.bfloat16)
|
| 268 |
+
else:
|
| 269 |
+
files = sorted(Path(args.data).glob("*.jpg")) + sorted(Path(args.data).glob("*.png"))
|
| 270 |
+
if args.limit:
|
| 271 |
+
files = files[: args.limit]
|
| 272 |
+
caps = [f.with_suffix(".txt").read_text().strip() for f in files]
|
| 273 |
+
ae = load_ae(args)
|
| 274 |
+
enc = EttinText(str(ROOT / "text_encoder"), DEV, max_len=args.text_len)
|
| 275 |
+
X = torch.stack([load(f) for f in files]).to(DEV)
|
| 276 |
+
with torch.no_grad():
|
| 277 |
+
Z = torch.cat([ae.encode(X[i:i + 8]) for i in range(0, len(X), 8)])
|
| 278 |
+
HS, MK = enc.slices(caps)
|
| 279 |
+
HS, MK = HS.to(torch.bfloat16), MK
|
| 280 |
+
HS_E, MK_E = enc.slices([""], pad_to_len=HS.shape[1])
|
| 281 |
+
HS_E = HS_E.to(torch.bfloat16)
|
| 282 |
+
n = len(files)
|
| 283 |
+
per_epoch = max(1, n // (args.batch * args.accum))
|
| 284 |
+
steps = int(round(args.epochs * per_epoch)) if args.epochs else (args.steps or 0)
|
| 285 |
+
print(f"[data] {n} пар, латент {tuple(Z.shape[1:])}, текст {tuple(HS.shape[1:])}, "
|
| 286 |
+
f"эпоха = {per_epoch} шагов → "
|
| 287 |
+
+ (f"бюджет {steps}" if steps else f"цель шаг {args.target}"), flush=True)
|
| 288 |
+
|
| 289 |
+
# ---------------------------------------------------------------- модель и оптимизатор
|
| 290 |
+
m = UDiT(ae, text_dim=enc.dim).to(DEV)
|
| 291 |
+
start = 0
|
| 292 |
+
|
| 293 |
+
def load_body(sd):
|
| 294 |
+
own = m.body.state_dict()
|
| 295 |
+
take = {k: v for k, v in sd.items() if k in own and own[k].shape == v.shape}
|
| 296 |
+
m.body.load_state_dict(take, strict=False)
|
| 297 |
+
return len(take)
|
| 298 |
+
|
| 299 |
+
if args.graft:
|
| 300 |
+
plan = {k.strip(): int(v) for k, v in (kv.split("=") for kv in args.graft.split(","))}
|
| 301 |
+
added = m.body.graft(plan)
|
| 302 |
+
print(f"[graft] привито {added} блоков: {plan}", flush=True)
|
| 303 |
+
|
| 304 |
+
if args.init:
|
| 305 |
+
p = Path(args.init)
|
| 306 |
+
if p.is_dir():
|
| 307 |
+
from export import load_exported
|
| 308 |
+
load_exported(m, enc, ae, p, DEV)
|
| 309 |
+
start = args.step # шаг — параметр запуска, не поле конфига
|
| 310 |
+
print(f"[init] папка {p}; старт шага {start} (--step)", flush=True)
|
| 311 |
+
else:
|
| 312 |
+
sd = torch.load(p, map_location=DEV)
|
| 313 |
+
n_take = load_body(sd["body"]); enc.mixer.load_state_dict(sd["mixer"])
|
| 314 |
+
start = int(sd.get("step", 0))
|
| 315 |
+
print(f"[init] {p}: перенесено {n_take} тензоров, шаг {start}", flush=True)
|
| 316 |
+
if args.resume and (out / "last.pt").exists():
|
| 317 |
+
sd = torch.load(out / "last.pt", map_location=DEV)
|
| 318 |
+
load_body(sd["body"]); enc.mixer.load_state_dict(sd["mixer"])
|
| 319 |
+
start = int(sd.get("step", 0))
|
| 320 |
+
print(f"[resume] {out / 'last.pt'} → шаг {start}", flush=True)
|
| 321 |
+
|
| 322 |
+
if not args.steps and not args.epochs: # бюджет по умолчанию — до абсолютного шага
|
| 323 |
+
steps = max(0, args.target - start)
|
| 324 |
+
print(f"[budget] цель шаг {args.target} от {start} → {steps} шагов", flush=True)
|
| 325 |
+
if steps <= 0:
|
| 326 |
+
print(f"[budget] цель {args.target} уже достигнута (шаг {start}) — учить нечего", flush=True)
|
| 327 |
+
if stream is not None:
|
| 328 |
+
stream.stop = True
|
| 329 |
+
import os as _os
|
| 330 |
+
_os._exit(0)
|
| 331 |
+
|
| 332 |
+
n_body = sum(p.numel() for p in m.body.parameters())
|
| 333 |
+
print(f"[model] тело {n_body/1e6:.1f}M + миксер {sum(p.numel() for p in enc.trainable())/1e6:.2f}M",
|
| 334 |
+
flush=True)
|
| 335 |
+
|
| 336 |
+
groups = [{"params": list(m.body.parameters()), "lr": args.lr},
|
| 337 |
+
{"params": enc.trainable(), "lr": args.lr}]
|
| 338 |
+
params = [p for g in groups for p in g["params"]]
|
| 339 |
+
try:
|
| 340 |
+
import bitsandbytes as bnb
|
| 341 |
+
opt = bnb.optim.AdamW8bit(groups, betas=(0.9, 0.95), weight_decay=0.0)
|
| 342 |
+
print("[opt] AdamW8bit", flush=True)
|
| 343 |
+
except Exception as e:
|
| 344 |
+
opt = torch.optim.AdamW(groups, betas=(0.9, 0.95), weight_decay=0.0)
|
| 345 |
+
print(f"[opt] AdamW fp32 (8bit недоступен: {type(e).__name__})", flush=True)
|
| 346 |
+
|
| 347 |
+
# ---- EMA весов: сглаженные копии для сэмплинга (обучение идёт на сырых)
|
| 348 |
+
ema_body = ema_mixer = None
|
| 349 |
+
if args.ema:
|
| 350 |
+
ema_body = {k: p.detach().float().clone() for k, p in m.body.named_parameters()}
|
| 351 |
+
ema_mixer = {k: p.detach().float().clone() for k, p in enc.mixer.named_parameters()}
|
| 352 |
+
print(f"[ema] decay {args.ema}, {sum(v.numel() for v in ema_body.values())/1e6:.1f}M параметров тела", flush=True)
|
| 353 |
+
|
| 354 |
+
def ema_update():
|
| 355 |
+
if ema_body is None:
|
| 356 |
+
return
|
| 357 |
+
with torch.no_grad():
|
| 358 |
+
for k, p in m.body.named_parameters():
|
| 359 |
+
ema_body[k].mul_(args.ema).add_(p.detach().float(), alpha=1 - args.ema)
|
| 360 |
+
for k, p in enc.mixer.named_parameters():
|
| 361 |
+
ema_mixer[k].mul_(args.ema).add_(p.detach().float(), alpha=1 - args.ema)
|
| 362 |
+
|
| 363 |
+
class _EmaSwap:
|
| 364 |
+
"""Подменить веса на EMA на время сэмплинга, потом вернуть сырые."""
|
| 365 |
+
def __enter__(self):
|
| 366 |
+
if ema_body is None:
|
| 367 |
+
return self
|
| 368 |
+
self.raw = {k: p.detach().cpu().clone() for k, p in m.body.named_parameters()}
|
| 369 |
+
self.raw_mx = {k: p.detach().cpu().clone() for k, p in enc.mixer.named_parameters()}
|
| 370 |
+
with torch.no_grad():
|
| 371 |
+
for k, p in m.body.named_parameters():
|
| 372 |
+
p.copy_(ema_body[k].to(p.device))
|
| 373 |
+
for k, p in enc.mixer.named_parameters():
|
| 374 |
+
p.copy_(ema_mixer[k].to(p.device))
|
| 375 |
+
return self
|
| 376 |
+
def __exit__(self, *exc):
|
| 377 |
+
if ema_body is None:
|
| 378 |
+
return False
|
| 379 |
+
with torch.no_grad():
|
| 380 |
+
for k, p in m.body.named_parameters():
|
| 381 |
+
p.copy_(self.raw[k].to(p.device))
|
| 382 |
+
for k, p in enc.mixer.named_parameters():
|
| 383 |
+
p.copy_(self.raw_mx[k].to(p.device))
|
| 384 |
+
return False
|
| 385 |
+
|
| 386 |
+
lr_min = args.lr_min if args.lr_min is not None else args.lr * 0.1
|
| 387 |
+
period = args.lr_period or steps # длина cosine в шагах, отсчёт от start
|
| 388 |
+
print(f"[lr] {args.lr:g}: прогрев {args.lr_warmup} от шага {start}, cosine до {lr_min:g} к шагу {start + period}", flush=True)
|
| 389 |
+
|
| 390 |
+
# ---------------------------------------------------------------- показ сэмплов
|
| 391 |
+
SAMPLE_PROMPTS = [
|
| 392 |
+
"A photograph of a red fox sleeping on a mossy log in a misty forest at dawn.",
|
| 393 |
+
"A city made of glass where rivers of light flow between the towers, cinematic lighting.",
|
| 394 |
+
'A neon sign reading "hello world" mounted on a brick wall at night in the rain.',
|
| 395 |
+
"An impressionist oil painting of a woman with a parasol standing by a lake.",
|
| 396 |
+
]
|
| 397 |
+
show_slices, show_x = [], [] # срезы замороженного энкодера; миксер учится — смешиваем при каждом показе
|
| 398 |
+
if stream is not None:
|
| 399 |
+
for cap in SAMPLE_PROMPTS:
|
| 400 |
+
show_slices.append(enc.slices([cap]))
|
| 401 |
+
else:
|
| 402 |
+
ev_all = sorted(Path(args.eval).glob("*.jpg")) + sorted(Path(args.eval).glob("*.png"))
|
| 403 |
+
ev_cls = {}
|
| 404 |
+
for f in ev_all:
|
| 405 |
+
ev_cls.setdefault(f.stem.split("_")[0], f)
|
| 406 |
+
show = [ev_cls[k] for k in list(ev_cls)[:4]]
|
| 407 |
+
if len(show) < 4: # один класс в eval (бабочки) — берём первые 4 файла как есть
|
| 408 |
+
show = ev_all[:4]
|
| 409 |
+
for f in show:
|
| 410 |
+
hs, mk = enc.slices([f.with_suffix(".txt").read_text().strip()], pad_to_len=HS.shape[1])
|
| 411 |
+
show_slices.append((hs, mk))
|
| 412 |
+
show_x.append(load(f)[None].to(DEV))
|
| 413 |
+
|
| 414 |
+
def sample_grid(step):
|
| 415 |
+
tiles, hfs, ys = [], [], []
|
| 416 |
+
# веса уже подменены на EMA в вызывающем месте
|
| 417 |
+
for hs, mk in show_slices:
|
| 418 |
+
y = m.sample(enc.mix(hs.float()), mk, lat_hw[0], lat_hw[1], steps=args.sample_steps,
|
| 419 |
+
tail_steps=0, cfg=args.cfg,
|
| 420 |
+
null_text=enc.mix(HS_E.float()), null_mask=MK_E,
|
| 421 |
+
generator=torch.Generator(DEV).manual_seed(0))
|
| 422 |
+
tiles.append(Image.fromarray(((y[0].clamp(-1, 1) + 1) * 127.5).byte()
|
| 423 |
+
.permute(1, 2, 0).cpu().numpy()))
|
| 424 |
+
hfs.append(hf_energy(y)); ys.append(y)
|
| 425 |
+
if show_x:
|
| 426 |
+
ae_rec = ae.decode_raw(ae.encode(show_x[0]) * ae.latent_std + ae.latent_mean)
|
| 427 |
+
hf_ae = hf_energy(ae_rec)
|
| 428 |
+
else:
|
| 429 |
+
hf_ae = float("nan")
|
| 430 |
+
W, H = tiles[0].size
|
| 431 |
+
canvas = Image.new("RGB", (W * len(tiles), H), "white")
|
| 432 |
+
for i, t in enumerate(tiles):
|
| 433 |
+
canvas.paste(t, (i * W, 0))
|
| 434 |
+
canvas.save(out / "samples" / "latest.png")
|
| 435 |
+
canvas.save(out / "samples" / f"step_{step:07d}.png")
|
| 436 |
+
rotate(out / "samples", args.samples_keep, "step_*.png")
|
| 437 |
+
bright = float(torch.stack(ys).mean())
|
| 438 |
+
contrast = float(torch.stack(ys).std())
|
| 439 |
+
return hfs[0], hf_ae, canvas, bright, contrast
|
| 440 |
+
|
| 441 |
+
def save(step):
|
| 442 |
+
sd = {"body": {k: v.cpu() for k, v in m.body.state_dict().items()},
|
| 443 |
+
"body_ema": ({k: v.cpu() for k, v in ema_body.items()} if ema_body else None),
|
| 444 |
+
"mixer_ema": ({k: v.cpu() for k, v in ema_mixer.items()} if ema_mixer else None),
|
| 445 |
+
"mixer": {k: v.cpu() for k, v in enc.mixer.state_dict().items()},
|
| 446 |
+
"step": step, "args": vars(args), "seed": seed}
|
| 447 |
+
torch.save(sd, out / "last.pt")
|
| 448 |
+
torch.save(sd, out / "ckpt" / f"step_{step:07d}.pt")
|
| 449 |
+
rotate(out / "ckpt", args.keep, "step_*.pt")
|
| 450 |
+
|
| 451 |
+
# ---------------------------------------------------------------- цикл
|
| 452 |
+
lat_hw = (args.size // 8, args.size // 8)
|
| 453 |
+
if args.probe:
|
| 454 |
+
best = args.batch
|
| 455 |
+
for bs in (args.batch, args.batch * 2, args.batch * 4, args.batch * 8):
|
| 456 |
+
try:
|
| 457 |
+
z = torch.randn(bs, 32, lat_hw[0], lat_hw[1], device=DEV)
|
| 458 |
+
t = torch.rand(bs, device=DEV)
|
| 459 |
+
if stream is not None: # в потоке текста нет — берём фиктивный
|
| 460 |
+
hs_probe, mk_probe = enc.slices(["probe caption"] * bs)
|
| 461 |
+
else:
|
| 462 |
+
hs_probe, mk_probe = HS[:bs], MK[:bs]
|
| 463 |
+
with torch.autocast(DEV, torch.bfloat16, enabled=DEV == "cuda"):
|
| 464 |
+
m.body(z, t, enc.mix(hs_probe.float()), mk_probe).float().pow(2).mean().backward()
|
| 465 |
+
best = bs
|
| 466 |
+
m.zero_grad(set_to_none=True)
|
| 467 |
+
except torch.OutOfMemoryError:
|
| 468 |
+
m.zero_grad(set_to_none=True); torch.cuda.empty_cache(); break
|
| 469 |
+
print(f"[probe] максимальный батч ≈ {best} (пик {torch.cuda.max_memory_allocated()/1e9:.1f} ГБ)",
|
| 470 |
+
flush=True)
|
| 471 |
+
if stream is not None:
|
| 472 |
+
stream.stop = True
|
| 473 |
+
import os as _os
|
| 474 |
+
_os._exit(0)
|
| 475 |
+
|
| 476 |
+
if stream is not None:
|
| 477 |
+
def get_batch():
|
| 478 |
+
x, caps = stream.batch(args.batch)
|
| 479 |
+
x = x.to(DEV)
|
| 480 |
+
with torch.no_grad():
|
| 481 |
+
z = ae.encode(x)
|
| 482 |
+
hs, mk = enc.slices(caps)
|
| 483 |
+
return x, z, hs.to(torch.bfloat16), mk
|
| 484 |
+
else:
|
| 485 |
+
def get_batch():
|
| 486 |
+
idx = torch.randint(0, n, (args.batch,), device=DEV)
|
| 487 |
+
return X[idx], Z[idx], HS[idx], MK[idx]
|
| 488 |
+
|
| 489 |
+
hf_gen, hf_ae, _, bright0, contrast0 = sample_grid(start)
|
| 490 |
+
print(f"[sample] шаг {start}: яркость {bright0:+.3f}, контраст {contrast0:.3f}, hf {hf_gen:.3f}", flush=True)
|
| 491 |
+
|
| 492 |
+
t0 = time.perf_counter(); prev_el = t0
|
| 493 |
+
ema = {}
|
| 494 |
+
run = {"lat": 0.0, "low": 0.0, "high": 0.0, "pix": 0.0, "n_low": 0, "n_high": 0, "n": 0}
|
| 495 |
+
skips = 0
|
| 496 |
+
for step in range(start + 1, start + steps + 1):
|
| 497 |
+
opt.zero_grad(set_to_none=True)
|
| 498 |
+
ok = 0
|
| 499 |
+
for _ in range(args.accum):
|
| 500 |
+
x0, z0, hs, mk = get_batch()
|
| 501 |
+
# дропаут подписи (CFG) и дропаут слов внутри подписи
|
| 502 |
+
if args.drop_text:
|
| 503 |
+
drop = torch.rand(args.batch, device=DEV) < args.drop_text
|
| 504 |
+
if drop.any(): # пустая подпись обрезается под длину батча
|
| 505 |
+
L = hs.shape[1]
|
| 506 |
+
hs = torch.where(drop[:, None, None, None], HS_E[:, :L].expand_as(hs), hs)
|
| 507 |
+
mk = torch.where(drop[:, None], MK_E[:, :L].expand_as(mk), mk)
|
| 508 |
+
if args.drop_word:
|
| 509 |
+
word = (torch.rand(mk.shape, device=DEV) < args.drop_word) & mk
|
| 510 |
+
hs = hs.masked_fill(word[:, :, None, None], 0)
|
| 511 |
+
mk = mk & ~word
|
| 512 |
+
with torch.autocast(DEV, torch.bfloat16, enabled=DEV == "cuda"):
|
| 513 |
+
txt = enc.mix(hs.float())
|
| 514 |
+
tb = torch.rand(args.batch, device=DEV)
|
| 515 |
+
eb = torch.randn_like(z0)
|
| 516 |
+
zt = (1 - tb[:, None, None, None]) * z0 + tb[:, None, None, None] * eb
|
| 517 |
+
z_hat = m.body(zt, tb, txt, mk)
|
| 518 |
+
per_elem = (z_hat.float() - z0.float()).pow(2).mean(dim=(1, 2, 3))
|
| 519 |
+
low, high = tb < 0.3, tb > 0.7
|
| 520 |
+
loss_l = per_elem.mean()
|
| 521 |
+
total = loss_l
|
| 522 |
+
if not torch.isfinite(total):
|
| 523 |
+
skips += 1
|
| 524 |
+
continue
|
| 525 |
+
(total / args.accum).backward()
|
| 526 |
+
ok += 1
|
| 527 |
+
run["lat"] += loss_l.item()
|
| 528 |
+
if low.any():
|
| 529 |
+
run["low"] += per_elem[low].mean().item(); run["n_low"] += 1
|
| 530 |
+
if high.any():
|
| 531 |
+
run["high"] += per_elem[high].mean().item(); run["n_high"] += 1
|
| 532 |
+
if not ok:
|
| 533 |
+
print(f" ! шаг {step}: не-конечный лосс, пропуск (всего {skips})", flush=True)
|
| 534 |
+
continue
|
| 535 |
+
run["n"] += 1
|
| 536 |
+
torch.nn.utils.clip_grad_norm_(params, 1.0)
|
| 537 |
+
lr = lr_at(step, args.lr, lr_min, args.lr_warmup, period, start)
|
| 538 |
+
for g, base in zip(opt.param_groups, (args.lr, args.lr)):
|
| 539 |
+
g["lr"] = lr * (base / args.lr)
|
| 540 |
+
opt.step()
|
| 541 |
+
ema_update()
|
| 542 |
+
|
| 543 |
+
if step % args.log_every == 0:
|
| 544 |
+
d = args.ema_decay
|
| 545 |
+
cur = {"lat": run["lat"] / run["n"],
|
| 546 |
+
"low": run["low"] / max(run["n_low"], 1), "high": run["high"] / max(run["n_high"], 1)}
|
| 547 |
+
for k, v in cur.items():
|
| 548 |
+
ema[k] = v if k not in ema else d * ema[k] + (1 - d) * v
|
| 549 |
+
el = time.perf_counter() - t0; inc = el - prev_el; prev_el = el
|
| 550 |
+
ms = inc / max(run["n"], 1) * 1e3
|
| 551 |
+
avg = el / max(step - start, 1) # с/шаг с начала прогона (включая простои)
|
| 552 |
+
left = (start + steps - step) * avg
|
| 553 |
+
eta = time.strftime("%d.%m %H:%M", time.gmtime(time.time() + left))
|
| 554 |
+
print(f" {step:6d} | mse_lat {cur['lat']:.4f} (ema {ema['lat']:.4f})"
|
| 555 |
+
f" | lr {lr:.1e} | {args.batch * args.accum / (ms / 1e3):.1f} карт/с ({ms:.0f} мс/шаг)"
|
| 556 |
+
f" | осталось {left / 3600:.1f} ч (финиш {eta} UTC)"
|
| 557 |
+
+ (f" | skip {skips}" if skips else ""), flush=True)
|
| 558 |
+
run = {"lat": 0.0, "low": 0.0, "high": 0.0, "pix": 0.0, "n_low": 0, "n_high": 0, "n": 0}
|
| 559 |
+
|
| 560 |
+
if args.sample_every and step % args.sample_every == 0:
|
| 561 |
+
ema_ctx = _EmaSwap().__enter__()
|
| 562 |
+
if DEV == "cuda":
|
| 563 |
+
torch.cuda.empty_cache() # сэмплинг идёт поверх фрагментированного кэша — чистим заранее
|
| 564 |
+
zs = []
|
| 565 |
+
lat_pc_min = float("nan")
|
| 566 |
+
for hs, mk in show_slices[:2]:
|
| 567 |
+
txt = enc.mix(hs.float())
|
| 568 |
+
z = torch.randn(1, 32, lat_hw[0], lat_hw[1], device=DEV,
|
| 569 |
+
generator=torch.Generator(DEV).manual_seed(7))
|
| 570 |
+
with torch.no_grad(), torch.autocast(DEV, torch.bfloat16, enabled=DEV == "cuda"):
|
| 571 |
+
for i in range(args.sample_steps):
|
| 572 |
+
t = torch.full((1,), 1.0 - i / args.sample_steps, device=DEV)
|
| 573 |
+
o = m.body(z, t, txt, mk).float()
|
| 574 |
+
ou = m.body(z, t, enc.mix(HS_E.float()), MK_E).float()
|
| 575 |
+
o = ou + args.cfg * (o - ou)
|
| 576 |
+
z = z - (z - o) / t.clamp(min=1e-2)[:, None, None, None] / args.sample_steps
|
| 577 |
+
zs.append(z)
|
| 578 |
+
switch = (((zs[0] - zs[1]).norm() / zs[0].norm()).item() if len(zs) > 1 else float("nan"))
|
| 579 |
+
if zs:
|
| 580 |
+
lat_pc_min = float(zs[0].std(dim=(0, 2, 3)).min()) # сторож сжатия дисперсии латента
|
| 581 |
+
hfg, hfa, canvas, bright, contrast = sample_grid(step)
|
| 582 |
+
tail_psnr = float("nan") # метрика хвоста больше не существует
|
| 583 |
+
el = time.perf_counter() - t0
|
| 584 |
+
avg = el / max(step - start, 1); left = (start + steps - step) * avg
|
| 585 |
+
print(f" ↳ {step} | hf {hfg:.3f} | латент std {lat_pc_min:.2f} | switch {switch:.3f}"
|
| 586 |
+
f" | {time.strftime('%H:%M', time.gmtime())} UTC", flush=True)
|
| 587 |
+
with open(log_path, "a") as fh:
|
| 588 |
+
fh.write(f"{step},{ema.get('lat', 0):.5f},{ema.get('low', 0):.5f},{ema.get('high', 0):.5f},"
|
| 589 |
+
f"0.0000,{switch:.4f},{bright:.4f},{contrast:.4f},"
|
| 590 |
+
f"{lat_pc_min:.4f},{hfg:.4f},{tail_psnr:.2f},"
|
| 591 |
+
f"{args.batch * args.accum / (el / max(step - start, 1)):.1f},{left / 3600:.2f},{el:.1f}\n")
|
| 592 |
+
ema_ctx.__exit__(None, None, None) # вернуть сырые веса
|
| 593 |
+
if args.save_every and step % args.save_every == 0:
|
| 594 |
+
save(step)
|
| 595 |
+
save(start + steps)
|
| 596 |
+
if stream is not None:
|
| 597 |
+
stream.stop = True # фоновые потоки не должны переживать интерпретатор
|
| 598 |
+
print("[done]", flush=True)
|
| 599 |
+
import os as _os
|
| 600 |
+
_os._exit(0) # обойти шумную финализацию streaming-генераторов
|
| 601 |
+
|
| 602 |
+
|
| 603 |
+
if __name__ == "__main__":
|
| 604 |
+
main()
|
src/vae_adapter.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""VAE проекта: AsymmetricAutoencoderKL (энкодер f8, декодер f16 → выход 2×).
|
| 2 |
+
|
| 3 |
+
Что адаптер прячет от остального кода:
|
| 4 |
+
* латент нормируется своими `latents_mean`/`latents_std` из vae/config.json (≈ -0.04 / 1.58):
|
| 5 |
+
encode(x) → нормированный латент, `to_raw()` в модели возвращает сырой;
|
| 6 |
+
* декодер асимметричный: энкодер f8, декодер f16, то есть латент 32×32 отдаёт картинку 512²
|
| 7 |
+
(вдвое больше входа) — это и есть смысл этого VAE;
|
| 8 |
+
* декодирование однопроходное — обучение идёт как обычная латентная диффузия (только MSE по латенту).
|
| 9 |
+
|
| 10 |
+
API, которого ждут train.py/sample.py: `encode`, `decode_raw`, `latent_mean`, `latent_std`,
|
| 11 |
+
`config.latent_channels`, `config.patch_size`, `compute_dtype`, `device`, `.eval()`, `.to(device)`.
|
| 12 |
+
"""
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
import torch.nn as nn
|
| 17 |
+
|
| 18 |
+
PATCH = 8 # энкодер сжимает в 8 раз; декодер потом растягивает в 16 → 2× на выходе
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class VAE(nn.Module):
|
| 22 |
+
def __init__(self, vae, device: str = "cuda"):
|
| 23 |
+
super().__init__()
|
| 24 |
+
self.vae = vae
|
| 25 |
+
self.device_name = device
|
| 26 |
+
self.compute_dtype = torch.float32
|
| 27 |
+
cfg = vae.config
|
| 28 |
+
mean = torch.tensor(getattr(cfg, "latents_mean"), dtype=torch.float32)
|
| 29 |
+
std = torch.tensor(getattr(cfg, "latents_std"), dtype=torch.float32)
|
| 30 |
+
self.register_buffer("latent_mean", mean.view(1, -1, 1, 1))
|
| 31 |
+
self.register_buffer("latent_std", std.view(1, -1, 1, 1))
|
| 32 |
+
self.config = type("Cfg", (), {"latent_channels": int(cfg.latent_channels),
|
| 33 |
+
"patch_size": PATCH})()
|
| 34 |
+
|
| 35 |
+
@classmethod
|
| 36 |
+
def from_pretrained(cls, path, device: str = "cuda"):
|
| 37 |
+
from diffusers import AsymmetricAutoencoderKL
|
| 38 |
+
vae = AsymmetricAutoencoderKL.from_pretrained(str(path), torch_dtype=torch.float32)
|
| 39 |
+
vae = vae.to(device).eval()
|
| 40 |
+
vae.requires_grad_(False)
|
| 41 |
+
return cls(vae, device).to(device).eval()
|
| 42 |
+
|
| 43 |
+
@property
|
| 44 |
+
def device(self):
|
| 45 |
+
return self.latent_mean.device
|
| 46 |
+
|
| 47 |
+
def train(self, mode: bool = True):
|
| 48 |
+
super().train(mode)
|
| 49 |
+
self.vae.eval() # VAE заморожен всегда
|
| 50 |
+
return self
|
| 51 |
+
|
| 52 |
+
def encode(self, x: torch.Tensor) -> torch.Tensor:
|
| 53 |
+
"""x в -1..1 → нормированный латент (B, 32, H/8, W/8)."""
|
| 54 |
+
with torch.no_grad():
|
| 55 |
+
raw = self.vae.encode(x.to(self.compute_dtype)).latent_dist.mode()
|
| 56 |
+
return ((raw - self.latent_mean) / self.latent_std).to(torch.float32)
|
| 57 |
+
|
| 58 |
+
def decode_raw(self, z_raw: torch.Tensor, steps: int = 0, generator=None) -> torch.Tensor:
|
| 59 |
+
"""Сырой латент → картинка в -1..1. `steps`/`generator` — для совместимости с нашим API."""
|
| 60 |
+
with torch.no_grad():
|
| 61 |
+
return self.vae.decode(z_raw.to(self.compute_dtype), return_dict=False)[0].clamp(-1, 1)
|
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,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "UDiT",
|
| 3 |
+
"format": "udm/v1",
|
| 4 |
+
"storage_dtype": "float16",
|
| 5 |
+
"latent_channels": 32,
|
| 6 |
+
"text_dim": 768,
|
| 7 |
+
"body": {
|
| 8 |
+
"kind": "пирамидальный U: ширина растёт к низу",
|
| 9 |
+
"widths": [
|
| 10 |
+
192,
|
| 11 |
+
512,
|
| 12 |
+
1024,
|
| 13 |
+
1536
|
| 14 |
+
],
|
| 15 |
+
"heads": [
|
| 16 |
+
6,
|
| 17 |
+
8,
|
| 18 |
+
8,
|
| 19 |
+
8
|
| 20 |
+
],
|
| 21 |
+
"blocks": {
|
| 22 |
+
"e0": 6,
|
| 23 |
+
"e0x": 3,
|
| 24 |
+
"d0x": 3,
|
| 25 |
+
"e1": 5,
|
| 26 |
+
"e2": 5,
|
| 27 |
+
"e3": 4,
|
| 28 |
+
"d2": 4,
|
| 29 |
+
"d1": 4,
|
| 30 |
+
"d0": 4
|
| 31 |
+
},
|
| 32 |
+
"levels": [
|
| 33 |
+
{
|
| 34 |
+
"level": 0,
|
| 35 |
+
"tokens": "÷1",
|
| 36 |
+
"width": 192,
|
| 37 |
+
"blocks": "conv 6 enc / 4 dec + 2 self-attn на сторону"
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"level": 1,
|
| 41 |
+
"tokens": "÷2",
|
| 42 |
+
"width": 512,
|
| 43 |
+
"blocks": "attn 5 enc / 4 dec"
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"level": 2,
|
| 47 |
+
"tokens": "÷4",
|
| 48 |
+
"width": 1024,
|
| 49 |
+
"blocks": "attn 5 enc / 4 dec"
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"level": 3,
|
| 53 |
+
"tokens": "÷8",
|
| 54 |
+
"width": 1536,
|
| 55 |
+
"blocks": "attn 4 (боттлнек)"
|
| 56 |
+
}
|
| 57 |
+
],
|
| 58 |
+
"skips": "аддитивные (SD-стиль): e0/e1/e2 прибавляются в декодере",
|
| 59 |
+
"down": "mean-pool 2x + Linear",
|
| 60 |
+
"up": "Linear + nearest 2x",
|
| 61 |
+
"positions": {
|
| 62 |
+
"rope": {
|
| 63 |
+
"kind": "2d",
|
| 64 |
+
"base": 100,
|
| 65 |
+
"phase": "в латентных пикселях на каждом уровне"
|
| 66 |
+
},
|
| 67 |
+
"absolute": {
|
| 68 |
+
"kind": "2d sincos",
|
| 69 |
+
"dim": 64,
|
| 70 |
+
"into": "zero-init Linear(64 -> width) к выходу stem, в паре с RoPE"
|
| 71 |
+
}
|
| 72 |
+
},
|
| 73 |
+
"text": "кросс-внимание на всех уровнях",
|
| 74 |
+
"parametrization": "x0 латента",
|
| 75 |
+
"params": 240.04
|
| 76 |
+
}
|
| 77 |
+
}
|
transformer/diffusion_pytorch_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0e046b9aaede687fd792d7727ce35ea4287b0c6a8894986f61c5ce370643392d
|
| 3 |
+
size 500218686
|
vae/config.json
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "AsymmetricAutoencoderKL",
|
| 3 |
+
"_diffusers_version": "0.37.0",
|
| 4 |
+
"_name_or_path": "asymmetric_vae",
|
| 5 |
+
"act_fn": "silu",
|
| 6 |
+
"down_block_out_channels": [
|
| 7 |
+
128,
|
| 8 |
+
256,
|
| 9 |
+
512,
|
| 10 |
+
512
|
| 11 |
+
],
|
| 12 |
+
"down_block_types": [
|
| 13 |
+
"DownEncoderBlock2D",
|
| 14 |
+
"DownEncoderBlock2D",
|
| 15 |
+
"DownEncoderBlock2D",
|
| 16 |
+
"DownEncoderBlock2D"
|
| 17 |
+
],
|
| 18 |
+
"in_channels": 3,
|
| 19 |
+
"latent_channels": 32,
|
| 20 |
+
"latents_mean": [
|
| 21 |
+
-0.03542253375053406,
|
| 22 |
+
0.20086465775966644,
|
| 23 |
+
-0.016413161531090736,
|
| 24 |
+
-0.0956302210688591,
|
| 25 |
+
-0.2672063112258911,
|
| 26 |
+
0.2609933018684387,
|
| 27 |
+
-0.07806991040706635,
|
| 28 |
+
-0.48407721519470215,
|
| 29 |
+
0.21844269335269928,
|
| 30 |
+
-0.1122383326292038,
|
| 31 |
+
0.27197545766830444,
|
| 32 |
+
-0.18958772718906403,
|
| 33 |
+
0.18776826560497284,
|
| 34 |
+
0.0987580344080925,
|
| 35 |
+
0.2837068736553192,
|
| 36 |
+
-0.4486690163612366,
|
| 37 |
+
0.4816776514053345,
|
| 38 |
+
0.02947971224784851,
|
| 39 |
+
-0.1337375044822693,
|
| 40 |
+
-0.39750921726226807,
|
| 41 |
+
-0.08513020724058151,
|
| 42 |
+
-0.054023586213588715,
|
| 43 |
+
-0.3943594992160797,
|
| 44 |
+
0.23918119072914124,
|
| 45 |
+
-0.12466679513454437,
|
| 46 |
+
0.09935147315263748,
|
| 47 |
+
0.31858691573143005,
|
| 48 |
+
0.48585832118988037,
|
| 49 |
+
-0.6416525840759277,
|
| 50 |
+
-0.15164820849895477,
|
| 51 |
+
-0.4693508744239807,
|
| 52 |
+
-0.13071806728839874
|
| 53 |
+
],
|
| 54 |
+
"latents_std": [
|
| 55 |
+
1.5792087316513062,
|
| 56 |
+
1.5769503116607666,
|
| 57 |
+
1.5864241123199463,
|
| 58 |
+
1.6454921960830688,
|
| 59 |
+
1.5336694717407227,
|
| 60 |
+
1.5587652921676636,
|
| 61 |
+
1.5838669538497925,
|
| 62 |
+
1.5659377574920654,
|
| 63 |
+
1.6860467195510864,
|
| 64 |
+
1.5192310810089111,
|
| 65 |
+
1.573639988899231,
|
| 66 |
+
1.5953549146652222,
|
| 67 |
+
1.5271092653274536,
|
| 68 |
+
1.6246271133422852,
|
| 69 |
+
1.7054023742675781,
|
| 70 |
+
1.607722282409668,
|
| 71 |
+
1.558642864227295,
|
| 72 |
+
1.5824549198150635,
|
| 73 |
+
1.6202995777130127,
|
| 74 |
+
1.6206320524215698,
|
| 75 |
+
1.6379750967025757,
|
| 76 |
+
1.6527063846588135,
|
| 77 |
+
1.498811960220337,
|
| 78 |
+
1.5706247091293335,
|
| 79 |
+
1.5854856967926025,
|
| 80 |
+
1.4828169345855713,
|
| 81 |
+
1.5693111419677734,
|
| 82 |
+
1.692481517791748,
|
| 83 |
+
1.6409776210784912,
|
| 84 |
+
1.6216280460357666,
|
| 85 |
+
1.6087706089019775,
|
| 86 |
+
1.5776633024215698
|
| 87 |
+
],
|
| 88 |
+
"layers_per_down_block": 2,
|
| 89 |
+
"layers_per_up_block": 2,
|
| 90 |
+
"norm_num_groups": 32,
|
| 91 |
+
"out_channels": 3,
|
| 92 |
+
"sample_size": 32,
|
| 93 |
+
"scaling_factor": 1.0,
|
| 94 |
+
"up_block_out_channels": [
|
| 95 |
+
128,
|
| 96 |
+
128,
|
| 97 |
+
256,
|
| 98 |
+
512,
|
| 99 |
+
512
|
| 100 |
+
],
|
| 101 |
+
"up_block_types": [
|
| 102 |
+
"UpDecoderBlock2D",
|
| 103 |
+
"UpDecoderBlock2D",
|
| 104 |
+
"UpDecoderBlock2D",
|
| 105 |
+
"UpDecoderBlock2D",
|
| 106 |
+
"UpDecoderBlock2D"
|
| 107 |
+
]
|
| 108 |
+
}
|
vae/diffusion_pytorch_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b11f911599c8f066158206726e7adf8882649e15e6275868f0f435f3b914c00a
|
| 3 |
+
size 191765430
|