darask0 commited on
Commit
47ea602
·
verified ·
1 Parent(s): 7c6afd7

rapid-moss: MOSS-Image-SFT 4-step adapter (data-space DMD + DMAD, it 500 EMA), code, logs, eval

Browse files
.gitattributes CHANGED
@@ -33,3 +33,10 @@ saved_model/**/* 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
 
 
 
 
 
 
 
 
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
36
+ images/compare-1.jpg filter=lfs diff=lfs merge=lfs -text
37
+ images/compare-2.jpg filter=lfs diff=lfs merge=lfs -text
38
+ images/compare-3.jpg filter=lfs diff=lfs merge=lfs -text
39
+ images/training/000300.jpg filter=lfs diff=lfs merge=lfs -text
40
+ images/training/000500.jpg filter=lfs diff=lfs merge=lfs -text
41
+ images/training/000800.jpg filter=lfs diff=lfs merge=lfs -text
42
+ images/training/init.jpg filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model:
4
+ - OpenMOSS-Team/MOSS-Image-SFT
5
+ - lodestones/MOSS-Image-SFT-8-steps-dmd
6
+ library_name: diffusers
7
+ pipeline_tag: text-to-image
8
+ language:
9
+ - ja
10
+ - en
11
+ tags:
12
+ - moss-image
13
+ - lora
14
+ - distillation
15
+ - few-step
16
+ - dmad
17
+ - dmd
18
+ ---
19
+
20
+ # rapid-moss: MOSS-Image-SFT 4-step adapter (data-space DMD → DMAD)
21
+
22
+ **English summary.** A 4-step, CFG-free adapter for [OpenMOSS-Team/MOSS-Image-SFT](https://huggingface.co/OpenMOSS-Team/MOSS-Image-SFT)
23
+ (50 steps × 2 forwards with CFG 3). It continues [lodestones/MOSS-Image-SFT-8-steps-dmd](https://huggingface.co/lodestones/MOSS-Image-SFT-8-steps-dmd)
24
+ (data-space DMD, arXiv 2610.12102) restricted to 4 iterations, trained with DMAD (arXiv 2610.02188): a LoRA critic on the
25
+ frozen teacher separates teacher samples from student samples, and the student minimises the negative critic logit.
26
+ DiT time per 1024² image on one H100: 16.2 s (teacher) → 0.81 s. Compared with the lodestones checkpoint run for 4 steps,
27
+ images have more detail and livelier composition, with some regressions (see Results). Same file format and sampler as the
28
+ lodestones checkpoint; `inference.py` runs it with the release pipeline. Training code is in `code/`.
29
+
30
+ ---
31
+
32
+ MOSS-Image-SFT(50 ステップ・CFG 3 で 1 枚 100 回の DiT 計算)を、**4 回の計算・CFG なし**で画像を出せるようにする高速化アダプタです。
33
+ lodestones の 8 ステップ版(data-space DMD、update 2600)を出発点に、4 ステップに絞って DMAD で追加学習しました。
34
+
35
+ | | DiT 時間 / 1024² 1 枚(H100) | DiT の計算回数 |
36
+ |---|---:|---:|
37
+ | MOSS-Image-SFT(50 step、CFG 3、負側 block 22 skip) | 16.2 s | 100 |
38
+ | lodestones 8 step | 1.54 s | 8 |
39
+ | **このアダプタ(4 step)** | **0.81 s** | **4** |
40
+
41
+ (DiT のみの計測。テキストエンコーダと VAE は含まない。実装は `code/rapid_moss/model.py`。)
42
+
43
+ ## 使い方
44
+
45
+ ```bash
46
+ pip install "diffusers>=0.38" "transformers>=5.3" accelerate safetensors huggingface_hub pillow
47
+ python inference.py --prompt "an anime girl in a yellow raincoat at a rainy bus stop at night" --width 1024 --height 1024 --out out.png
48
+ ```
49
+
50
+ ファイルは lodestones と同じ形式です。
51
+
52
+ | ファイル | 中身 |
53
+ |---|---|
54
+ | `lora.safetensors` | fp32 LoRA、rank 128 / alpha 128、全 42 ブロックの `proj.{q,k,v,o,ffn_up_gate,ffn_down}`。kohya 形式のキー(`diffusion_model.<module>.lora_down.weight` / `lora_up.weight` / `alpha`) |
55
+ | `time_embedding.safetensors` | fp32 の `time_embedding.*` と `shared_block_modulation.bias` / `video_head_modulation.bias` の置き換え |
56
+ | `dataspace.safetensors` | `latent_mix.Q` [128, 128] と `step_codes` [1000, 968, 926, 871] |
57
+
58
+ サンプラー(ノイズの再注入もタイムステップのスケジュールもなく、同じ z を使い続ける):
59
+
60
+ ```python
61
+ x0 = torch.zeros_like(z) # z: fp32 noise [1, 128, H/16, W/16]
62
+ for r in range(4): # 1〜3 回で止めても画像になる(各段階を学習している)
63
+ x = z + torch.einsum("oc,bchw->bohw", Q, x0)
64
+ x0 = z - transformer(x, codes[r], prompt_embeds, prompt_mask).sample.float()
65
+ # x0 を release の VAE でデコード(BN の逆正規化 + 2x2 unpatchify)
66
+ ```
67
+
68
+ - base の transformer は bf16 のまま、LoRA は**マージせずに** fp32 で足してください。差分が bf16 の丸め幅より小さく、bf16 の重みにマージすると効果がほぼ消えます。
69
+ - time path の 3 モジュールは fp32 のまま使います。
70
+ - CFG とネガティブプロンプトは使いません。プロンプトの形式(自然文・タグ列・JSON レイアウト、日本語の文字指定)は MOSS-Image と同じです。
71
+
72
+ ## 結果
73
+
74
+ 左から teacher(50 step)/ lodestones 8 step 版を 4 step で使ったもの / このアダプタ(4 step)。同じ seed、評価用 17 プロンプト(`eval/eval_prompts.jsonl`、学習には未使用)。
75
+
76
+ ![compare-1](images/compare-1.jpg)
77
+ ![compare-2](images/compare-2.jpg)
78
+ ![compare-3](images/compare-3.jpg)
79
+
80
+ lodestones の 4 step と比べた所見(目視のみ。数値指標では評価していません):
81
+
82
+ - 良くなった点
83
+ - 背景や小物の描き込みが増え、構図に動きが出た(teacher の雰囲気に近づいた)
84
+ - 縦書きポスターで「夏」が二重になる崩れが直った
85
+ - 付箋の 3 行、看板、吹き出しの日本語は正確なまま
86
+ - 悪くなった点
87
+ - ライトノベル表紙に頼んでいない英字が増えた
88
+ - 2 コマ漫画(JSON レイアウト)の下のコマに 2 人目が出た
89
+ - 暖簾などの小さな文字の崩れが増えた
90
+ - 変わらない点
91
+ - 厚塗りの侍の絵に頼んでいない題字が入る(lodestones から引き継いだ癖。teacher は入れない)
92
+ - 白黒漫画の吹き出しが意味の通らない文字になる
93
+
94
+ 結論として、細部は良くなったが一長一短です。
95
+
96
+ ### 学習の経過(EMA、4 step、同じ seed)
97
+
98
+ | init(lodestones) | 300 it | 500 it(公開版) | 800 it |
99
+ |---|---|---|---|
100
+ | ![](images/training/init.jpg) | ![](images/training/000300.jpg) | ![](images/training/000500.jpg) | ![](images/training/000800.jpg) |
101
+
102
+ - 300 it では、桜の女子高生の絵が顔の寄せ集めになる崩れが一度出ました。500 it では元に戻っています。
103
+ - 500 → 800 it ではプロンプトへの従い方(`1girl, solo` なのに 2 人)と文字が崩れていきました。841〜850 it で NaN になり、学習を止めました。teacher サンプルに非有限値はなく、原因は特定できていません。現在のコードは非有限の勾配で更新を飛ばします。
104
+ - **公開版は 500 it の EMA** です。
105
+
106
+ ## 手法
107
+
108
+ ### 生成器(data-space iteration、arXiv 2610.12102)
109
+
110
+ lodestones の形式そのままです。`x = z + Q·x0`, `v = net(x, code_r)`, `x0 ← z − v`。
111
+
112
+ - 学習対象は LoRA(rank 128)、time path(fp32)、Q の 3 つです。
113
+ - lodestones update 2600 から初期化し、step codes は先頭 4 つを使います。
114
+ - 反復番号 r は 0〜3 を順番に回します。r 回目までは勾配なしで進め、最後の 1 回だけ勾配を流します。
115
+
116
+ ### 識別器と損失(DMAD、arXiv 2610.02188)
117
+
118
+ - **critic の構成:** 凍結した teacher(MOSS-Image-SFT の bf16)に、block 21〜41 の LoRA(rank 128)を付けます。block 41 の画像トークンにヘッドを付け、1 枚につき 1 つのロジットを出します。ヘッドは 4×4 stride-2 conv(3072→512)、GroupNorm、SiLU を 2 回重ね、空間平均を取ってから線形層に通します。
119
+ - **critic の損失:** `softplus(−d(teacher サンプル)) + softplus(d(生徒の出力))`。ノイズ量は σ = 5u / (1 + 4u)、u ~ U[0.02, 0.98] で、teacher と生徒の組で同じ σ を使います。
120
+ - **生成器の損失:** `−d(ノイズを足した生徒の x0)`。teacher の CFG 推論も fake score も、学習中は使いません。
121
+ - **teacher サンプル:** 3,199 枚。1 プロンプトにつき 1 枚で、50 step・CFG 3・負側 block 22 skip で作りました。解像度は約 1MP で、正方形・縦長・横長を混ぜています。学習時のプロンプトはこの 3,199 件から選びます。
122
+ - **本物の画像を使うヘッドは使っていません**(DMAD は 2 ヘッドですが、こちらは teacher ヘッドだけ)。
123
+
124
+ ### 設定
125
+
126
+ | 項目 | 値 |
127
+ |---|---|
128
+ | バッチ | 16(8 GPU × 2、1 サンプルずつ順に処理) |
129
+ | 生成器 | AdamW lr 2e-5、betas (0, 0.99)、weight decay 0、EMA 0.998 |
130
+ | critic | AdamW lr 4e-5、betas (0, 0.99)、weight decay 0.01、生成器を更新しない 50 it のウォームアップ |
131
+ | 反復 | 500 it(生成器の更新 450 回)、1 it 約 5.7 秒 |
132
+ | 計算機 | RunPod 8×H100 SXM。teacher サンプル生成約 1.9 時間、学習 500 it 約 47 分 |
133
+ | 費用 | 試運転と比較実験を含めて計約 $126 |
134
+
135
+ ### 学習プロンプト
136
+
137
+ 24,000 件(再配布はしていません)から、teacher サンプルを作った 3,199 件を学習に使いました。
138
+
139
+ | 種類 | 出典(ライセンス) |
140
+ |---|---|
141
+ | アニメの自然文キャプション | [BootsofLagrangian/danbooru-multitier-captions-202606](https://huggingface.co/datasets/BootsofLagrangian/danbooru-multitier-captions-202606)(CC-BY-4.0、Giyeong Oh)、[CaptionEmporium/anime-caption-danbooru-2021-sfw-5m-hq](https://huggingface.co/datasets/CaptionEmporium/anime-caption-danbooru-2021-sfw-5m-hq)(CC-BY-SA-4.0) |
142
+ | タグ列 | 上記の Danbooru タグ、[FredZhang7/anime-prompts-180K](https://huggingface.co/datasets/FredZhang7/anime-prompts-180K)(CreativeML OpenRAIL-M)。絵師名は除去 |
143
+ | 一般 | [ProGamerGov/synthetic-dataset-1m-dalle3-high-quality-captions](https://huggingface.co/datasets/ProGamerGov/synthetic-dataset-1m-dalle3-high-quality-captions)(MIT)、[data-is-better-together/open-image-preferences-v1](https://huggingface.co/datasets/data-is-better-together/open-image-preferences-v1)(Apache-2.0)、[nateraw/parti-prompts](https://huggingface.co/datasets/nateraw/parti-prompts)(Apache-2.0) |
144
+ | 文字描画(日 60% / 英 25% / 中 15%)、JSON レイアウト、短文 | テンプレートから生成 |
145
+
146
+ ## うまくいかなかったこと(DMDX)
147
+
148
+ 最初は DMDX(arXiv 2507.18569)の ADM を data-space DMD に足す計画でした。ADM は、生成器の出力にノイズをかけた同じ x_τ から、teacher(CFG あり)と fake score の ODE を Δt = 1/64 だけ進め、その 2 つを識別器に見分けさせる方式です。
149
+
150
+ - **結果:** 識別器は 130 回更新しても正解率 0.500〜0.506 のままで、学習しませんでした(`logs/probe_adm_discriminator.jsonl`)。2 つの入力の差は Δt·(v_real − v_fake) だけで、1 枚ずつの判定ではほぼ見分けられません。
151
+ - **比較:** 同じヘッドで本物側を teacher サンプルにすると、60 回で 0.56 → 0.68 まで上がりました(`logs/probe_teacher_discriminator.jsonl`)。
152
+ - この結果と、その後に出た DMAD を受けて、fake score も teacher の推論もいらない DMAD に切り替えました。
153
+
154
+ ## 制限
155
+
156
+ - 評価は目視だけで、プロンプトは 17 件です。FID などの数値指標は取っていません。
157
+ - キャラクター LoRA など、他の LoRA と重ねたときの挙動は試していません。
158
+ - 学習プロンプトは 3,199 件と少なく、teacher サンプルは 1 プロンプトにつき 1 枚です。
159
+
160
+ ## ライセンスとクレジット
161
+
162
+ - 重みとコード: Apache-2.0
163
+ - 元のモデル: [OpenMOSS-Team/MOSS-Image-SFT](https://huggingface.co/OpenMOSS-Team/MOSS-Image-SFT)(Apache-2.0)、初期値: [lodestones/MOSS-Image-SFT-8-steps-dmd](https://huggingface.co/lodestones/MOSS-Image-SFT-8-steps-dmd)(Apache-2.0)
164
+ - 参考にした論文:
165
+ - Few-Step Generation via Data-Space Iteration(arXiv 2610.12102)
166
+ - DMAD: Distribution Matching as Adversarial Distillation(arXiv 2610.02188、[コード](https://github.com/Yzmblog/DMAD))
167
+ - Adversarial Distribution Matching / DMDX(arXiv 2507.18569)
168
+ - 前作: [Anima の DMDX・DMD2・PCM 蒸留の記録](https://note.com/darask/n/neef98f000087)
code/README.md ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # rapid-moss training code
2
+
3
+ Distillation of MOSS-Image-SFT into an R-step data-space generator. Python 3.10+, torch >= 2.9, diffusers >= 0.38,
4
+ transformers >= 5.3. The release `pipeline_moss_image.py` (shipped in the MOSS-Image-SFT repo) is imported for the
5
+ transformer class only; the forward pass is reimplemented in `rapid_moss/model.py` (fp32 residual + modulation,
6
+ bf16 matmuls, unmerged LoRA sets, per-block gradient checkpointing, feature taps).
7
+
8
+ | file | role |
9
+ |---|---|
10
+ | `rapid_moss/model.py` | MOSS DiT forward shared by teacher / generator / fake score / critic (one frozen bf16 base, per-path LoRA and time path); release sampler (`sample_teacher`) |
11
+ | `rapid_moss/nets.py` | `Generator` (data-space iteration, lodestones format I/O), `FakeScore`, DMDX `Discriminator`, DMAD `Critic` |
12
+ | `rapid_moss/textenc.py` | prompt embedding cache (release Qwen3.5 text encoder, same chat template as the pipeline) |
13
+ | `rapid_moss/teachergen.py` | teacher samples (50 steps, CFG 3, negative-branch block-22 skip) for the critic's real side |
14
+ | `rapid_moss/train.py` | training loop: `method=dmad` (used for the release) or `method=dmd` (data-space DMD + DMDX discriminator) |
15
+ | `rapid_moss/sample.py` | side-by-side evaluation (teacher vs lodestones-format adapters) |
16
+ | `tests/test_equiv.py` | CPU fp32 check: forward, skip-22 and the lodestones generator path match the release transformer exactly |
17
+ | `tests/test_dmad.py` | CPU check of the critic wiring (generator phase / critic phase gradients) |
18
+ | `scripts/` | RunPod GraphQL client, pod setup and run scripts, balance watchdog |
19
+ | `configs/dmad.json` | the run that produced the released adapter (best checkpoint = EMA export at iteration 500) |
20
+
21
+ ```
22
+ torchrun --nproc-per-node 8 -m rapid_moss.textenc --model MOSS-Image-SFT --prompts train.jsonl eval.jsonl --out cache
23
+ torchrun --nproc-per-node 8 -m rapid_moss.teachergen --model MOSS-Image-SFT --cache cache --prompts train.jsonl --out teacher --count 2000
24
+ torchrun --nproc-per-node 8 -m rapid_moss.train --config configs/dmad.json --set teacher_dir=teacher
25
+ python -m rapid_moss.sample --model MOSS-Image-SFT --cache cache --prompts eval.jsonl --out eval \
26
+ --method teacher:50 --method dmd:LODESTONES_DIR:4 --method dmd:runs/dmad/export/000500:4
27
+ ```
28
+
29
+ The prompt set (24,000 prompts: anime captions and tags, general captions, Japanese / English / Chinese text rendering,
30
+ MOSS JSON layouts, short prompts) is not redistributed; see the model card for its sources.
code/configs/dmad.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "method": "dmad",
3
+ "out_dir": "/workspace/runs/dmad",
4
+ "teacher_dir": ["/workspace/teacher", "/workspace/teacher2"],
5
+ "iterations": 2500,
6
+ "micro": 2,
7
+ "critic_warmup": 50,
8
+ "lr_gen": 2e-5, "lr_time": 2e-5, "lr_q": 2e-5,
9
+ "lr_critic": 4e-5,
10
+ "ema": 0.998,
11
+ "log_every": 10,
12
+ "sample_every": 100,
13
+ "save_every": 200,
14
+ "export_every": 500,
15
+ "eval_count": 8
16
+ }
code/configs/main.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "out_dir": "/workspace/runs/main",
3
+ "iterations": 2000,
4
+ "disc_real": "teacher",
5
+ "teacher_dir": "/workspace/teacher",
6
+ "p_gan": 0.5,
7
+ "micro": 2,
8
+ "fake_per_gen": 2,
9
+ "fake_warmup": 150,
10
+ "adv_start": 50,
11
+ "adv_warmup": 200,
12
+ "lr_gen": 1.5e-5, "lr_time": 1.5e-5, "lr_q": 1.5e-5,
13
+ "ema": 0.998,
14
+ "log_every": 10,
15
+ "sample_every": 100,
16
+ "save_every": 200,
17
+ "export_every": 400,
18
+ "eval_count": 8
19
+ }
code/configs/smoke.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "out_dir": "/workspace/runs/smoke",
3
+ "iterations": 24,
4
+ "micro": 1,
5
+ "fake_per_gen": 2,
6
+ "fake_warmup": 4,
7
+ "adv_start": 2,
8
+ "adv_warmup": 4,
9
+ "log_every": 2,
10
+ "sample_every": 12,
11
+ "save_every": 24,
12
+ "export_every": 24,
13
+ "eval_count": 4
14
+ }
code/rapid_moss/__init__.py ADDED
File without changes
code/rapid_moss/model.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MOSS-Image DiT for distillation.
2
+
3
+ One frozen bf16 base (the release MOSSImageTransformer) is shared by the teacher, the
4
+ data-space generator, the fake score and the discriminator backbone; each path only
5
+ differs by its LoRA, its time path and optional extras. The residual stream and the
6
+ modulation run in fp32 (the release trains fp32-master + autocast), matmuls in bf16.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import importlib.util
11
+ import math
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+
15
+ import torch
16
+ from torch import nn
17
+ from torch.nn import functional as F
18
+ from torch.utils.checkpoint import checkpoint
19
+
20
+ BF16 = torch.bfloat16
21
+ PROJ = ('q', 'k', 'v', 'o', 'ffn_up_gate', 'ffn_down')
22
+
23
+
24
+ def load_release_module(model_dir):
25
+ """Import the release pipeline_moss_image.py (read and reviewed; no side effects on import)."""
26
+ path = Path(model_dir) / 'pipeline_moss_image.py'
27
+ spec = importlib.util.spec_from_file_location('pipeline_moss_image', path)
28
+ module = importlib.util.module_from_spec(spec)
29
+ spec.loader.exec_module(module)
30
+ return module
31
+
32
+
33
+ def load_base(model_dir, device):
34
+ release = load_release_module(model_dir)
35
+ tr = release.MOSSImageTransformer.from_pretrained(Path(model_dir) / 'transformer', torch_dtype=BF16,
36
+ local_files_only=True)
37
+ return Moss(tr.to(device), release)
38
+
39
+
40
+ def rms(x, weight, eps=1e-6):
41
+ y = x.float() * torch.rsqrt(x.float().square().mean(-1, keepdim=True) + eps)
42
+ return y.to(x.dtype) * weight
43
+
44
+
45
+ def situ(hidden, gate):
46
+ return (4.0 * torch.tanh(gate / 4.0) * torch.sigmoid(gate)) * (25.0 * torch.tanh(hidden / 25.0))
47
+
48
+
49
+ def proj_dims(dim, ffn_dim):
50
+ return {'q': (dim, dim), 'k': (dim, dim), 'v': (dim, dim), 'o': (dim, dim),
51
+ 'ffn_up_gate': (dim, 2 * ffn_dim), 'ffn_down': (ffn_dim, dim)}
52
+
53
+
54
+ class LoRA(nn.Module):
55
+ """Stacked LoRA for the projections of blocks [start, layers): A [n, r, in], B [n, out, r]; y += (alpha/r) B A x."""
56
+
57
+ def __init__(self, dims, layers, rank, alpha, start=0):
58
+ super().__init__()
59
+ self.rank, self.alpha, self.scale, self.start = rank, alpha, alpha / rank, start
60
+ layers -= start
61
+ self.A = nn.ParameterDict()
62
+ self.B = nn.ParameterDict()
63
+ for name, (fan_in, fan_out) in dims.items():
64
+ bound = 1 / math.sqrt(fan_in)
65
+ self.A[name] = nn.Parameter(torch.empty(layers, rank, fan_in).uniform_(-bound, bound))
66
+ self.B[name] = nn.Parameter(torch.zeros(layers, fan_out, rank))
67
+ self._cache = None
68
+
69
+ def delta(self, x, layer, name):
70
+ layer -= self.start
71
+ if self._cache is not None and not torch.is_grad_enabled():
72
+ a, b = self._cache[name]
73
+ a, b = a[layer], b[layer]
74
+ else:
75
+ a, b = self.A[name][layer].to(x.dtype), self.B[name][layer].to(x.dtype)
76
+ return F.linear(F.linear(x, a), b) * self.scale
77
+
78
+ def freeze_cache(self):
79
+ """bf16 copies used by no-grad forwards only; drop_cache() before the next optimizer step."""
80
+ with torch.no_grad():
81
+ self._cache = {n: (self.A[n].to(BF16), self.B[n].to(BF16)) for n in self.A}
82
+
83
+ def drop_cache(self):
84
+ self._cache = None
85
+
86
+
87
+ class TimePath(nn.Module):
88
+ """time_embedding MLP + both modulation biases (fp32); modulation weights stay the base's."""
89
+
90
+ NAMES = ('time_embedding.0.weight', 'time_embedding.0.bias', 'time_embedding.2.weight',
91
+ 'time_embedding.2.bias', 'shared_block_modulation.bias', 'video_head_modulation.bias')
92
+
93
+ def __init__(self, tensors, trainable):
94
+ super().__init__()
95
+ self.p = nn.ParameterDict({k.replace('.', '_'): nn.Parameter(tensors[k].detach().float().clone(),
96
+ requires_grad=trainable)
97
+ for k in self.NAMES})
98
+
99
+ def __getitem__(self, name):
100
+ return self.p[name.replace('.', '_')]
101
+
102
+ def state(self):
103
+ return {k: self[k].detach() for k in self.NAMES}
104
+
105
+
106
+ @dataclass
107
+ class Context:
108
+ tokens: torch.Tensor # [1, L, D] text_embedder output (bf16)
109
+ length: int
110
+
111
+
112
+ class Moss(nn.Module):
113
+ def __init__(self, tr, release):
114
+ super().__init__()
115
+ self.tr = tr.requires_grad_(False).eval()
116
+ self.release = release
117
+ cfg = tr.config
118
+ self.dim, self.heads, self.depth = cfg.dim, cfg.num_heads, cfg.num_layers
119
+ self.dims = proj_dims(cfg.dim, cfg.ffn_dim)
120
+ sd = dict(tr.named_parameters())
121
+ self.base_time = TimePath(sd, trainable=False)
122
+ self.register_buffer('mod_w', tr.shared_block_modulation.weight.detach().float(), persistent=False)
123
+ self.register_buffer('head_w', tr.video_head_modulation.weight.detach().float(), persistent=False)
124
+ self._rope = {}
125
+
126
+ # ---- conditioning ----
127
+ def rope(self, h, w, length):
128
+ key = (h, w, length)
129
+ if key not in self._rope:
130
+ if len(self._rope) > 64:
131
+ self._rope.clear()
132
+ with torch.no_grad():
133
+ self._rope[key] = self.tr.rope(h, w, length, self.mod_w.device).to(BF16)
134
+ return self._rope[key]
135
+
136
+ @torch.no_grad()
137
+ def context(self, emb):
138
+ """emb [L, text_dim] text-encoder hidden states -> Context (text refinement is prompt-only)."""
139
+ length = emb.shape[0]
140
+ rope = self.tr.rope(1, 1, length, self.mod_w.device).to(BF16)[:, 1:]
141
+ tokens = self.tr.text_embedder(emb[None].to(device=self.mod_w.device, dtype=BF16), rope)
142
+ return Context(tokens, length)
143
+
144
+ def time_mod(self, t, tp, extra=None):
145
+ half = self.tr.config.freq_dim // 2
146
+ t = torch.as_tensor(t, device=self.mod_w.device, dtype=torch.float32).reshape(-1)
147
+ freq = 10000 ** (-torch.arange(half, device=t.device, dtype=torch.float64) / half)
148
+ phase = t.double()[:, None] * freq
149
+ emb = torch.cat((phase.cos(), phase.sin()), -1).float()
150
+ time = F.linear(emb, tp['time_embedding.0.weight'], tp['time_embedding.0.bias'])
151
+ time = F.linear(F.silu(time), tp['time_embedding.2.weight'], tp['time_embedding.2.bias'])
152
+ if extra is not None:
153
+ time = time + extra
154
+ mod = F.linear(time, self.mod_w, tp['shared_block_modulation.bias']).view(-1, 6, self.dim)
155
+ head = F.linear(time, self.head_w, tp['video_head_modulation.bias']).view(-1, 2, self.dim)
156
+ return mod, head
157
+
158
+ # ---- blocks ----
159
+ def lin(self, y, i, name, lora):
160
+ out = F.linear(y, self.tr.layers[i].proj[name].weight)
161
+ if lora is not None and i >= lora.start:
162
+ out = out + lora.delta(y, i, name)
163
+ return out
164
+
165
+ def block(self, i, x, rope, mod, lora):
166
+ layer = self.tr.layers[i]
167
+ heads = self.heads
168
+ shift_a, scale_a, gate_a, shift_f, scale_f, gate_f = mod.unbind(1)
169
+ y = (rms(x, layer.norm1.weight) * (1 + scale_a[:, None]) + shift_a[:, None]).to(BF16)
170
+ q = rms(self.lin(y, i, 'q', lora).unflatten(-1, (heads, -1)), layer.q_norm.weight).flatten(2)
171
+ k = rms(self.lin(y, i, 'k', lora).unflatten(-1, (heads, -1)), layer.k_norm.weight).flatten(2)
172
+ a = self.release._attention(q, k, self.lin(y, i, 'v', lora), rope, heads)
173
+ x = x + self.lin(a, i, 'o', lora).float() * gate_a[:, None]
174
+ y = (rms(x, layer.norm2.weight) * (1 + scale_f[:, None]) + shift_f[:, None]).to(BF16)
175
+ hidden, gate = self.lin(y, i, 'ffn_up_gate', lora).chunk(2, -1)
176
+ return x + self.lin(situ(hidden, gate), i, 'ffn_down', lora).float() * gate_f[:, None]
177
+
178
+ def forward(self, latent, t, ctx, *, lora=None, tp=None, time_extra=None, skip=(), taps=None, ckpt=False):
179
+ """latent [1, C, h, w] fp32, t on the 0..1000 scale. Returns velocity [1, C, h, w] fp32,
180
+ or {block: image-token features [1, h*w, D]} when taps is given (stops after the last tap)."""
181
+ _, _, h, w = latent.shape
182
+ hw = h * w
183
+ mod, head = self.time_mod(t, tp if tp is not None else self.base_time, time_extra)
184
+ rope = self.rope(h, w, ctx.length)
185
+ video = self.tr.video_embedder(latent.flatten(2).transpose(1, 2).to(BF16))
186
+ x = torch.cat((video, ctx.tokens), 1).float()
187
+ last = max(taps) if taps else self.depth - 1
188
+ use_ckpt = ckpt and torch.is_grad_enabled()
189
+ feats = {}
190
+ for i in range(last + 1):
191
+ if i not in skip:
192
+ if use_ckpt:
193
+ x = checkpoint(self.block, i, x, rope, mod, lora, use_reentrant=False)
194
+ else:
195
+ x = self.block(i, x, rope, mod, lora)
196
+ if taps and i in taps:
197
+ feats[i] = x[:, :hw]
198
+ if taps:
199
+ return feats
200
+ x = rms(x[:, :hw], self.tr.final_norm_video.weight)
201
+ x = x * (1 + head[:, 1, None]) + head[:, 0, None]
202
+ x = F.linear(x.to(BF16), self.tr.final_linear_video.weight).float()
203
+ return x.transpose(1, 2).reshape(1, -1, h, w)
204
+
205
+ @torch.no_grad()
206
+ def teacher(self, latent, t, pos, neg, cfg, skip_neg=(22,)):
207
+ """Release guidance: CFG with block 22 skipped in the negative branch only."""
208
+ vp = self(latent, t, pos)
209
+ vn = self(latent, t, neg, skip=skip_neg)
210
+ return vn + cfg * (vp - vn)
211
+
212
+
213
+ def release_sigmas(steps, shift=5.0, total=1000, training_shift=1.0):
214
+ """MOSSImageScheduler.set_timesteps sigmas (shifted grid snapped to the training grid)."""
215
+ def grid(count, s):
216
+ lower = s / (total + s - 1)
217
+ values = torch.linspace(1, lower, count)
218
+ return s * values / (1 + (s - 1) * values)
219
+ sigmas = grid(steps, shift)
220
+ training = grid(total, training_shift).flip(0)
221
+ upper = torch.searchsorted(training, sigmas).clamp(max=total - 1)
222
+ lower = (upper - 1).clamp(min=0)
223
+ nearest = torch.where((sigmas - training[lower]).abs() <= (training[upper] - sigmas).abs(), lower, upper)
224
+ return torch.unique_consecutive(training[nearest]).tolist() + [0.0]
225
+
226
+
227
+ @torch.no_grad()
228
+ def sample_teacher(model, noise, pos, neg, steps=50, cfg=3.0, shift=5.0):
229
+ x = noise.float()
230
+ sigmas = release_sigmas(steps, shift)
231
+ for s, s_next in zip(sigmas[:-1], sigmas[1:]):
232
+ v = model.teacher(x, s * 1000, pos, neg, cfg)
233
+ x = x + (s_next - s) * v
234
+ return x
code/rapid_moss/nets.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Trainable parts: data-space generator, fake score, DMDX discriminator heads; lodestones-format I/O."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+
6
+ import torch
7
+ from safetensors.torch import load_file, save_file
8
+ from torch import nn
9
+ from torch.nn import functional as F
10
+
11
+ from .model import PROJ, LoRA, TimePath
12
+
13
+
14
+ class Generator(nn.Module):
15
+ """Data-space iteration (arXiv 2610.12102) in the lodestones MOSS form:
16
+ x = z + Q x0 (channel mix), v = net(x, code_r), x0' = z - v, x0 starts at zero."""
17
+
18
+ def __init__(self, model, rank, alpha, steps):
19
+ super().__init__()
20
+ self.lora = LoRA(model.dims, model.depth, rank, alpha)
21
+ self.time = TimePath(model.base_time.state(), trainable=True)
22
+ channels = model.tr.config.latent_channels
23
+ self.Q = nn.Parameter(torch.zeros(channels, channels))
24
+ self.register_buffer('codes', torch.full((steps,), 1000.0))
25
+ self.steps = steps
26
+
27
+ def step(self, model, z, x0, r, ctx, ckpt=False):
28
+ x = z + torch.einsum('oc,bchw->bohw', self.Q, x0)
29
+ return z - model(x, self.codes[r], ctx, lora=self.lora, tp=self.time, ckpt=ckpt)
30
+
31
+ @torch.no_grad()
32
+ def rollout(self, model, z, ctx, n):
33
+ """n generator calls from x0 = 0 (no grad); returns x0 after n steps."""
34
+ x0 = torch.zeros_like(z)
35
+ for r in range(n):
36
+ x0 = self.step(model, z, x0, r, ctx)
37
+ return x0
38
+
39
+ @torch.no_grad()
40
+ def load_lodestones(self, path):
41
+ path = Path(path)
42
+ ds = load_file(path / 'dataspace.safetensors')
43
+ self.Q.copy_(ds['latent_mix.Q'])
44
+ self.codes.copy_(ds['step_codes'][:self.steps])
45
+ te = load_file(path / 'time_embedding.safetensors')
46
+ for k in TimePath.NAMES:
47
+ self.time[k].copy_(te[k])
48
+ lora = load_file(path / 'lora.safetensors')
49
+ for i in range(self.lora.A[PROJ[0]].shape[0]):
50
+ for name in PROJ:
51
+ p = f'diffusion_model.layers.{i}.proj.{name}'
52
+ down, up = lora[p + '.lora_down.weight'], lora[p + '.lora_up.weight']
53
+ alpha = float(lora[p + '.alpha'])
54
+ if down.shape[0] != self.lora.rank:
55
+ raise ValueError(f'rank mismatch: file {down.shape[0]} vs {self.lora.rank}')
56
+ # fold the file's alpha/rank into B so our own scale applies unchanged
57
+ self.lora.A[name][i].copy_(down)
58
+ self.lora.B[name][i].copy_(up * (alpha / down.shape[0]) / self.lora.scale)
59
+
60
+ @torch.no_grad()
61
+ def export(self, out_dir, meta=None):
62
+ """Write lodestones-compatible files (kohya LoRA, fp32 time path, Q + step codes)."""
63
+ out = Path(out_dir)
64
+ out.mkdir(parents=True, exist_ok=True)
65
+ rank = self.lora.rank
66
+ lora = {}
67
+ for i in range(self.lora.A[PROJ[0]].shape[0]):
68
+ for name in PROJ:
69
+ p = f'diffusion_model.layers.{i}.proj.{name}'
70
+ lora[p + '.lora_down.weight'] = self.lora.A[name][i].float().contiguous()
71
+ lora[p + '.lora_up.weight'] = (self.lora.B[name][i].float() * self.lora.scale).contiguous()
72
+ lora[p + '.alpha'] = torch.tensor(float(rank))
73
+ info = {'rank': str(rank), 'alpha': f'{float(rank)}', 'steps': str(self.steps), **(meta or {})}
74
+ save_file(lora, out / 'lora.safetensors', metadata=info)
75
+ save_file({k: v.float().contiguous() for k, v in self.time.state().items()}, out / 'time_embedding.safetensors')
76
+ save_file({'latent_mix.Q': self.Q.float().contiguous(), 'step_codes': self.codes.float().contiguous()},
77
+ out / 'dataspace.safetensors', metadata=info)
78
+
79
+
80
+ class FakeScore(nn.Module):
81
+ """Generator-distribution velocity: base + LoRA, conditioned on the refinement index r."""
82
+
83
+ def __init__(self, model, rank, alpha, steps):
84
+ super().__init__()
85
+ self.lora = LoRA(model.dims, model.depth, rank, alpha)
86
+ self.r_emb = nn.Parameter(torch.zeros(steps, model.dim))
87
+
88
+ def __call__(self, model, x, t, ctx, r, ckpt=False):
89
+ return model(x, t, ctx, lora=self.lora, time_extra=self.r_emb[r][None], ckpt=ckpt)
90
+
91
+
92
+ class Head(nn.Module):
93
+ """DMDX 2D head: 4x4 stride-2 conv, GroupNorm(32), SiLU, then 1x1 to patch logits."""
94
+
95
+ def __init__(self, dim, hidden):
96
+ super().__init__()
97
+ self.conv = nn.Conv2d(dim, hidden, 4, 2, 1)
98
+ self.norm = nn.GroupNorm(32, hidden)
99
+ self.out = nn.Conv2d(hidden, 1, 1)
100
+
101
+ def forward(self, f):
102
+ return self.out(F.silu(self.norm(self.conv(f))))
103
+
104
+
105
+ class Discriminator(nn.Module):
106
+ """Trainable heads on frozen-teacher block features (backbone = base, no LoRA)."""
107
+
108
+ def __init__(self, dim, taps, hidden):
109
+ super().__init__()
110
+ self.taps = tuple(sorted(taps))
111
+ self.heads = nn.ModuleList([Head(dim, hidden) for _ in self.taps])
112
+
113
+ def forward(self, feats, h, w):
114
+ logits = []
115
+ for i, head in zip(self.taps, self.heads):
116
+ f = feats[i].float()
117
+ f = f * torch.rsqrt(f.square().mean(-1, keepdim=True) + 1e-6)
118
+ logits.append(head(f.transpose(1, 2).reshape(1, -1, h, w)).flatten(1))
119
+ return torch.cat(logits, 1)
120
+
121
+
122
+ class Critic(nn.Module):
123
+ """DMAD critic (arXiv 2610.02188), teacher head only: base + LoRA on blocks [start:], a conv head on the
124
+ last block's image tokens giving one logit per sample (teacher sample vs generator sample)."""
125
+
126
+ def __init__(self, model, rank, alpha, start, hidden):
127
+ super().__init__()
128
+ self.lora = LoRA(model.dims, model.depth, rank, alpha, start=start)
129
+ self.last = model.depth - 1
130
+ self.head = nn.Sequential(nn.Conv2d(model.dim, hidden, 4, 2, 1), nn.GroupNorm(32, hidden), nn.SiLU(),
131
+ nn.Conv2d(hidden, hidden, 4, 2, 1), nn.GroupNorm(32, hidden), nn.SiLU())
132
+ self.out = nn.Linear(hidden, 1)
133
+
134
+ def forward(self, model, x, t, ctx, ckpt=False):
135
+ _, _, h, w = x.shape
136
+ f = model(x, t, ctx, lora=self.lora, taps=(self.last,), ckpt=ckpt)[self.last].float()
137
+ f = f * torch.rsqrt(f.square().mean(-1, keepdim=True) + 1e-6)
138
+ f = self.head(f.transpose(1, 2).reshape(1, -1, h, w))
139
+ return self.out(f.mean((2, 3)))[:, 0]
code/rapid_moss/sample.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Side-by-side evaluation: release teacher vs data-space generators (lodestones-format dirs).
2
+
3
+ python -m rapid_moss.sample --model DIR --cache CACHE --prompts eval.jsonl --out OUT \
4
+ --method teacher:50 --method dmd:/path/lodestones:8 --method dmd:/path/lodestones:4
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import json
10
+ import time
11
+ from pathlib import Path
12
+
13
+ import torch
14
+ from safetensors import safe_open
15
+
16
+ from .model import load_base, sample_teacher
17
+ from .nets import Generator
18
+ from .textenc import Cache, read_prompts
19
+ from .train import decode
20
+
21
+
22
+ def load_generator(model, path, steps, device):
23
+ with safe_open(Path(path) / 'lora.safetensors', 'pt') as f:
24
+ key = next(k for k in f.keys() if k.endswith('.lora_down.weight'))
25
+ rank = f.get_slice(key).get_shape()[0]
26
+ gen = Generator(model, rank, rank, steps).to(device).requires_grad_(False)
27
+ gen.load_lodestones(path)
28
+ gen.lora.freeze_cache()
29
+ return gen
30
+
31
+
32
+ def label_grid(columns, labels, ids, path, cell=512):
33
+ from PIL import Image, ImageDraw
34
+ pad = 28
35
+ sheet = Image.new('RGB', (len(columns) * cell, len(ids) * (cell + pad)), 'white')
36
+ draw = ImageDraw.Draw(sheet)
37
+ for c, (images, label) in enumerate(zip(columns, labels)):
38
+ for r, im in enumerate(images):
39
+ im = Image.fromarray(im)
40
+ im.thumbnail((cell, cell))
41
+ y = r * (cell + pad)
42
+ draw.text((c * cell + 6, y + 6), f'{label} | {ids[r]}', fill='black')
43
+ sheet.paste(im, (c * cell + (cell - im.width) // 2, y + pad + (cell - im.height) // 2))
44
+ sheet.save(path, quality=88)
45
+
46
+
47
+ @torch.no_grad()
48
+ def main():
49
+ ap = argparse.ArgumentParser()
50
+ ap.add_argument('--model', required=True)
51
+ ap.add_argument('--cache', required=True)
52
+ ap.add_argument('--prompts', required=True)
53
+ ap.add_argument('--out', required=True)
54
+ ap.add_argument('--method', action='append', required=True)
55
+ ap.add_argument('--seed', type=int, default=1234)
56
+ ap.add_argument('--limit', type=int, default=0)
57
+ args = ap.parse_args()
58
+ dev = torch.device('cuda')
59
+ torch.backends.cuda.matmul.allow_tf32 = True
60
+ out = Path(args.out)
61
+ out.mkdir(parents=True, exist_ok=True)
62
+ model = load_base(args.model, dev)
63
+ from diffusers import AutoencoderKLFlux2
64
+ vae = AutoencoderKLFlux2.from_pretrained(Path(args.model) / 'vae', torch_dtype=torch.float32,
65
+ local_files_only=True).to(dev).eval()
66
+ cache = Cache(args.cache)
67
+ rows = read_prompts([args.prompts])
68
+ rows = rows[:args.limit] if args.limit else rows
69
+ neg = model.context(cache.get('').to(dev))
70
+ columns, labels, timings = [], [], {}
71
+ for spec in args.method:
72
+ kind, *rest = spec.split(':')
73
+ if kind == 'teacher':
74
+ steps = int(rest[0]) if rest else 50
75
+ cfg = float(rest[1]) if len(rest) > 1 else 3.0
76
+ tag, gen = f'teacher{steps}-cfg{cfg:g}', None
77
+ elif kind == 'dmd':
78
+ path, steps = rest[0], int(rest[1])
79
+ tag = f'{Path(path).name}-{steps}step'
80
+ gen = load_generator(model, path, steps, dev)
81
+ else:
82
+ raise SystemExit(f'unknown method {spec}')
83
+ (out / tag).mkdir(exist_ok=True)
84
+ images, secs = [], []
85
+ for row in rows:
86
+ ctx = model.context(cache.get(row['id']).to(dev))
87
+ g = torch.Generator('cpu').manual_seed(args.seed)
88
+ z = torch.randn(1, model.tr.config.latent_channels, row['h'] // 16, row['w'] // 16, generator=g).to(dev)
89
+ torch.cuda.synchronize()
90
+ t0 = time.time()
91
+ x0 = sample_teacher(model, z, ctx, neg, steps, cfg) if gen is None else gen.rollout(model, z, ctx, steps)
92
+ torch.cuda.synchronize()
93
+ secs.append(time.time() - t0)
94
+ images.append(decode(vae, x0))
95
+ from PIL import Image
96
+ Image.fromarray(images[-1]).save(out / tag / f"{row['id']}.png")
97
+ del gen
98
+ columns.append(images)
99
+ labels.append(tag)
100
+ timings[tag] = round(sum(secs) / len(secs), 3)
101
+ print(json.dumps({'method': tag, 'dit_s_per_image': timings[tag]}), flush=True)
102
+ (out / 'timings.json').write_text(json.dumps(timings, indent=1))
103
+ ids = [r['id'] for r in rows]
104
+ for start in range(0, len(ids), 6):
105
+ label_grid([c[start:start + 6] for c in columns], labels, ids[start:start + 6],
106
+ out / f'grid-{start // 6:02d}.jpg')
107
+
108
+
109
+ if __name__ == '__main__':
110
+ main()
code/rapid_moss/teachergen.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Teacher samples for the discriminator's real side (DMDX ADP-style: teacher ODE endpoints).
2
+
3
+ torchrun --nproc-per-node N -m rapid_moss.teachergen --model DIR --cache CACHE --prompts train.jsonl \
4
+ --out TEACHER --count 2400 [--steps 50 --cfg 3]
5
+ Picks `count` prompts stratified by src (fixed seed), a resolution per prompt (its own w/h for
6
+ layouts, else a bucket), runs the release sampler (CFG 3, negative skips block 22) and stores
7
+ the final latent (fp16) per prompt id with index.json {id: {file, w, h}}.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import os
14
+ import random
15
+ import time
16
+ import zlib
17
+ from collections import defaultdict
18
+ from pathlib import Path
19
+
20
+ import torch
21
+ import torch.distributed as dist
22
+ from safetensors import safe_open
23
+ from safetensors.torch import save_file
24
+
25
+ from .model import load_base, sample_teacher
26
+ from .textenc import Cache, read_prompts
27
+ from .train import DEFAULTS
28
+
29
+
30
+ def pick(rows, count, seed):
31
+ rng = random.Random(seed)
32
+ by_src = defaultdict(list)
33
+ for r in rows:
34
+ by_src[r.get('src', '')].append(r)
35
+ total = len(rows)
36
+ chosen = []
37
+ for src, items in sorted(by_src.items()):
38
+ rng.shuffle(items)
39
+ chosen += items[:round(count * len(items) / total)]
40
+ rng.shuffle(chosen)
41
+ buckets = DEFAULTS['buckets']
42
+ for r in chosen:
43
+ if 'w' not in r or 'h' not in r:
44
+ r['w'], r['h'], _ = rng.choices(buckets, weights=[b[2] for b in buckets])[0]
45
+ return chosen[:count]
46
+
47
+
48
+ @torch.no_grad()
49
+ def main():
50
+ ap = argparse.ArgumentParser()
51
+ ap.add_argument('--model', required=True)
52
+ ap.add_argument('--cache', required=True)
53
+ ap.add_argument('--prompts', required=True)
54
+ ap.add_argument('--out', required=True)
55
+ ap.add_argument('--count', type=int, default=2400)
56
+ ap.add_argument('--steps', type=int, default=50)
57
+ ap.add_argument('--cfg', type=float, default=3.0)
58
+ ap.add_argument('--seed', type=int, default=7)
59
+ ap.add_argument('--shard-size', type=int, default=200)
60
+ ap.add_argument('--exclude', nargs='*', default=[], help='index.json files of earlier runs; their prompts are skipped')
61
+ args = ap.parse_args()
62
+ distributed = 'RANK' in os.environ
63
+ if distributed:
64
+ dist.init_process_group('nccl')
65
+ rank, world = (dist.get_rank(), dist.get_world_size()) if distributed else (0, 1)
66
+ dev = torch.device('cuda', int(os.environ.get('LOCAL_RANK', 0)))
67
+ torch.cuda.set_device(dev)
68
+ torch.backends.cuda.matmul.allow_tf32 = True
69
+ out = Path(args.out)
70
+ out.mkdir(parents=True, exist_ok=True)
71
+ cache = Cache(args.cache)
72
+ skip = set()
73
+ for path in args.exclude:
74
+ skip.update(json.loads(Path(path).read_text()))
75
+ rows = pick([r for r in read_prompts([args.prompts]) if r['id'] in cache and r['id'] not in skip],
76
+ args.count, args.seed)
77
+ meta = {r['id']: {'w': r['w'], 'h': r['h']} for r in rows}
78
+ done = set()
79
+ for p in out.glob(f'shard-{rank}-*.safetensors'):
80
+ with safe_open(p, 'pt') as f:
81
+ done.update(f.keys())
82
+ mine = [r for i, r in enumerate(rows) if i % world == rank and r['id'] not in done]
83
+ model = load_base(args.model, dev)
84
+ neg = model.context(cache.get('').to(dev))
85
+ shard = len(list(out.glob(f'shard-{rank}-*.safetensors')))
86
+ buf, t0 = {}, time.time()
87
+ for n, row in enumerate(mine):
88
+ g = torch.Generator('cpu').manual_seed(zlib.crc32(f"{args.seed}:{row['id']}".encode()))
89
+ z = torch.randn(1, model.tr.config.latent_channels, row['h'] // 16, row['w'] // 16, generator=g).to(dev)
90
+ x0 = sample_teacher(model, z, model.context(cache.get(row['id']).to(dev)), neg, args.steps, args.cfg)
91
+ buf[row['id']] = x0[0].to('cpu', torch.float16).contiguous()
92
+ if len(buf) >= args.shard_size or n == len(mine) - 1:
93
+ save_file(buf, out / f'shard-{rank}-{shard:05d}.safetensors')
94
+ shard += 1
95
+ buf = {}
96
+ if rank == 0 and n % 20 == 0:
97
+ print(json.dumps({'done': n, 'of': len(mine), 's': round(time.time() - t0, 1)}), flush=True)
98
+ if distributed:
99
+ dist.barrier()
100
+ if rank == 0:
101
+ index = {}
102
+ for p in sorted(out.glob('shard-*.safetensors')):
103
+ with safe_open(p, 'pt') as f:
104
+ for k in f.keys():
105
+ index[k] = {'file': p.name, **meta[k]}
106
+ (out / 'index.json').write_text(json.dumps(index))
107
+ print(json.dumps({'index': len(index), 'wanted': len(rows)}), flush=True)
108
+ if distributed:
109
+ dist.destroy_process_group()
110
+
111
+
112
+ if __name__ == '__main__':
113
+ main()
code/rapid_moss/textenc.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Prompt embedding cache: release text encoder (Qwen3.5) hidden states, one tensor per prompt.
2
+
3
+ torchrun --nproc-per-node N -m rapid_moss.textenc --model DIR --prompts train.jsonl eval.jsonl --out CACHE
4
+ Each rank writes cache/shard-<rank>-<k>.safetensors; rank 0 merges cache/index.json at the end.
5
+ Key "" is the empty negative prompt.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import json
11
+ import os
12
+ import time
13
+ from pathlib import Path
14
+
15
+ import torch
16
+ import torch.distributed as dist
17
+ from safetensors import safe_open
18
+ from safetensors.torch import save_file
19
+
20
+
21
+ def read_prompts(paths):
22
+ rows = []
23
+ for p in paths:
24
+ with open(p, encoding='utf-8') as f:
25
+ rows += [json.loads(line) for line in f if line.strip()]
26
+ return rows
27
+
28
+
29
+ class Encoder:
30
+ def __init__(self, model_dir, device):
31
+ from transformers import AutoModel, AutoTokenizer
32
+ self.tok = AutoTokenizer.from_pretrained(Path(model_dir) / 'tokenizer', local_files_only=True)
33
+ self.enc = AutoModel.from_pretrained(Path(model_dir) / 'text_encoder', dtype=torch.bfloat16,
34
+ local_files_only=True).to(device).eval()
35
+ self.device = device
36
+
37
+ @torch.no_grad()
38
+ def __call__(self, prompt, max_len=4096):
39
+ # Same as MOSSImagePipeline.encode_prompt for one prompt.
40
+ text = self.tok.apply_chat_template([{'role': 'user', 'content': prompt}], tokenize=False,
41
+ add_generation_prompt=True, enable_thinking=True)
42
+ inputs = self.tok(text, return_tensors='pt', truncation=True, max_length=max_len).to(self.device)
43
+ return self.enc(**inputs, use_cache=False, return_dict=True).last_hidden_state[0]
44
+
45
+
46
+ class Cache:
47
+ """Read side: id -> [L, 4096] bf16 (CPU, memory-mapped shards)."""
48
+
49
+ def __init__(self, root):
50
+ self.root = Path(root)
51
+ self.index = json.loads((self.root / 'index.json').read_text())
52
+ self._files = {}
53
+
54
+ def __contains__(self, key):
55
+ return key in self.index
56
+
57
+ def get(self, key):
58
+ name = self.index[key]
59
+ if name not in self._files:
60
+ self._files[name] = safe_open(self.root / name, 'pt')
61
+ return self._files[name].get_tensor(key)
62
+
63
+
64
+ def main():
65
+ ap = argparse.ArgumentParser()
66
+ ap.add_argument('--model', required=True)
67
+ ap.add_argument('--prompts', nargs='+', required=True)
68
+ ap.add_argument('--out', required=True)
69
+ ap.add_argument('--shard-size', type=int, default=500)
70
+ args = ap.parse_args()
71
+ distributed = 'RANK' in os.environ
72
+ if distributed:
73
+ dist.init_process_group('nccl')
74
+ rank, world = (dist.get_rank(), dist.get_world_size()) if distributed else (0, 1)
75
+ device = torch.device('cuda', int(os.environ.get('LOCAL_RANK', 0)))
76
+ torch.cuda.set_device(device)
77
+ out = Path(args.out)
78
+ out.mkdir(parents=True, exist_ok=True)
79
+ rows = read_prompts(args.prompts)
80
+ keys = [''] + [r['id'] for r in rows]
81
+ texts = dict([('', '')] + [(r['id'], r['prompt']) for r in rows])
82
+ if len(set(keys)) != len(keys):
83
+ raise SystemExit('duplicate prompt ids')
84
+ done = set()
85
+ for p in out.glob(f'shard-{rank}-*.safetensors'):
86
+ with safe_open(p, 'pt') as f:
87
+ done.update(f.keys())
88
+ mine = [k for i, k in enumerate(keys) if i % world == rank and k not in done]
89
+ enc = Encoder(args.model, device)
90
+ buf, shard, t0 = {}, len(list(out.glob(f'shard-{rank}-*.safetensors'))), time.time()
91
+
92
+ def flush():
93
+ nonlocal buf, shard
94
+ if buf:
95
+ save_file(buf, out / f'shard-{rank}-{shard:05d}.safetensors')
96
+ shard += 1
97
+ buf = {}
98
+
99
+ for n, key in enumerate(mine):
100
+ buf[key] = enc(texts[key]).to('cpu', torch.bfloat16).contiguous()
101
+ if len(buf) >= args.shard_size:
102
+ flush()
103
+ if rank == 0 and n % 200 == 0:
104
+ print(json.dumps({'encoded': n, 'of': len(mine), 's': round(time.time() - t0, 1)}), flush=True)
105
+ flush()
106
+ if distributed:
107
+ dist.barrier()
108
+ if rank == 0:
109
+ index = {}
110
+ for p in sorted(out.glob('shard-*.safetensors')):
111
+ with safe_open(p, 'pt') as f:
112
+ for k in f.keys():
113
+ index[k] = p.name
114
+ missing = [k for k in keys if k not in index]
115
+ (out / 'index.json').write_text(json.dumps(index))
116
+ print(json.dumps({'index': len(index), 'missing': len(missing)}), flush=True)
117
+ if distributed:
118
+ dist.destroy_process_group()
119
+
120
+
121
+ if __name__ == '__main__':
122
+ main()
code/rapid_moss/train.py ADDED
@@ -0,0 +1,499 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Distillation of MOSS-Image-SFT to an R-step data-space generator (arXiv 2610.12102).
2
+
3
+ torchrun --nproc-per-node N -m rapid_moss.train --config configs/main.json [--set key=value ...]
4
+
5
+ method=dmad (arXiv 2610.02188): no fake score and no teacher evaluation during training. A critic
6
+ (base + LoRA on the later blocks + a head on the last block) separates noised teacher samples
7
+ (rapid_moss.teachergen) from noised generator samples of the same prompts with a logistic loss;
8
+ the generator minimises -critic(noised x0). Prompts are drawn from the teacher-sample pool.
9
+
10
+ method=dmd, per iteration: fake_per_gen fake-score updates, then (after fake_warmup) one generator update
11
+ with the DMD direction sg[v_real - v_fake] on x0 (arXiv 2610.12102) plus the ADM generator loss
12
+ -D(x_fake at tau - dt) (arXiv 2507.18569), and one discriminator hinge update on the same pair
13
+ (x_real = x_tau - dt * v_teacher_cfg, x_fake = x_tau - dt * v_fake). Each GPU runs `micro`
14
+ single-sample micro-steps per update (samples have different resolutions and text lengths).
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import math
21
+ import os
22
+ import random
23
+ import time
24
+ from pathlib import Path
25
+
26
+ import torch
27
+ import torch.distributed as dist
28
+ from torch.nn import functional as F
29
+
30
+ from .model import load_base
31
+ from .nets import Critic, Discriminator, FakeScore, Generator
32
+ from .textenc import Cache, read_prompts
33
+
34
+ DEFAULTS = {
35
+ 'model_dir': '/workspace/models/MOSS-Image-SFT',
36
+ 'lodestones_dir': '/workspace/models/MOSS-Image-SFT-8-steps-dmd',
37
+ 'cache_dir': '/workspace/cache',
38
+ 'prompts': '/workspace/prompts/train.jsonl',
39
+ 'eval_prompts': '/workspace/prompts/eval.jsonl',
40
+ 'out_dir': '/workspace/runs/main',
41
+ 'steps': 4,
42
+ 'gen_rank': 128, 'gen_alpha': 128,
43
+ 'fake_rank': 64, 'fake_alpha': 64,
44
+ 'disc_taps': [5, 11, 17, 23, 29, 35], 'disc_hidden': 256,
45
+ 'iterations': 3000,
46
+ 'micro': 2,
47
+ 'fake_per_gen': 2,
48
+ 'fake_warmup': 200,
49
+ 'lr_gen': 1e-5, 'lr_time': 1e-5, 'lr_q': 1e-5, 'lr_fake': 1e-4, 'lr_disc': 5e-5,
50
+ 'betas': [0.0, 0.99],
51
+ 'ema': 0.999,
52
+ 'cfg': [3.0, 3.0],
53
+ 'skip_neg': [22],
54
+ 'tau_beta': [2.5, 1.0], 'tau_clamp': [0.03, 0.985],
55
+ 'dt': 1 / 64,
56
+ 'adv_weight': 0.5, 'adv_start': 50, 'adv_warmup': 200,
57
+ # discriminator real side: 'adm' = teacher ODE step from the same noised generator sample
58
+ # (ADM, x_real/x_fake at tau - dt); 'teacher' = noised teacher samples of the same prompt
59
+ # (ADP-style endpoints from rapid_moss.teachergen), drawn on a p_gan share of generator steps
60
+ 'disc_real': 'adm', 'teacher_dir': '/workspace/teacher', 'p_gan': 0.5, 'gan_t': [0.02, 0.98],
61
+ 'clip_gen': 0.0, 'clip_critic': 0.0, # 0 = no clipping; a non-finite gradient norm always skips the step
62
+ 'buckets': [[1024, 1024, 0.35], [896, 1152, 0.15], [832, 1216, 0.15], [1152, 896, 0.1],
63
+ [1216, 832, 0.1], [768, 1344, 0.075], [1344, 768, 0.075]],
64
+ 'method': 'dmd',
65
+ 'critic_rank': 128, 'critic_alpha': 128, 'critic_start': 21, 'critic_hidden': 512,
66
+ 'critic_shift': 5.0, 'critic_sigma': [0.02, 0.98], 'lr_critic': 4e-5, 'wd_critic': 0.01,
67
+ 'critic_warmup': 50,
68
+ 'ckpt': True,
69
+ 'seed': 0,
70
+ 'log_every': 10, 'sample_every': 100, 'save_every': 250, 'export_every': 500,
71
+ 'eval_count': 8, 'eval_seed': 1234,
72
+ }
73
+
74
+
75
+ def parse_config():
76
+ ap = argparse.ArgumentParser()
77
+ ap.add_argument('--config')
78
+ ap.add_argument('--set', nargs='*', default=[])
79
+ args = ap.parse_args()
80
+ cfg = dict(DEFAULTS)
81
+ if args.config:
82
+ cfg.update(json.loads(Path(args.config).read_text()))
83
+ for item in args.set:
84
+ key, value = item.split('=', 1)
85
+ if key not in DEFAULTS:
86
+ raise SystemExit(f'unknown key {key}')
87
+ try:
88
+ cfg[key] = json.loads(value)
89
+ except json.JSONDecodeError:
90
+ cfg[key] = value
91
+ return cfg
92
+
93
+
94
+ class Dist:
95
+ def __init__(self):
96
+ self.on = 'RANK' in os.environ
97
+ if self.on:
98
+ dist.init_process_group('nccl')
99
+ self.rank = dist.get_rank() if self.on else 0
100
+ self.world = dist.get_world_size() if self.on else 1
101
+ self.device = torch.device('cuda', int(os.environ.get('LOCAL_RANK', 0)))
102
+ torch.cuda.set_device(self.device)
103
+
104
+ def mean_grads(self, params):
105
+ grads = [p.grad for p in params]
106
+ if self.world == 1:
107
+ return
108
+ flat = torch._utils._flatten_dense_tensors(grads)
109
+ dist.all_reduce(flat)
110
+ flat /= self.world
111
+ for g, f in zip(grads, torch._utils._unflatten_dense_tensors(flat, grads)):
112
+ g.copy_(f)
113
+
114
+ def mean(self, values):
115
+ t = torch.tensor(values, device=self.device, dtype=torch.float64)
116
+ if self.on:
117
+ dist.all_reduce(t)
118
+ t /= self.world
119
+ return t.tolist()
120
+
121
+ def barrier(self):
122
+ if self.on:
123
+ dist.barrier()
124
+
125
+
126
+ def zero_grads(params):
127
+ for p in params:
128
+ if p.grad is None:
129
+ p.grad = torch.zeros_like(p)
130
+ else:
131
+ p.grad.zero_()
132
+
133
+
134
+ def load_teacher(dirs, rows, dev):
135
+ """Scan teachergen shards (any number of dirs); returns the prompt rows that have a teacher latent
136
+ (with w/h from the latent shape) and a getter id -> [1, C, h, w] fp32 on dev."""
137
+ from safetensors import safe_open
138
+ files, where = [], {}
139
+ for d in [dirs] if isinstance(dirs, str) else dirs:
140
+ for path in sorted(Path(d).glob('shard-*.safetensors')):
141
+ try:
142
+ f = safe_open(path, 'pt')
143
+ except Exception as e: # a shard cut off when its teachergen run was stopped
144
+ print(json.dumps({'skip_shard': str(path), 'error': str(e)[:120]}), flush=True)
145
+ continue
146
+ files.append(f)
147
+ for k in f.keys():
148
+ _, h, w = f.get_slice(k).get_shape()
149
+ where[k] = (len(files) - 1, w * 16, h * 16)
150
+ pool = [dict(r, w=where[r['id']][1], h=where[r['id']][2]) for r in rows if r['id'] in where]
151
+
152
+ def get(key):
153
+ return files[where[key][0]].get_tensor(key)[None].to(dev, torch.float32)
154
+ return pool, get
155
+
156
+
157
+ def decode(vae, x0):
158
+ z = x0.to(vae.dtype)
159
+ bn, eps = vae.bn, vae.config.batch_norm_eps
160
+ z = z * (bn.running_var + eps).sqrt().view(1, -1, 1, 1) + bn.running_mean.view(1, -1, 1, 1)
161
+ b, c, h, w = z.shape
162
+ z = z.reshape(b, c // 4, 2, 2, h, w).permute(0, 1, 4, 2, 5, 3).reshape(b, c // 4, 2 * h, 2 * w)
163
+ img = vae.decode(z, return_dict=False)[0].clamp(-1, 1)
164
+ return ((img[0].float() + 1) * 127.5).round().byte().permute(1, 2, 0).cpu().numpy()
165
+
166
+
167
+ def contact_sheet(images, path, cell=384, cols=4):
168
+ from PIL import Image
169
+ rows = math.ceil(len(images) / cols)
170
+ sheet = Image.new('RGB', (cols * cell, rows * cell), 'white')
171
+ for i, im in enumerate(images):
172
+ im = Image.fromarray(im)
173
+ im.thumbnail((cell, cell))
174
+ sheet.paste(im, ((i % cols) * cell + (cell - im.width) // 2, (i // cols) * cell + (cell - im.height) // 2))
175
+ sheet.save(path, quality=90)
176
+
177
+
178
+ def main():
179
+ C = parse_config()
180
+ D = Dist()
181
+ dev = D.device
182
+ torch.backends.cuda.matmul.allow_tf32 = True
183
+ torch.backends.cudnn.allow_tf32 = True
184
+ out = Path(C['out_dir'])
185
+ if D.rank == 0:
186
+ (out / 'samples').mkdir(parents=True, exist_ok=True)
187
+ (out / 'ckpt').mkdir(exist_ok=True)
188
+ (out / 'config.json').write_text(json.dumps(C, indent=1))
189
+ R = C['steps']
190
+
191
+ model = load_base(C['model_dir'], dev)
192
+ gen = Generator(model, C['gen_rank'], C['gen_alpha'], R).to(dev)
193
+ gen.load_lodestones(C['lodestones_dir'])
194
+ ema = Generator(model, C['gen_rank'], C['gen_alpha'], R).to(dev).requires_grad_(False)
195
+ ema.load_state_dict(gen.state_dict())
196
+ betas = tuple(C['betas'])
197
+ gen_params = list(gen.parameters())
198
+ opt_gen = torch.optim.AdamW([{'params': list(gen.lora.parameters()), 'lr': C['lr_gen']},
199
+ {'params': list(gen.time.parameters()), 'lr': C['lr_time']},
200
+ {'params': [gen.Q], 'lr': C['lr_q']}], betas=betas, weight_decay=0.0)
201
+ parts = {'gen': gen, 'ema': ema, 'opt_gen': opt_gen}
202
+ dmad = C['method'] == 'dmad'
203
+ if dmad:
204
+ critic = Critic(model, C['critic_rank'], C['critic_alpha'], C['critic_start'], C['critic_hidden']).to(dev)
205
+ opt_critic = torch.optim.AdamW(critic.parameters(), lr=C['lr_critic'], betas=betas,
206
+ weight_decay=C['wd_critic'])
207
+ parts.update(critic=critic, opt_critic=opt_critic)
208
+ trainables = (gen_params, list(critic.parameters()))
209
+ elif C['method'] == 'dmd':
210
+ fake = FakeScore(model, C['fake_rank'], C['fake_alpha'], R).to(dev)
211
+ disc = Discriminator(model.dim, C['disc_taps'], C['disc_hidden']).to(dev)
212
+ opt_fake = torch.optim.AdamW(fake.parameters(), lr=C['lr_fake'], betas=betas, weight_decay=0.0)
213
+ opt_disc = torch.optim.AdamW(disc.parameters(), lr=C['lr_disc'], betas=betas, weight_decay=0.0)
214
+ parts.update(fake=fake, disc=disc, opt_fake=opt_fake, opt_disc=opt_disc)
215
+ trainables = (gen_params, list(fake.parameters()), list(disc.parameters()))
216
+ else:
217
+ raise SystemExit(f"method must be dmd or dmad, got {C['method']}")
218
+ for params in trainables:
219
+ zero_grads(params)
220
+
221
+ cache = Cache(C['cache_dir'])
222
+ rows = [r for r in read_prompts([C['prompts']]) if r['id'] in cache]
223
+ evals = [r for r in read_prompts([C['eval_prompts']]) if r['id'] in cache][:C['eval_count']]
224
+ neg = model.context(cache.get('').to(dev))
225
+ buckets = C['buckets']
226
+ teacher = None
227
+ if dmad or C['disc_real'] == 'teacher':
228
+ trows, teacher = load_teacher(C['teacher_dir'], rows, dev)
229
+ if D.rank == 0:
230
+ print(json.dumps({'teacher_samples': len(trows)}), flush=True)
231
+ elif C['disc_real'] != 'adm':
232
+ raise SystemExit(f"disc_real must be adm or teacher, got {C['disc_real']}")
233
+
234
+ start, gen_updates = 0, 0
235
+ latest = out / 'ckpt' / 'latest.pt'
236
+ if latest.exists():
237
+ state = torch.load(latest, map_location=dev, weights_only=False)
238
+ for key, mod in parts.items():
239
+ mod.load_state_dict(state[key])
240
+ start, gen_updates = state['it'], state['gen_updates']
241
+ del state
242
+ if D.rank == 0:
243
+ print(json.dumps({'resume': start, 'gen_updates': gen_updates}), flush=True)
244
+ rng = random.Random(C['seed'] * 1_000_003 + D.rank * 7919 + start)
245
+ torch.manual_seed(C['seed'] * 1_000_003 + D.rank * 7919 + start)
246
+ beta = torch.distributions.Beta(*[torch.tensor(float(v)) for v in C['tau_beta']])
247
+ lo, hi = C['tau_clamp']
248
+ dt = C['dt']
249
+ vae = None
250
+
251
+ def draw(pool=None):
252
+ pool = pool or rows
253
+ row = pool[rng.randrange(len(pool))]
254
+ if 'w' in row and 'h' in row:
255
+ w, h = row['w'], row['h']
256
+ else:
257
+ w, h, _ = rng.choices(buckets, weights=[b[2] for b in buckets])[0]
258
+ ctx = model.context(cache.get(row['id']).to(dev))
259
+ z = torch.randn(1, model.tr.config.latent_channels, h // 16, w // 16, device=dev)
260
+ return ctx, z, row
261
+
262
+ def tau():
263
+ return float(beta.sample().clamp(lo, hi))
264
+
265
+ def r_index(phase, m):
266
+ return ((phase * D.world + D.rank) * C['micro'] + m) % R
267
+
268
+ def fake_step(phase):
269
+ losses = []
270
+ gen.lora.freeze_cache()
271
+ for m in range(C['micro']):
272
+ ctx, z, _ = draw()
273
+ r = r_index(phase, m)
274
+ x0 = gen.rollout(model, z, ctx, r + 1)
275
+ t = tau()
276
+ eps = torch.randn_like(z)
277
+ v = fake(model, (1 - t) * x0 + t * eps, t * 1000, ctx, r, ckpt=C['ckpt'])
278
+ loss = F.mse_loss(v, eps - x0)
279
+ (loss / C['micro']).backward()
280
+ losses.append(loss.item())
281
+ gen.lora.drop_cache()
282
+ D.mean_grads(list(fake.parameters()))
283
+ opt_fake.step()
284
+ opt_fake.zero_grad(set_to_none=False)
285
+ return sum(losses) / len(losses)
286
+
287
+ def gen_step(phase):
288
+ ramp = 0.0 if gen_updates < C['adv_start'] else min(1.0, (gen_updates - C['adv_start'] + 1) / max(1, C['adv_warmup']))
289
+ stats = {k: 0.0 for k in ('dmd_rms', 'adv_ratio', 'lam', 'd_loss', 'd_real', 'd_fake', 'd_acc')}
290
+ n_gan = 0
291
+ fake.lora.freeze_cache()
292
+ gen.lora.freeze_cache()
293
+ for m in range(C['micro']):
294
+ use_teacher = teacher is not None and rng.random() < C['p_gan']
295
+ ctx, z, row = draw(trows if use_teacher else None)
296
+ r = r_index(phase, m)
297
+ x_prev = gen.rollout(model, z, ctx, r)
298
+ x0_hat = gen.step(model, z, x_prev, r, ctx, ckpt=C['ckpt'])
299
+ x0d = x0_hat.detach()
300
+ t = tau()
301
+ eps = torch.randn_like(z)
302
+ xt = (1 - t) * x0d + t * eps
303
+ w = rng.uniform(*C['cfg'])
304
+ with torch.no_grad():
305
+ v_real = model.teacher(xt, t * 1000, ctx, neg, w, tuple(C['skip_neg']))
306
+ v_fake = fake(model, xt, t * 1000, ctx, r)
307
+ g_dmd = (v_real - v_fake) / x0d.numel()
308
+ stats['dmd_rms'] += (v_real - v_fake).square().mean().sqrt().item()
309
+ _, _, h, wd = z.shape
310
+ leaf = x0d.clone().requires_grad_(True)
311
+ if teacher is None:
312
+ # ADM: generator loss -D(x_tau - dt * v_fake) at tau - dt; gradient taken w.r.t. x0 only
313
+ s = t - dt
314
+ x_fake = (1 - t) * leaf + t * eps - dt * v_fake
315
+ x_real = xt - dt * v_real
316
+ elif use_teacher:
317
+ s = rng.uniform(*C['gan_t'])
318
+ x_fake = (1 - s) * leaf + s * torch.randn_like(z)
319
+ x_real = (1 - s) * teacher(row['id']) + s * torch.randn_like(z)
320
+ else:
321
+ x0_hat.backward(g_dmd / C['micro'])
322
+ del x0_hat
323
+ continue
324
+ n_gan += 1
325
+ feats = model(x_fake, s * 1000, ctx, taps=disc.taps, ckpt=C['ckpt'])
326
+ logits_f = disc(feats, h, wd)
327
+ g_adv, = torch.autograd.grad(-logits_f.mean(), leaf)
328
+ ratio = (g_adv.norm() / (g_dmd.norm() + 1e-30)).item()
329
+ lam = C['adv_weight'] * ramp / max(ratio, 1e-12)
330
+ stats['adv_ratio'] += ratio
331
+ stats['lam'] += lam
332
+ x0_hat.backward((g_dmd + lam * g_adv) / C['micro'])
333
+ # discriminator hinge on the same pair (fake features reused from the pass above)
334
+ f_fake = {k: v.detach() for k, v in feats.items()}
335
+ del x0_hat, feats, logits_f
336
+ with torch.no_grad():
337
+ f_real = model(x_real, s * 1000, ctx, taps=disc.taps)
338
+ d_real, d_fake = disc(f_real, h, wd), disc(f_fake, h, wd)
339
+ d_loss = F.relu(1 - d_real).mean() + F.relu(1 + d_fake).mean()
340
+ (d_loss / C['micro']).backward()
341
+ stats['d_loss'] += d_loss.item()
342
+ stats['d_real'] += d_real.mean().item()
343
+ stats['d_fake'] += d_fake.mean().item()
344
+ stats['d_acc'] += 0.5 * ((d_real > 0).float().mean() + (d_fake < 0).float().mean()).item()
345
+ gen.lora.drop_cache()
346
+ fake.lora.drop_cache()
347
+ D.mean_grads(gen_params)
348
+ D.mean_grads(list(disc.parameters()))
349
+ gnorm = torch.nn.utils.clip_grad_norm_(gen_params, C['clip_gen'] or 1e12)
350
+ if torch.isfinite(gnorm):
351
+ opt_gen.step()
352
+ opt_disc.step()
353
+ opt_gen.zero_grad(set_to_none=False)
354
+ opt_disc.zero_grad(set_to_none=False)
355
+ with torch.no_grad():
356
+ for pe, p in zip(ema.parameters(), gen.parameters()):
357
+ pe.lerp_(p, 1 - C['ema'])
358
+ stats = {k: v / (C['micro'] if k == 'dmd_rms' else max(n_gan, 1)) for k, v in stats.items()}
359
+ stats['gan_frac'] = n_gan / C['micro']
360
+ stats['gnorm'] = gnorm.item()
361
+ stats['ramp'] = ramp
362
+ return stats
363
+
364
+ def critic_sigma():
365
+ u, k = rng.uniform(*C['critic_sigma']), C['critic_shift']
366
+ return k * u / (1 + (k - 1) * u)
367
+
368
+ def dmad_step(phase, gen_on):
369
+ stats = {k: 0.0 for k in ('g_logit', 'c_loss', 'd_teacher', 'd_gen', 'c_acc')}
370
+ stash = []
371
+ gen.lora.freeze_cache()
372
+ critic.requires_grad_(False)
373
+ for m in range(C['micro']):
374
+ ctx, z, row = draw(trows)
375
+ r = r_index(phase, m)
376
+ if gen_on:
377
+ x0_hat = gen.step(model, z, gen.rollout(model, z, ctx, r), r, ctx, ckpt=C['ckpt'])
378
+ s = critic_sigma()
379
+ logit = critic(model, (1 - s) * x0_hat + s * torch.randn_like(z), s * 1000, ctx, ckpt=C['ckpt'])
380
+ (-logit.mean() / C['micro']).backward()
381
+ stats['g_logit'] += logit.item()
382
+ x0 = x0_hat.detach()
383
+ del x0_hat, logit
384
+ else:
385
+ x0 = gen.rollout(model, z, ctx, r + 1)
386
+ stash.append((ctx, row, x0))
387
+ gen.lora.drop_cache()
388
+ if gen_on:
389
+ D.mean_grads(gen_params)
390
+ gnorm = torch.nn.utils.clip_grad_norm_(gen_params, C['clip_gen'] or 1e12)
391
+ if torch.isfinite(gnorm):
392
+ opt_gen.step()
393
+ with torch.no_grad():
394
+ for pe, p in zip(ema.parameters(), gen.parameters()):
395
+ pe.lerp_(p, 1 - C['ema'])
396
+ stats['gnorm'] = gnorm.item() * C['micro']
397
+ else:
398
+ stats['skip_gen'] = C['micro']
399
+ opt_gen.zero_grad(set_to_none=False)
400
+ critic.requires_grad_(True)
401
+ for ctx, row, x0 in stash:
402
+ s = critic_sigma()
403
+ d_q = critic(model, (1 - s) * teacher(row['id']) + s * torch.randn_like(x0), s * 1000, ctx, ckpt=C['ckpt'])
404
+ d_g = critic(model, (1 - s) * x0 + s * torch.randn_like(x0), s * 1000, ctx, ckpt=C['ckpt'])
405
+ loss = F.softplus(-d_q).mean() + F.softplus(d_g).mean()
406
+ (loss / C['micro']).backward()
407
+ stats['c_loss'] += loss.item()
408
+ stats['d_teacher'] += d_q.item()
409
+ stats['d_gen'] += d_g.item()
410
+ stats['c_acc'] += 0.5 * ((d_q > 0).float() + (d_g < 0).float()).item()
411
+ critic_params = list(critic.parameters())
412
+ D.mean_grads(critic_params)
413
+ cnorm = torch.nn.utils.clip_grad_norm_(critic_params, C['clip_critic'] or 1e12)
414
+ if torch.isfinite(cnorm):
415
+ opt_critic.step()
416
+ stats['cnorm'] = cnorm.item() * C['micro']
417
+ else:
418
+ stats['skip_critic'] = C['micro']
419
+ opt_critic.zero_grad(set_to_none=False)
420
+ return {k: v / C['micro'] for k, v in stats.items()}
421
+
422
+ @torch.no_grad()
423
+ def sample(tag):
424
+ nonlocal vae
425
+ if D.rank == 0:
426
+ if vae is None:
427
+ from diffusers import AutoencoderKLFlux2
428
+ vae = AutoencoderKLFlux2.from_pretrained(Path(C['model_dir']) / 'vae', torch_dtype=torch.float32,
429
+ local_files_only=True).to(dev).eval()
430
+ d = out / 'samples' / tag
431
+ d.mkdir(parents=True, exist_ok=True)
432
+ images = []
433
+ for row in evals:
434
+ g = torch.Generator('cpu').manual_seed(C['eval_seed'])
435
+ z = torch.randn(1, model.tr.config.latent_channels, row['h'] // 16, row['w'] // 16, generator=g).to(dev)
436
+ x0 = ema.rollout(model, z, model.context(cache.get(row['id']).to(dev)), R)
437
+ images.append(decode(vae, x0))
438
+ from PIL import Image
439
+ Image.fromarray(images[-1]).save(d / f"{row['id']}.png")
440
+ contact_sheet(images, out / 'samples' / f'{tag}.jpg')
441
+ D.barrier()
442
+
443
+ def save(it):
444
+ if D.rank == 0:
445
+ state = {key: mod.state_dict() for key, mod in parts.items()}
446
+ state.update(it=it, gen_updates=gen_updates, config=C)
447
+ tmp = out / 'ckpt' / 'latest.tmp'
448
+ torch.save(state, tmp)
449
+ if latest.exists():
450
+ latest.replace(out / 'ckpt' / 'prev.pt') # one older state survives a bad save
451
+ tmp.replace(latest)
452
+ D.barrier()
453
+
454
+ if start == 0 and C['sample_every']:
455
+ sample('init')
456
+ t_last, log = time.time(), []
457
+ for it in range(start, C['iterations']):
458
+ entry = {'it': it}
459
+ if dmad:
460
+ gen_on = it >= C['critic_warmup']
461
+ entry.update(dmad_step(it, gen_on))
462
+ gen_updates += gen_on
463
+ else:
464
+ entry['fake'] = sum(fake_step(it * C['fake_per_gen'] + k) for k in range(C['fake_per_gen'])) / C['fake_per_gen']
465
+ if it >= C['fake_warmup']:
466
+ entry.update(gen_step(it))
467
+ gen_updates += 1
468
+ log.append(entry)
469
+ done = it + 1
470
+ if done % C['log_every'] == 0:
471
+ keys = sorted({k for e in log for k in e if k != 'it'})
472
+ means = D.mean([sum(e.get(k, 0.0) for e in log) / len(log) for k in keys])
473
+ now = time.time()
474
+ if D.rank == 0:
475
+ rec = {'it': done, 'gen_updates': gen_updates, 's_per_it': round((now - t_last) / len(log), 2),
476
+ 'mem_gib': round(torch.cuda.max_memory_allocated() / 2**30, 1),
477
+ **{k: round(v, 6) for k, v in zip(keys, means)}}
478
+ print(json.dumps(rec), flush=True)
479
+ with open(out / 'log.jsonl', 'a') as f:
480
+ f.write(json.dumps(rec) + '\n')
481
+ t_last, log = now, []
482
+ if C['sample_every'] and done % C['sample_every'] == 0:
483
+ sample(f'{done:06d}')
484
+ if C['save_every'] and done % C['save_every'] == 0:
485
+ save(done)
486
+ if C['export_every'] and done % C['export_every'] == 0 and D.rank == 0:
487
+ ema.export(out / 'export' / f'{done:06d}', {'iteration': str(done)})
488
+ save(C['iterations'])
489
+ if D.rank == 0:
490
+ ema.export(out / 'export' / 'final', {'iteration': str(C['iterations'])})
491
+ gen.export(out / 'export' / 'final-raw', {'iteration': str(C['iterations'])})
492
+ if C['sample_every']:
493
+ sample('final')
494
+ if D.on:
495
+ dist.destroy_process_group()
496
+
497
+
498
+ if __name__ == '__main__':
499
+ main()
code/scripts/main_run.sh ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Runs on the main pod (all GPUs): setup -> text cache -> [teacher samples] -> training.
3
+ # DISC_REAL=adm|teacher TEACHER_COUNT=2400 bash scripts/main_run.sh [--set key=value ...]
4
+ # Expects code at /workspace/code and prompts at /workspace/prompts/{train,eval}.jsonl.
5
+ set -euo pipefail
6
+ cd /workspace/code
7
+ N=$(nvidia-smi -L | wc -l)
8
+ M=/workspace/models/MOSS-Image-SFT
9
+ echo "== setup $(date -u +%T)"
10
+ bash scripts/pod_setup.sh
11
+ echo "== text cache $(date -u +%T)"
12
+ torchrun --nproc-per-node "$N" -m rapid_moss.textenc --model $M \
13
+ --prompts /workspace/prompts/train.jsonl /workspace/prompts/eval.jsonl --out /workspace/cache
14
+ EXTRA=(disc_real=adm)
15
+ if [[ "${DISC_REAL:-teacher}" == teacher ]]; then
16
+ echo "== teacher samples $(date -u +%T)"
17
+ torchrun --nproc-per-node "$N" -m rapid_moss.teachergen --model $M --cache /workspace/cache \
18
+ --prompts /workspace/prompts/train.jsonl --out /workspace/teacher --count "${TEACHER_COUNT:-2000}"
19
+ EXTRA=()
20
+ fi
21
+ echo "== train $(date -u +%T)"
22
+ torchrun --nproc-per-node "$N" -m rapid_moss.train --config configs/main.json --set "${EXTRA[@]}" "$@"
23
+ echo "== done $(date -u +%T)"
code/scripts/pod_dmad.sh ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # On the pod: let the second teacher run go until DEADLINE (UTC, e.g. 21:50), stop it, then train DMAD on all GPUs.
3
+ DEADLINE=${1:?usage: pod_dmad.sh HH:MM}
4
+ until [[ "$(date -u +%H:%M)" > "$DEADLINE" || "$(date -u +%H:%M)" == "$DEADLINE" ]]; do sleep 20; done
5
+ pkill -f "rapid_moss.teachergen"
6
+ while pgrep -f "rapid_moss.teachergen" > /dev/null; do sleep 5; done
7
+ echo "== teacher samples: $(ls /workspace/teacher /workspace/teacher2 | grep -c safetensors) shards $(date -u +%T)"
8
+ cd /workspace/code
9
+ N=$(nvidia-smi -L | wc -l)
10
+ torchrun --nproc-per-node "$N" -m rapid_moss.train --config configs/dmad.json
11
+ echo "== done $(date -u +%T)"
code/scripts/pod_setup.sh ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Runs on the pod: Python deps + model downloads into /workspace (idempotent).
3
+ set -euo pipefail
4
+ cd /workspace
5
+ export HF_HUB_ENABLE_HF_TRANSFER=0 HF_XET_HIGH_PERFORMANCE=1 PIP_BREAK_SYSTEM_PACKAGES=1
6
+ pip install -q --root-user-action=ignore "diffusers==0.38.0" "transformers==5.17.0" "accelerate>=1.10" \
7
+ "safetensors>=0.6" "huggingface_hub[hf_xet]>=1.0" pillow flash-linear-attention 2>&1 | tail -3
8
+ python - <<'EOF'
9
+ import torch, diffusers, transformers
10
+ print('torch', torch.__version__, 'cuda', torch.version.cuda, 'diffusers', diffusers.__version__,
11
+ 'transformers', transformers.__version__, [torch.cuda.get_device_name(i) for i in range(torch.cuda.device_count())])
12
+ EOF
13
+ mkdir -p models
14
+ hf download OpenMOSS-Team/MOSS-Image-SFT --local-dir models/MOSS-Image-SFT --exclude "assets/*" > /dev/null
15
+ hf download lodestones/MOSS-Image-SFT-8-steps-dmd lora.safetensors time_embedding.safetensors dataspace.safetensors README.md \
16
+ --local-dir models/MOSS-Image-SFT-8-steps-dmd > /dev/null
17
+ du -sh models/*
18
+ md5sum models/MOSS-Image-SFT/pipeline_moss_image.py
code/scripts/pod_stop_train.sh ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # On the pod: stop the training run (torchrun + workers) and its pod_dmad.sh wrapper.
3
+ pkill -f "scripts/pod_dmad.sh"
4
+ pkill -f "rapid_moss.train"
5
+ sleep 5
6
+ pgrep -fa "rapid_moss.train" | head -3 || true
code/scripts/pod_teacher2.sh ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # On the pod: after the first teacher run finishes, generate more teacher samples for other prompts
3
+ # (small shards so a stop loses little; train.py scans shards directly).
4
+ while pgrep -f "rapid_moss.teachergen --model" > /dev/null; do sleep 15; done
5
+ cd /workspace/code
6
+ torchrun --nproc-per-node 8 -m rapid_moss.teachergen --model /workspace/models/MOSS-Image-SFT --cache /workspace/cache \
7
+ --prompts /workspace/prompts/train.jsonl --out /workspace/teacher2 --count 4000 --seed 8 --shard-size 25 \
8
+ --exclude /workspace/teacher/index.json
code/scripts/pssh.sh ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # ssh/rsync helpers for the pod in scripts/pod.env: pssh.sh CMD... | pssh.sh --push SRC DST | pssh.sh --pull SRC DST
3
+ set -euo pipefail
4
+ source "$(dirname "$0")/pod.env"
5
+ KEY=$HOME/.runpod/ssh/runpodctl-ssh-key
6
+ OPTS=(-i "$KEY" -p "$POD_PORT" -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=$HOME/.runpod/ssh/known_hosts -o ConnectTimeout=20 -o ServerAliveInterval=30)
7
+ if [[ "${1:-}" == --push ]]; then rsync -rlptz --no-owner --no-group --exclude .git -e "ssh ${OPTS[*]}" "$2" "root@$POD_HOST:$3"
8
+ elif [[ "${1:-}" == --pull ]]; then rsync -rlptz --no-owner --no-group --exclude .git -e "ssh ${OPTS[*]}" "root@$POD_HOST:$2" "$3"
9
+ else exec ssh "${OPTS[@]}" "root@$POD_HOST" "$@"; fi
code/scripts/runpod.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Minimal RunPod GraphQL client (API key from ~/.runpod/config.toml).
2
+
3
+ python scripts/runpod.py balance
4
+ python scripts/runpod.py gpus
5
+ python scripts/runpod.py create NAME --gpu "NVIDIA H100 80GB HBM3" --count 1 [--cloud COMMUNITY] [--volume 300]
6
+ python scripts/runpod.py status POD_ID # prints ssh target when running
7
+ python scripts/runpod.py stop POD_ID | terminate POD_ID
8
+ """
9
+ import argparse
10
+ import json
11
+ import re
12
+ import sys
13
+ import time
14
+ import urllib.request
15
+ from pathlib import Path
16
+
17
+ IMAGE = 'runpod/pytorch:1.4.1-cu1281-torch2130-ubuntu2404'
18
+ KEY_FILE = Path.home() / '.runpod' / 'ssh' / 'runpodctl-ssh-key'
19
+
20
+
21
+ def api_key():
22
+ text = (Path.home() / '.runpod' / 'config.toml').read_text()
23
+ return re.search(r"apikey\s*=\s*['\"]([^'\"]+)", text).group(1)
24
+
25
+
26
+ def gql(query, variables=None):
27
+ req = urllib.request.Request('https://api.runpod.io/graphql',
28
+ data=json.dumps({'query': query, 'variables': variables or {}}).encode(),
29
+ headers={'Content-Type': 'application/json', 'Authorization': f'Bearer {api_key()}',
30
+ 'User-Agent': 'rapid-moss'})
31
+ with urllib.request.urlopen(req, timeout=60) as r:
32
+ out = json.load(r)
33
+ if out.get('errors'):
34
+ raise SystemExit(json.dumps(out['errors'], indent=1))
35
+ return out['data']
36
+
37
+
38
+ def pod(pod_id):
39
+ return gql('query($id: String!) { pod(input: {podId: $id}) { id name desiredStatus costPerHr gpuCount '
40
+ 'machine { gpuDisplayName location } runtime { uptimeInSeconds ports { ip isIpPublic privatePort '
41
+ 'publicPort type } } } }', {'id': pod_id})['pod']
42
+
43
+
44
+ def ssh_target(p):
45
+ for port in (p.get('runtime') or {}).get('ports') or []:
46
+ if port['privatePort'] == 22 and port['isIpPublic']:
47
+ return port['ip'], port['publicPort']
48
+ return None
49
+
50
+
51
+ def main():
52
+ ap = argparse.ArgumentParser()
53
+ sub = ap.add_subparsers(dest='cmd', required=True)
54
+ sub.add_parser('balance')
55
+ sub.add_parser('gpus')
56
+ c = sub.add_parser('create')
57
+ c.add_argument('name')
58
+ c.add_argument('--gpu', default='NVIDIA H100 80GB HBM3')
59
+ c.add_argument('--count', type=int, default=1)
60
+ c.add_argument('--cloud', default='COMMUNITY')
61
+ c.add_argument('--volume', type=int, default=300)
62
+ c.add_argument('--disk', type=int, default=60)
63
+ for name in ('status', 'stop', 'terminate', 'wait'):
64
+ sub.add_parser(name).add_argument('pod_id')
65
+ a = ap.parse_args()
66
+ if a.cmd == 'balance':
67
+ print(json.dumps(gql('query { myself { clientBalance currentSpendPerHr pods { id name desiredStatus costPerHr } } }')['myself'], indent=1))
68
+ elif a.cmd == 'gpus':
69
+ data = gql('query { gpuTypes { id memoryInGb securePrice communityPrice lowestPrice(input:{gpuCount:1}) { stockStatus } } }')
70
+ for g in sorted(data['gpuTypes'], key=lambda g: -(g['memoryInGb'] or 0)):
71
+ if (g['memoryInGb'] or 0) >= 80:
72
+ print(f"{g['id']:<60} {g['memoryInGb']:>4} GB secure {g['securePrice']} community {g['communityPrice']} stock {(g['lowestPrice'] or {}).get('stockStatus')}")
73
+ elif a.cmd == 'create':
74
+ pub = KEY_FILE.with_suffix('.pub').read_text().strip()
75
+ q = ('mutation($in: PodFindAndDeployOnDemandInput) { podFindAndDeployOnDemand(input: $in) '
76
+ '{ id costPerHr machine { gpuDisplayName location } } }')
77
+ inp = {'cloudType': a.cloud, 'gpuCount': a.count, 'volumeInGb': a.volume, 'containerDiskInGb': a.disk,
78
+ 'gpuTypeId': a.gpu, 'name': a.name, 'imageName': IMAGE, 'ports': '22/tcp',
79
+ 'volumeMountPath': '/workspace', 'startSsh': True, 'supportPublicIp': True,
80
+ 'minCudaVersion': '12.8', 'env': [{'key': 'PUBLIC_KEY', 'value': pub}]}
81
+ print(json.dumps(gql(q, {'in': inp})['podFindAndDeployOnDemand'], indent=1))
82
+ elif a.cmd == 'status':
83
+ p = pod(a.pod_id)
84
+ print(json.dumps(p, indent=1))
85
+ t = ssh_target(p)
86
+ if t:
87
+ print(f'ssh -i {KEY_FILE} -p {t[1]} root@{t[0]}')
88
+ elif a.cmd == 'wait':
89
+ for _ in range(120):
90
+ t = ssh_target(pod(a.pod_id))
91
+ if t:
92
+ print(f'{t[0]} {t[1]}')
93
+ return
94
+ time.sleep(10)
95
+ sys.exit('pod did not expose ssh in 20 min')
96
+ elif a.cmd == 'stop':
97
+ print(gql('mutation($id: String!) { podStop(input: {podId: $id}) { id desiredStatus } }', {'id': a.pod_id}))
98
+ elif a.cmd == 'terminate':
99
+ print(gql('mutation($id: String!) { podTerminate(input: {podId: $id}) }', {'id': a.pod_id}))
100
+
101
+
102
+ if __name__ == '__main__':
103
+ main()
code/scripts/watchdog.sh ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Local: stop the pod (keeps its volume) when the RunPod balance drops below $MIN (default 6).
3
+ cd "$(dirname "$0")/.."
4
+ source scripts/pod.env
5
+ MIN=${MIN:-6}
6
+ while true; do
7
+ OUT=$(nix shell nixpkgs#python3 -c python3 scripts/runpod.py balance 2>/dev/null) || { sleep 120; continue; }
8
+ BAL=$(echo "$OUT" | grep clientBalance | grep -o '[0-9.]*')
9
+ RUN=$(echo "$OUT" | grep -c '"RUNNING"')
10
+ echo "$(date +%T) balance=$BAL running=$RUN"
11
+ [[ "$RUN" == 0 ]] && { echo "no running pod, exit"; exit 0; }
12
+ if awk "BEGIN{exit !($BAL < $MIN)}"; then
13
+ echo "balance below $MIN, stopping $POD_ID"; nix shell nixpkgs#python3 -c python3 scripts/runpod.py stop "$POD_ID"; exit 0
14
+ fi
15
+ sleep 300
16
+ done
code/tests/test_dmad.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CPU check of the DMAD critic wiring (fp32, tiny latent).
2
+
3
+ python tests/test_dmad.py MODEL_DIR LODESTONES_DIR
4
+ Generator phase (critic frozen): gradients reach the generator and nothing reaches the critic.
5
+ Critic phase: gradients reach the critic head and its LoRA B on blocks >= start only.
6
+ """
7
+ import sys
8
+
9
+ import torch
10
+ from torch.nn import functional as F
11
+
12
+ import rapid_moss.model as M
13
+ from rapid_moss.nets import Critic, Generator
14
+
15
+ torch.manual_seed(0)
16
+ model_dir, lode_dir = sys.argv[1], sys.argv[2]
17
+ M.BF16 = torch.float32
18
+ release = M.load_release_module(model_dir)
19
+ tr = release.MOSSImageTransformer.from_pretrained(f'{model_dir}/transformer', torch_dtype=torch.float32,
20
+ local_files_only=True)
21
+ model = M.Moss(tr, release)
22
+ ctx = model.context(torch.randn(10, 4096) * 2)
23
+ z = torch.randn(1, 128, 4, 6)
24
+
25
+ gen = Generator(model, 128, 128, 4)
26
+ gen.load_lodestones(lode_dir)
27
+ critic = Critic(model, 16, 16, start=30, hidden=64)
28
+ assert critic.lora.A['q'].shape[0] == 12
29
+
30
+ # generator phase
31
+ critic.requires_grad_(False)
32
+ x0 = gen.step(model, z, gen.rollout(model, z, ctx, 1), 1, ctx, ckpt=True)
33
+ logit = critic(model, 0.6 * x0 + 0.4 * torch.randn_like(z), 400.0, ctx, ckpt=True)
34
+ assert logit.shape == (1,)
35
+ (-logit.mean()).backward()
36
+ assert all(p.grad is not None and p.grad.abs().sum() > 0 for p in gen.parameters()), 'generator grads missing'
37
+ assert all(p.grad is None for p in critic.parameters()), 'critic got generator-phase grads'
38
+ print('generator phase OK, logit', logit.item())
39
+
40
+ # critic phase
41
+ critic.requires_grad_(True)
42
+ for p in gen.parameters():
43
+ p.grad = None
44
+ d_q = critic(model, 0.5 * torch.randn_like(z) + 0.5 * z, 300.0, ctx, ckpt=True)
45
+ d_g = critic(model, 0.5 * x0.detach() + 0.5 * torch.randn_like(z), 300.0, ctx, ckpt=True)
46
+ (F.softplus(-d_q) + F.softplus(d_g)).mean().backward()
47
+ assert all(p.grad is None for p in gen.parameters()), 'generator got critic-phase grads'
48
+ assert all(p.grad.abs().sum() > 0 for p in critic.head.parameters()) and critic.out.weight.grad.abs().sum() > 0
49
+ b_rows = (critic.lora.B['q'].grad.flatten(1).abs().sum(1) > 0).tolist()
50
+ print('critic LoRA B grad per block (30..41):', b_rows)
51
+ assert all(b_rows)
52
+ print('OK')
code/tests/test_equiv.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CPU check of rapid_moss.model against the release MOSSImageTransformer.
2
+
3
+ python tests/test_equiv.py MODEL_DIR LODESTONES_DIR
4
+ Runs everything in fp32 (rapid_moss.model.BF16 patched) so the logic must match to ~1e-5:
5
+ plain forward, negative-branch skip 22, the lodestones generator path (fp32 time path +
6
+ unmerged LoRA hooks, as its model card describes), discriminator taps and a gradient check.
7
+ """
8
+ import sys
9
+ import time
10
+
11
+ import torch
12
+ from safetensors.torch import load_file
13
+
14
+ import rapid_moss.model as M
15
+ from rapid_moss.nets import Discriminator, FakeScore, Generator
16
+
17
+ torch.manual_seed(0)
18
+ model_dir, lode_dir = sys.argv[1], sys.argv[2]
19
+ M.BF16 = torch.float32
20
+ release = M.load_release_module(model_dir)
21
+ t0 = time.time()
22
+ tr = release.MOSSImageTransformer.from_pretrained(f'{model_dir}/transformer', torch_dtype=torch.float32,
23
+ local_files_only=True)
24
+ model = M.Moss(tr, release)
25
+ print(f'load {time.time() - t0:.1f}s')
26
+
27
+ h, w, L = 6, 10, 12
28
+ latent = torch.randn(1, 128, h, w)
29
+ emb = torch.randn(L, 4096) * 2
30
+ ctx = model.context(emb)
31
+
32
+
33
+ def rel(a, b):
34
+ return ((a - b).norm() / b.norm()).item()
35
+
36
+
37
+ with torch.no_grad():
38
+ for t in (999.0, 500.0, 37.0):
39
+ ref = tr(latent, t, emb[None]).sample.float()
40
+ got = model(latent, t, ctx)
41
+ print(f'forward t={t}: rel {rel(got, ref):.2e}')
42
+ assert rel(got, ref) < 1e-4
43
+ ref = tr(latent, 600.0, emb[None], skip_layers=[22]).sample.float()
44
+ got = model(latent, 600.0, ctx, skip=(22,))
45
+ print(f'skip22: rel {rel(got, ref):.2e} (vs no-skip {rel(got, model(latent, 600.0, ctx)):.2e})')
46
+ assert rel(got, ref) < 1e-4
47
+
48
+ # lodestones generator: release model + replaced time path + LoRA hooks (model card recipe)
49
+ gen = Generator(model, 128, 128, 4)
50
+ gen.load_lodestones(lode_dir)
51
+ te = load_file(f'{lode_dir}/time_embedding.safetensors')
52
+ lora = load_file(f'{lode_dir}/lora.safetensors')
53
+ import copy
54
+ tr2 = copy.deepcopy(tr)
55
+ tr2.load_state_dict(te, strict=False)
56
+ for i, layer in enumerate(tr2.layers):
57
+ for name, lin in layer.proj.items():
58
+ p = f'diffusion_model.layers.{i}.proj.{name}'
59
+ down, up, alpha = lora[p + '.lora_down.weight'], lora[p + '.lora_up.weight'], lora[p + '.alpha']
60
+ scale = float(alpha) / down.shape[0]
61
+ lin.register_forward_hook(lambda m, inp, out, d=down, u=up, s=scale: out + s * (inp[0] @ d.t()) @ u.t())
62
+ Q = load_file(f'{lode_dir}/dataspace.safetensors')['latent_mix.Q']
63
+ codes = load_file(f'{lode_dir}/dataspace.safetensors')['step_codes']
64
+ z = torch.randn(1, 128, h, w)
65
+ x0_ref = torch.zeros_like(z)
66
+ x0_got = torch.zeros_like(z)
67
+ for r in range(3):
68
+ x = z + torch.einsum('oc,bchw->bohw', Q, x0_ref)
69
+ x0_ref = z - tr2(x, codes[r], emb[None]).sample.float()
70
+ x0_got = gen.step(model, z, x0_got, r, ctx)
71
+ print(f'generator r={r}: rel {rel(x0_got, x0_ref):.2e}')
72
+ assert rel(x0_got, x0_ref) < 1e-4
73
+ del tr2
74
+
75
+ disc = Discriminator(model.dim, (5, 11), 256)
76
+ feats = model(latent, 300.0, ctx, taps=disc.taps)
77
+ print('taps', {k: tuple(v.shape) for k, v in feats.items()}, 'logits', tuple(disc(feats, h, w).shape))
78
+
79
+ # gradients: generator step through checkpointed blocks, fake score, D input-gradient
80
+ fake = FakeScore(model, 16, 16, 4)
81
+ x0 = gen.step(model, z, torch.randn_like(z), 1, ctx, ckpt=True)
82
+ x0.backward(torch.randn_like(x0))
83
+ nz = {n: p.grad.abs().sum().item() > 0 for n, p in gen.named_parameters()}
84
+ print('gen grads nonzero:', all(nz.values()), [n for n, v in nz.items() if not v][:5])
85
+ assert all(nz.values())
86
+ v = fake(model, latent, 400.0, ctx, 2, ckpt=True)
87
+ v.square().mean().backward()
88
+ print('fake r_emb grad rows nonzero:', (fake.r_emb.grad.abs().sum(1) > 0).tolist())
89
+ leaf = latent.clone().requires_grad_(True)
90
+ g, = torch.autograd.grad(-disc(model(leaf, 300.0, ctx, taps=disc.taps, ckpt=True), h, w).mean(), leaf)
91
+ print('D input grad norm', g.norm().item())
92
+ assert g.norm() > 0
93
+ print('OK')
dataspace.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d19f0e695bb26886cfce57441fd412f358e159d6eec8555ec1a71daaf30a7968
3
+ size 65784
eval/eval_prompts.jsonl ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"id": "anime-girl", "src": "eval", "prompt": "A high-quality anime illustration of a girl with long silver hair and teal eyes, wearing a yellow raincoat, standing at a rainy bus stop at night, neon reflections on wet asphalt, detailed cel shading, Japanese anime style", "w": 1024, "h": 1024}
2
+ {"id": "anime-tags", "src": "eval", "prompt": "1girl, solo, school uniform, cherry blossoms, wind, smile, looking at viewer, anime style, masterpiece, best quality, detailed eyes, spring, blue sky", "w": 1024, "h": 1024}
3
+ {"id": "chibi", "src": "eval", "prompt": "A cute chibi anime character, a small fox girl with big round eyes holding a strawberry daifuku, pastel colors, white background, sticker style illustration", "w": 1024, "h": 1024}
4
+ {"id": "thick-paint", "src": "eval", "prompt": "Painterly semi-realistic anime illustration of a swordsman in a bamboo forest at dawn, dramatic lighting, thick brush strokes, Japanese fantasy key visual", "w": 1024, "h": 1024}
5
+ {"id": "manga-mono", "src": "eval", "prompt": "Black and white Japanese manga panel, screentone shading, a high school boy running on a rooftop with speed lines, dynamic perspective, ink lineart", "w": 1024, "h": 1024}
6
+ {"id": "jp-sign", "src": "eval", "prompt": "A traditional Japanese ramen shop at night, a wooden sign above the door clearly reads \"麺処 はやて\" in bold brush calligraphy, a red paper lantern with the text \"営業中\"", "w": 1024, "h": 1024}
7
+ {"id": "jp-bubble", "src": "eval", "prompt": "An anime illustration of a girl surprised in a classroom, with a white manga speech bubble that says \"えっ、本当に!?\" in clear Japanese text", "w": 1024, "h": 1024}
8
+ {"id": "jp-note", "src": "eval", "prompt": "A close-up of a handwritten sticky note on a refrigerator, written in Japanese: \"牛乳を買う\n卵は二パック\n夜七時に帰ります\"", "w": 1024, "h": 1024}
9
+ {"id": "jp-cover", "src": "eval", "prompt": "A light novel cover illustration, anime girl mage casting a spell, the title text \"疾風の魔導書\" in large stylized Japanese letters at the top, and \"第一巻\" in the corner", "w": 1024, "h": 1024}
10
+ {"id": "jp-poster", "src": "eval", "prompt": "A Japanese summer festival poster, fireworks over a river, vertical text on the right side reading \"夏祭り 花火大会\" and the date \"八月十五日\" at the bottom", "w": 1024, "h": 1024}
11
+ {"id": "layout-manga", "src": "eval", "prompt": "{\"summary\": {\"meta\": [\"manga\"], \"canvas\": \"single\", \"caption\": \"A two-panel anime manga page in color. Top panel: a girl with short black hair waves at a friend in front of a school gate in the morning. Bottom panel: her friend, a girl with long brown hair, smiles back. Each panel has a white speech bubble with Japanese text.\"}, \"items\": [{\"label\": \"object\", \"caption\": \"Top panel: a girl with short black hair in a sailor school uniform waving, school gate behind her.\", \"bbox\": [-512, -512, -16, 512]}, {\"label\": \"object\", \"caption\": \"Bottom panel: a girl with long brown hair in a sailor school uniform smiling.\", \"bbox\": [16, -512, 512, 512]}, {\"label\": \"text\", \"caption\": \"Black Japanese text inside a white round speech bubble at the top right of the top panel.\", \"text\": \"おはよう!\", \"bbox\": [-460, 180, -330, 460]}, {\"label\": \"text\", \"caption\": \"Black Japanese text inside a white round speech bubble at the bottom left of the bottom panel.\", \"text\": \"今日もいい天気だね\", \"bbox\": [300, -470, 440, -80]}], \"bbox_coord_space\": \"abs_centralized\", \"bbox_format\": \"yxyx\"}", "w": 1024, "h": 1024}
12
+ {"id": "rain-neon", "src": "eval", "prompt": "masterpiece, best quality, highres, 1girl, solo, long silver hair, glowing blue eyes, oversized black hoodie, holding a transparent umbrella, neon-lit Tokyo alley at night, heavy rain, reflections on wet ground, cinematic lighting, depth of field, light particles, detailed background, vibrant colors", "w": 832, "h": 1216}
13
+ {"id": "sky-flight", "src": "eval", "prompt": "A breathtaking anime key visual of a girl in a white sundress jumping off a cliff into a vast summer sky, dynamic low-angle perspective, towering cumulonimbus clouds, sunbeams, flying birds, hair and dress fluttering in the wind, ultra detailed, vivid saturated colors, sense of freedom", "w": 832, "h": 1216}
14
+ {"id": "duo-fantasy", "src": "eval", "prompt": "masterpiece, best quality, 2girls, fantasy, a knight girl with golden armor and a witch girl with a large hat standing back to back, magic circles glowing around them, battlefield with ruins, dramatic rim lighting, sparks, highly detailed armor and fabric, epic composition", "w": 832, "h": 1216}
15
+ {"id": "wafu", "src": "eval", "prompt": "Japanese-style fantasy illustration of a fox spirit shrine maiden with white hair and red eyes, nine fluffy tails, holding a paper lantern, red torii gates at night, falling maple leaves, blue will-o'-the-wisps, intricate kimono patterns, highly detailed, soft glowing light", "w": 832, "h": 1216}
16
+ {"id": "transparent", "src": "eval", "prompt": "A delicate anime illustration with a transparent watercolor feeling, a girl with light blue hair lying in shallow water surrounded by floating flowers and small fish, sunlight caustics, pastel colors, soft focus, dreamy atmosphere, extremely detailed eyes", "w": 832, "h": 1216}
17
+ {"id": "scenery", "src": "eval", "prompt": "best quality, highres, scenery, 1girl, small figure, standing on a railway platform in the countryside at sunset, an old train arriving, power lines, summer clouds, orange and purple sky, lens flare, nostalgic atmosphere, highly detailed background art", "w": 832, "h": 1216}
images/compare-1.jpg ADDED

Git LFS Details

  • SHA256: 573b88ce825be3195030dfa493d41ee277437a102562a0c21cc21527cb37703a
  • Pointer size: 132 Bytes
  • Size of remote file: 1.25 MB
images/compare-2.jpg ADDED

Git LFS Details

  • SHA256: 5d22398dd13b8cb6c4c83b8f14f0cee67801b693e26f9c056d535a639673012f
  • Pointer size: 132 Bytes
  • Size of remote file: 1.11 MB
images/compare-3.jpg ADDED

Git LFS Details

  • SHA256: 5d4c25546dd2f69244dd68d3f1009a042a42dbd7129ef82c609ffd9bc3d6f7d2
  • Pointer size: 131 Bytes
  • Size of remote file: 759 kB
images/training/000300.jpg ADDED

Git LFS Details

  • SHA256: 7741b1a0d41ec3b8e0e6bc5d46553cca9972d433ac319617b6d0205c869db969
  • Pointer size: 131 Bytes
  • Size of remote file: 430 kB
images/training/000500.jpg ADDED

Git LFS Details

  • SHA256: d1bcacee6ee9a4f7820ca17eb638463d3bce61b25523e1c17782c12f8d7a1a34
  • Pointer size: 131 Bytes
  • Size of remote file: 442 kB
images/training/000800.jpg ADDED

Git LFS Details

  • SHA256: 78bfb73a3c06cadc425da2733c13e1255897e75959a4721fc9be0cec9642238a
  • Pointer size: 131 Bytes
  • Size of remote file: 472 kB
images/training/init.jpg ADDED

Git LFS Details

  • SHA256: cd9c128e6d91d429b7dd6354415758a6cb5219241c7217a3d77a36345489426e
  • Pointer size: 131 Bytes
  • Size of remote file: 390 kB
inference.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """4-step MOSS-Image-SFT with the rapid-moss adapter (data-space sampler, no CFG).
2
+
3
+ pip install "diffusers>=0.38" "transformers>=5.3" accelerate safetensors huggingface_hub pillow
4
+ python inference.py --prompt "an anime girl in a yellow raincoat at a rainy bus stop" --out out.png
5
+
6
+ The base stays bf16; the LoRA is applied unmerged in fp32 (its delta is smaller than bf16 rounding),
7
+ and the step embedder + modulation biases are replaced by fp32 tensors. Sampler:
8
+ x0 = 0; for r in range(steps): x = z + Q x0; x0 = z - transformer(x, codes[r], text)
9
+ with one fixed noise z, no noise re-injection and no timestep schedule.
10
+ """
11
+ import argparse
12
+ import os
13
+
14
+ import torch
15
+ from diffusers import DiffusionPipeline
16
+ from huggingface_hub import snapshot_download
17
+ from safetensors.torch import load_file
18
+
19
+
20
+ def load(base, adapter, device):
21
+ custom = os.path.join(base, 'pipeline_moss_image.py') if os.path.isdir(base) else 'pipeline_moss_image'
22
+ pipe = DiffusionPipeline.from_pretrained(base, custom_pipeline=custom, trust_remote_code=True,
23
+ torch_dtype={'default': torch.bfloat16, 'vae': torch.float32})
24
+ pipe.to(device)
25
+ tr = pipe.transformer
26
+ if not os.path.isdir(adapter):
27
+ adapter = snapshot_download(adapter, allow_patterns=['*.safetensors'])
28
+ for name in ('time_embedding', 'shared_block_modulation', 'video_head_modulation'):
29
+ getattr(tr, name).float()
30
+ time_path = load_file(os.path.join(adapter, 'time_embedding.safetensors'), device=str(device))
31
+ result = tr.load_state_dict(time_path, strict=False)
32
+ assert not result.unexpected_keys, result.unexpected_keys
33
+ lora = load_file(os.path.join(adapter, 'lora.safetensors'), device=str(device))
34
+ for i, layer in enumerate(tr.layers):
35
+ for name, lin in layer.proj.items():
36
+ key = f'diffusion_model.layers.{i}.proj.{name}'
37
+ down, up = lora[key + '.lora_down.weight'], lora[key + '.lora_up.weight']
38
+ scale = float(lora[key + '.alpha']) / down.shape[0]
39
+
40
+ def hook(module, inputs, output, down=down, up=up, scale=scale):
41
+ return output + (scale * ((inputs[0].float() @ down.t()) @ up.t())).to(output.dtype)
42
+ lin.register_forward_hook(hook)
43
+ ds = load_file(os.path.join(adapter, 'dataspace.safetensors'), device=str(device))
44
+ return pipe, ds['latent_mix.Q'], ds['step_codes']
45
+
46
+
47
+ @torch.no_grad()
48
+ def generate(pipe, Q, codes, prompt, width, height, steps, seed):
49
+ device = pipe._execution_device
50
+ emb, mask = pipe.encode_prompt(prompt)
51
+ z = torch.randn(1, 128, height // 16, width // 16, generator=torch.Generator('cpu').manual_seed(seed)).to(device)
52
+ x0 = torch.zeros_like(z)
53
+ for r in range(steps):
54
+ x = z + torch.einsum('oc,bchw->bohw', Q, x0)
55
+ x0 = z - pipe.transformer(x, codes[r], emb, mask).sample.float()
56
+ vae = pipe.vae
57
+ lat = x0.to(vae.dtype)
58
+ bn, eps = vae.bn, vae.config.batch_norm_eps
59
+ lat = lat * (bn.running_var + eps).sqrt().view(1, -1, 1, 1) + bn.running_mean.view(1, -1, 1, 1)
60
+ b, c, h, w = lat.shape
61
+ lat = lat.reshape(b, c // 4, 2, 2, h, w).permute(0, 1, 4, 2, 5, 3).reshape(b, c // 4, 2 * h, 2 * w)
62
+ image = vae.decode(lat, return_dict=False)[0].clamp(-1, 1)
63
+ return pipe.image_processor.postprocess(image, output_type='pil')[0]
64
+
65
+
66
+ def main():
67
+ ap = argparse.ArgumentParser()
68
+ ap.add_argument('--prompt', required=True, help='natural language, tags or a MOSS JSON layout string')
69
+ ap.add_argument('--width', type=int, default=1024)
70
+ ap.add_argument('--height', type=int, default=1024)
71
+ ap.add_argument('--steps', type=int, default=4, help='1-4; each prefix of the 4 steps is trained')
72
+ ap.add_argument('--seed', type=int, default=42)
73
+ ap.add_argument('--out', default='out.png')
74
+ ap.add_argument('--base', default='OpenMOSS-Team/MOSS-Image-SFT')
75
+ ap.add_argument('--adapter', default='darask0/rapid-moss')
76
+ ap.add_argument('--device', default='cuda')
77
+ args = ap.parse_args()
78
+ if args.width % 16 or args.height % 16:
79
+ raise SystemExit('width and height must be multiples of 16')
80
+ pipe, Q, codes = load(args.base, args.adapter, torch.device(args.device))
81
+ if not 1 <= args.steps <= len(codes):
82
+ raise SystemExit(f'steps must be 1..{len(codes)}')
83
+ generate(pipe, Q, codes, args.prompt, args.width, args.height, args.steps, args.seed).save(args.out)
84
+ print(args.out)
85
+
86
+
87
+ if __name__ == '__main__':
88
+ main()
logs/baseline_timings_h100.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "MOSS-Image-SFT-8-steps-dmd-4step": 0.805,
3
+ "MOSS-Image-SFT-8-steps-dmd-8step": 1.544,
4
+ "teacher50-cfg3": 16.188
5
+ }
logs/dmad_config.json ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_dir": "/workspace/models/MOSS-Image-SFT",
3
+ "lodestones_dir": "/workspace/models/MOSS-Image-SFT-8-steps-dmd",
4
+ "cache_dir": "/workspace/cache",
5
+ "prompts": "/workspace/prompts/train.jsonl",
6
+ "eval_prompts": "/workspace/prompts/eval.jsonl",
7
+ "out_dir": "/workspace/runs/dmad",
8
+ "steps": 4,
9
+ "gen_rank": 128,
10
+ "gen_alpha": 128,
11
+ "fake_rank": 64,
12
+ "fake_alpha": 64,
13
+ "disc_taps": [
14
+ 5,
15
+ 11,
16
+ 17,
17
+ 23,
18
+ 29,
19
+ 35
20
+ ],
21
+ "disc_hidden": 256,
22
+ "iterations": 2500,
23
+ "micro": 2,
24
+ "fake_per_gen": 2,
25
+ "fake_warmup": 200,
26
+ "lr_gen": 2e-05,
27
+ "lr_time": 2e-05,
28
+ "lr_q": 2e-05,
29
+ "lr_fake": 0.0001,
30
+ "lr_disc": 5e-05,
31
+ "betas": [
32
+ 0.0,
33
+ 0.99
34
+ ],
35
+ "ema": 0.998,
36
+ "cfg": [
37
+ 3.0,
38
+ 3.0
39
+ ],
40
+ "skip_neg": [
41
+ 22
42
+ ],
43
+ "tau_beta": [
44
+ 2.5,
45
+ 1.0
46
+ ],
47
+ "tau_clamp": [
48
+ 0.03,
49
+ 0.985
50
+ ],
51
+ "dt": 0.015625,
52
+ "adv_weight": 0.5,
53
+ "adv_start": 50,
54
+ "adv_warmup": 200,
55
+ "disc_real": "adm",
56
+ "teacher_dir": [
57
+ "/workspace/teacher",
58
+ "/workspace/teacher2"
59
+ ],
60
+ "p_gan": 0.5,
61
+ "gan_t": [
62
+ 0.02,
63
+ 0.98
64
+ ],
65
+ "clip_gen": 0.0,
66
+ "buckets": [
67
+ [
68
+ 1024,
69
+ 1024,
70
+ 0.35
71
+ ],
72
+ [
73
+ 896,
74
+ 1152,
75
+ 0.15
76
+ ],
77
+ [
78
+ 832,
79
+ 1216,
80
+ 0.15
81
+ ],
82
+ [
83
+ 1152,
84
+ 896,
85
+ 0.1
86
+ ],
87
+ [
88
+ 1216,
89
+ 832,
90
+ 0.1
91
+ ],
92
+ [
93
+ 768,
94
+ 1344,
95
+ 0.075
96
+ ],
97
+ [
98
+ 1344,
99
+ 768,
100
+ 0.075
101
+ ]
102
+ ],
103
+ "method": "dmad",
104
+ "critic_rank": 128,
105
+ "critic_alpha": 128,
106
+ "critic_start": 21,
107
+ "critic_hidden": 512,
108
+ "critic_shift": 5.0,
109
+ "critic_sigma": [
110
+ 0.02,
111
+ 0.98
112
+ ],
113
+ "lr_critic": 4e-05,
114
+ "wd_critic": 0.01,
115
+ "critic_warmup": 50,
116
+ "ckpt": true,
117
+ "seed": 0,
118
+ "log_every": 10,
119
+ "sample_every": 100,
120
+ "save_every": 200,
121
+ "export_every": 500,
122
+ "eval_count": 8,
123
+ "eval_seed": 1234
124
+ }
logs/dmad_train_log.jsonl ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"it": 10, "gen_updates": 0, "s_per_it": 3.51, "mem_gib": 20.4, "c_acc": 0.503125, "c_loss": 1.472324, "d_gen": -0.026185, "d_teacher": 0.02548, "g_logit": 0.0}
2
+ {"it": 20, "gen_updates": 0, "s_per_it": 3.4, "mem_gib": 20.4, "c_acc": 0.55, "c_loss": 1.393549, "d_gen": -0.070432, "d_teacher": 0.090518, "g_logit": 0.0}
3
+ {"it": 30, "gen_updates": 0, "s_per_it": 3.41, "mem_gib": 20.4, "c_acc": 0.5875, "c_loss": 1.374437, "d_gen": -0.295139, "d_teacher": 0.018142, "g_logit": 0.0}
4
+ {"it": 40, "gen_updates": 0, "s_per_it": 3.53, "mem_gib": 20.4, "c_acc": 0.640625, "c_loss": 1.303367, "d_gen": -0.228213, "d_teacher": 0.229624, "g_logit": 0.0}
5
+ {"it": 50, "gen_updates": 0, "s_per_it": 3.44, "mem_gib": 20.5, "c_acc": 0.65625, "c_loss": 1.230491, "d_gen": -0.380138, "d_teacher": 0.359761, "g_logit": 0.0}
6
+ {"it": 60, "gen_updates": 10, "s_per_it": 5.85, "mem_gib": 24.9, "c_acc": 0.60625, "c_loss": 1.320506, "d_gen": -0.204005, "d_teacher": 0.280464, "g_logit": -0.257044, "gnorm": 344.827544}
7
+ {"it": 70, "gen_updates": 20, "s_per_it": 5.89, "mem_gib": 24.9, "c_acc": 0.6125, "c_loss": 1.321517, "d_gen": -0.27649, "d_teacher": 0.113134, "g_logit": -0.267412, "gnorm": 169.696696}
8
+ {"it": 80, "gen_updates": 30, "s_per_it": 5.89, "mem_gib": 24.9, "c_acc": 0.61875, "c_loss": 1.258887, "d_gen": -0.346907, "d_teacher": 0.261502, "g_logit": -0.35437, "gnorm": 634.010178}
9
+ {"it": 90, "gen_updates": 40, "s_per_it": 5.76, "mem_gib": 24.9, "c_acc": 0.678125, "c_loss": 1.195195, "d_gen": -0.554969, "d_teacher": 0.365944, "g_logit": -0.459122, "gnorm": 157.662113}
10
+ {"it": 100, "gen_updates": 50, "s_per_it": 5.86, "mem_gib": 24.9, "c_acc": 0.65, "c_loss": 1.271955, "d_gen": -0.339471, "d_teacher": 0.444618, "g_logit": -0.380601, "gnorm": 398.204028}
11
+ {"it": 110, "gen_updates": 60, "s_per_it": 6.82, "mem_gib": 24.9, "c_acc": 0.703125, "c_loss": 1.135317, "d_gen": -0.523898, "d_teacher": 0.40571, "g_logit": -0.523776, "gnorm": 515.074624}
12
+ {"it": 120, "gen_updates": 70, "s_per_it": 5.75, "mem_gib": 24.9, "c_acc": 0.7, "c_loss": 1.107486, "d_gen": -0.584106, "d_teacher": 0.698363, "g_logit": -0.60042, "gnorm": 555.140576}
13
+ {"it": 130, "gen_updates": 80, "s_per_it": 5.81, "mem_gib": 24.9, "c_acc": 0.70625, "c_loss": 1.103744, "d_gen": -0.60869, "d_teacher": 0.821214, "g_logit": -0.620043, "gnorm": 425.754166}
14
+ {"it": 140, "gen_updates": 90, "s_per_it": 5.71, "mem_gib": 24.9, "c_acc": 0.784375, "c_loss": 0.8933, "d_gen": -1.041128, "d_teacher": 0.990952, "g_logit": -0.890582, "gnorm": 666.534967}
15
+ {"it": 150, "gen_updates": 100, "s_per_it": 5.76, "mem_gib": 24.9, "c_acc": 0.78125, "c_loss": 0.887413, "d_gen": -1.197625, "d_teacher": 0.985895, "g_logit": -1.082821, "gnorm": 379.410664}
16
+ {"it": 160, "gen_updates": 110, "s_per_it": 5.72, "mem_gib": 24.9, "c_acc": 0.790625, "c_loss": 0.856912, "d_gen": -1.541816, "d_teacher": 1.098191, "g_logit": -1.66994, "gnorm": 449.519646}
17
+ {"it": 170, "gen_updates": 120, "s_per_it": 5.7, "mem_gib": 24.9, "c_acc": 0.790625, "c_loss": 0.91846, "d_gen": -1.285428, "d_teacher": 1.172357, "g_logit": -1.261887, "gnorm": 269.401883}
18
+ {"it": 180, "gen_updates": 130, "s_per_it": 5.77, "mem_gib": 24.9, "c_acc": 0.84375, "c_loss": 0.797819, "d_gen": -1.637835, "d_teacher": 1.326887, "g_logit": -1.754513, "gnorm": 2882.158878}
19
+ {"it": 190, "gen_updates": 140, "s_per_it": 5.77, "mem_gib": 24.9, "c_acc": 0.825, "c_loss": 0.761074, "d_gen": -1.504383, "d_teacher": 1.440473, "g_logit": -1.562236, "gnorm": 861.8714}
20
+ {"it": 200, "gen_updates": 150, "s_per_it": 5.74, "mem_gib": 24.9, "c_acc": 0.91875, "c_loss": 0.463252, "d_gen": -2.063986, "d_teacher": 1.998507, "g_logit": -1.98676, "gnorm": 318.910345}
21
+ {"it": 210, "gen_updates": 160, "s_per_it": 7.69, "mem_gib": 24.9, "c_acc": 0.8375, "c_loss": 0.818473, "d_gen": -1.431804, "d_teacher": 1.621684, "g_logit": -1.605268, "gnorm": 399.719297}
22
+ {"it": 220, "gen_updates": 170, "s_per_it": 5.71, "mem_gib": 24.9, "c_acc": 0.85, "c_loss": 0.717284, "d_gen": -1.603106, "d_teacher": 1.577599, "g_logit": -1.603056, "gnorm": 452.001189}
23
+ {"it": 230, "gen_updates": 180, "s_per_it": 5.69, "mem_gib": 24.9, "c_acc": 0.875, "c_loss": 0.64667, "d_gen": -1.80143, "d_teacher": 1.676809, "g_logit": -1.889914, "gnorm": 366.318943}
24
+ {"it": 240, "gen_updates": 190, "s_per_it": 5.66, "mem_gib": 24.9, "c_acc": 0.8625, "c_loss": 0.639551, "d_gen": -1.8087, "d_teacher": 1.857835, "g_logit": -1.802448, "gnorm": 605.091997}
25
+ {"it": 250, "gen_updates": 200, "s_per_it": 5.71, "mem_gib": 24.9, "c_acc": 0.90625, "c_loss": 0.545975, "d_gen": -2.125394, "d_teacher": 1.798105, "g_logit": -2.117861, "gnorm": 649.524585}
26
+ {"it": 260, "gen_updates": 210, "s_per_it": 5.74, "mem_gib": 24.9, "c_acc": 0.9125, "c_loss": 0.484202, "d_gen": -2.16631, "d_teacher": 2.169687, "g_logit": -2.021578, "gnorm": 499.114932}
27
+ {"it": 270, "gen_updates": 220, "s_per_it": 5.7, "mem_gib": 24.9, "c_acc": 0.959375, "c_loss": 0.270355, "d_gen": -2.670422, "d_teacher": 2.456078, "g_logit": -2.582783, "gnorm": 495.963659}
28
+ {"it": 280, "gen_updates": 230, "s_per_it": 5.67, "mem_gib": 24.9, "c_acc": 0.925, "c_loss": 0.35589, "d_gen": -2.598104, "d_teacher": 2.468642, "g_logit": -2.570019, "gnorm": 357.345479}
29
+ {"it": 290, "gen_updates": 240, "s_per_it": 5.72, "mem_gib": 24.9, "c_acc": 0.978125, "c_loss": 0.224288, "d_gen": -3.003006, "d_teacher": 2.784076, "g_logit": -2.92619, "gnorm": 74.857542}
30
+ {"it": 300, "gen_updates": 250, "s_per_it": 5.69, "mem_gib": 24.9, "c_acc": 0.9875, "c_loss": 0.142997, "d_gen": -3.190769, "d_teacher": 3.069393, "g_logit": -3.20608, "gnorm": 69.31735}
31
+ {"it": 310, "gen_updates": 260, "s_per_it": 6.64, "mem_gib": 24.9, "c_acc": 0.9, "c_loss": 0.551212, "d_gen": -2.347602, "d_teacher": 2.404747, "g_logit": -2.188123, "gnorm": 342.295467}
32
+ {"it": 320, "gen_updates": 270, "s_per_it": 5.68, "mem_gib": 24.9, "c_acc": 0.91875, "c_loss": 0.433934, "d_gen": -2.529411, "d_teacher": 2.414225, "g_logit": -2.553354, "gnorm": 206.773278}
33
+ {"it": 330, "gen_updates": 280, "s_per_it": 5.7, "mem_gib": 24.9, "c_acc": 0.84375, "c_loss": 0.779442, "d_gen": -1.927769, "d_teacher": 1.969032, "g_logit": -2.008004, "gnorm": 551.766666}
34
+ {"it": 340, "gen_updates": 290, "s_per_it": 5.64, "mem_gib": 24.9, "c_acc": 0.85625, "c_loss": 0.71635, "d_gen": -1.580061, "d_teacher": 1.732232, "g_logit": -1.535344, "gnorm": 572.014401}
35
+ {"it": 350, "gen_updates": 300, "s_per_it": 5.7, "mem_gib": 24.9, "c_acc": 0.784375, "c_loss": 0.976114, "d_gen": -1.113697, "d_teacher": 0.95776, "g_logit": -1.236905, "gnorm": 1454.895154}
36
+ {"it": 360, "gen_updates": 310, "s_per_it": 5.65, "mem_gib": 24.9, "c_acc": 0.765625, "c_loss": 0.824124, "d_gen": -1.330074, "d_teacher": 1.316943, "g_logit": -1.403452, "gnorm": 1251.758484}
37
+ {"it": 370, "gen_updates": 320, "s_per_it": 5.64, "mem_gib": 24.9, "c_acc": 0.696875, "c_loss": 1.100472, "d_gen": -0.896046, "d_teacher": 0.928468, "g_logit": -0.96601, "gnorm": 605.353458}
38
+ {"it": 380, "gen_updates": 330, "s_per_it": 5.68, "mem_gib": 24.9, "c_acc": 0.778125, "c_loss": 0.887347, "d_gen": -1.422161, "d_teacher": 1.172825, "g_logit": -1.269689, "gnorm": 1429.191895}
39
+ {"it": 390, "gen_updates": 340, "s_per_it": 5.67, "mem_gib": 24.9, "c_acc": 0.8625, "c_loss": 0.707317, "d_gen": -1.915341, "d_teacher": 1.878058, "g_logit": -1.860343, "gnorm": 797.769325}
40
+ {"it": 400, "gen_updates": 350, "s_per_it": 5.67, "mem_gib": 24.9, "c_acc": 0.734375, "c_loss": 0.974438, "d_gen": -1.03391, "d_teacher": 1.058606, "g_logit": -0.970323, "gnorm": 591.646497}
41
+ {"it": 410, "gen_updates": 360, "s_per_it": 7.69, "mem_gib": 24.9, "c_acc": 0.79375, "c_loss": 0.89193, "d_gen": -1.427567, "d_teacher": 1.424491, "g_logit": -1.53325, "gnorm": 596.328967}
42
+ {"it": 420, "gen_updates": 370, "s_per_it": 5.71, "mem_gib": 24.9, "c_acc": 0.778125, "c_loss": 0.843672, "d_gen": -1.146293, "d_teacher": 1.200306, "g_logit": -1.091098, "gnorm": 491.311426}
43
+ {"it": 430, "gen_updates": 380, "s_per_it": 5.63, "mem_gib": 24.9, "c_acc": 0.80625, "c_loss": 0.798015, "d_gen": -1.562343, "d_teacher": 1.305641, "g_logit": -1.485226, "gnorm": 969.241519}
44
+ {"it": 440, "gen_updates": 390, "s_per_it": 5.67, "mem_gib": 24.9, "c_acc": 0.80625, "c_loss": 0.867097, "d_gen": -1.602022, "d_teacher": 1.307751, "g_logit": -1.710642, "gnorm": 1851.946349}
45
+ {"it": 450, "gen_updates": 400, "s_per_it": 5.66, "mem_gib": 24.9, "c_acc": 0.84375, "c_loss": 0.640278, "d_gen": -1.727461, "d_teacher": 1.752258, "g_logit": -1.790782, "gnorm": 1126.413405}
46
+ {"it": 460, "gen_updates": 410, "s_per_it": 5.63, "mem_gib": 24.9, "c_acc": 0.859375, "c_loss": 0.608079, "d_gen": -1.933124, "d_teacher": 1.953212, "g_logit": -2.132834, "gnorm": 316.285908}
47
+ {"it": 470, "gen_updates": 420, "s_per_it": 5.66, "mem_gib": 24.9, "c_acc": 0.915625, "c_loss": 0.396266, "d_gen": -2.375104, "d_teacher": 2.246049, "g_logit": -2.372623, "gnorm": 426.73709}
48
+ {"it": 480, "gen_updates": 430, "s_per_it": 5.71, "mem_gib": 24.9, "c_acc": 0.909375, "c_loss": 0.394121, "d_gen": -2.525033, "d_teacher": 2.411426, "g_logit": -2.550583, "gnorm": 580.174043}
49
+ {"it": 490, "gen_updates": 440, "s_per_it": 5.6, "mem_gib": 24.9, "c_acc": 0.865625, "c_loss": 0.593566, "d_gen": -2.148674, "d_teacher": 2.374551, "g_logit": -2.169901, "gnorm": 716.07439}
50
+ {"it": 500, "gen_updates": 450, "s_per_it": 5.6, "mem_gib": 24.9, "c_acc": 0.846875, "c_loss": 0.729606, "d_gen": -2.290267, "d_teacher": 1.572728, "g_logit": -2.285638, "gnorm": 392.940741}
51
+ {"it": 510, "gen_updates": 460, "s_per_it": 6.78, "mem_gib": 24.9, "c_acc": 0.921875, "c_loss": 0.370543, "d_gen": -2.736271, "d_teacher": 2.455694, "g_logit": -2.707942, "gnorm": 537.985992}
52
+ {"it": 520, "gen_updates": 470, "s_per_it": 5.67, "mem_gib": 24.9, "c_acc": 0.965625, "c_loss": 0.252394, "d_gen": -2.877263, "d_teacher": 2.850073, "g_logit": -2.739951, "gnorm": 515.508899}
53
+ {"it": 530, "gen_updates": 480, "s_per_it": 5.64, "mem_gib": 24.9, "c_acc": 0.890625, "c_loss": 0.563771, "d_gen": -2.414951, "d_teacher": 2.277598, "g_logit": -2.654116, "gnorm": 709.700902}
54
+ {"it": 540, "gen_updates": 490, "s_per_it": 5.7, "mem_gib": 24.9, "c_acc": 0.94375, "c_loss": 0.285931, "d_gen": -2.918549, "d_teacher": 2.788963, "g_logit": -2.851511, "gnorm": 271.158658}
55
+ {"it": 550, "gen_updates": 500, "s_per_it": 5.64, "mem_gib": 24.9, "c_acc": 0.93125, "c_loss": 0.386427, "d_gen": -3.007278, "d_teacher": 2.665188, "g_logit": -2.941659, "gnorm": 374.554742}
56
+ {"it": 560, "gen_updates": 510, "s_per_it": 5.59, "mem_gib": 24.9, "c_acc": 0.953125, "c_loss": 0.253991, "d_gen": -2.992537, "d_teacher": 2.871128, "g_logit": -2.8083, "gnorm": 421.902652}
57
+ {"it": 570, "gen_updates": 520, "s_per_it": 5.74, "mem_gib": 24.9, "c_acc": 0.94375, "c_loss": 0.296528, "d_gen": -3.032215, "d_teacher": 2.960297, "g_logit": -2.982546, "gnorm": 367.955676}
58
+ {"it": 580, "gen_updates": 530, "s_per_it": 5.65, "mem_gib": 24.9, "c_acc": 0.9, "c_loss": 0.493329, "d_gen": -2.62191, "d_teacher": 2.719897, "g_logit": -2.698434, "gnorm": 780.508957}
59
+ {"it": 590, "gen_updates": 540, "s_per_it": 5.69, "mem_gib": 24.9, "c_acc": 0.946875, "c_loss": 0.312489, "d_gen": -3.124627, "d_teacher": 2.567042, "g_logit": -3.063663, "gnorm": 672.813906}
60
+ {"it": 600, "gen_updates": 550, "s_per_it": 5.71, "mem_gib": 24.9, "c_acc": 0.9625, "c_loss": 0.203462, "d_gen": -3.358016, "d_teacher": 3.01446, "g_logit": -3.541651, "gnorm": 244.839248}
61
+ {"it": 610, "gen_updates": 560, "s_per_it": 7.86, "mem_gib": 24.9, "c_acc": 0.959375, "c_loss": 0.246468, "d_gen": -3.294766, "d_teacher": 2.899352, "g_logit": -3.391545, "gnorm": 274.389373}
62
+ {"it": 620, "gen_updates": 570, "s_per_it": 5.6, "mem_gib": 24.9, "c_acc": 0.9375, "c_loss": 0.389626, "d_gen": -3.036653, "d_teacher": 2.82729, "g_logit": -2.993505, "gnorm": 602.330268}
63
+ {"it": 630, "gen_updates": 580, "s_per_it": 5.65, "mem_gib": 24.9, "c_acc": 0.953125, "c_loss": 0.236319, "d_gen": -3.326519, "d_teacher": 2.977125, "g_logit": -3.006266, "gnorm": 275.102869}
64
+ {"it": 640, "gen_updates": 590, "s_per_it": 5.63, "mem_gib": 24.9, "c_acc": 0.9125, "c_loss": 0.468886, "d_gen": -2.839741, "d_teacher": 2.540864, "g_logit": -2.657633, "gnorm": 181.273212}
65
+ {"it": 650, "gen_updates": 600, "s_per_it": 5.57, "mem_gib": 24.9, "c_acc": 0.953125, "c_loss": 0.311223, "d_gen": -3.182146, "d_teacher": 2.863069, "g_logit": -3.080172, "gnorm": 173.449964}
66
+ {"it": 660, "gen_updates": 610, "s_per_it": 5.72, "mem_gib": 24.9, "c_acc": 0.94375, "c_loss": 0.354878, "d_gen": -3.160589, "d_teacher": 2.84673, "g_logit": -2.883895, "gnorm": 240.966786}
67
+ {"it": 670, "gen_updates": 620, "s_per_it": 5.82, "mem_gib": 24.9, "c_acc": 0.90625, "c_loss": 0.406371, "d_gen": -2.754415, "d_teacher": 2.638921, "g_logit": -2.808692, "gnorm": 298.15805}
68
+ {"it": 680, "gen_updates": 630, "s_per_it": 5.65, "mem_gib": 24.9, "c_acc": 0.878125, "c_loss": 0.531884, "d_gen": -2.432082, "d_teacher": 2.578791, "g_logit": -2.482742, "gnorm": 271.599031}
69
+ {"it": 690, "gen_updates": 640, "s_per_it": 5.65, "mem_gib": 24.9, "c_acc": 0.9125, "c_loss": 0.4271, "d_gen": -2.860206, "d_teacher": 2.463032, "g_logit": -2.752373, "gnorm": 355.511536}
70
+ {"it": 700, "gen_updates": 650, "s_per_it": 5.62, "mem_gib": 24.9, "c_acc": 0.8875, "c_loss": 0.575126, "d_gen": -2.657025, "d_teacher": 2.421408, "g_logit": -2.473467, "gnorm": 229.405529}
71
+ {"it": 710, "gen_updates": 660, "s_per_it": 6.63, "mem_gib": 24.9, "c_acc": 0.871875, "c_loss": 0.574605, "d_gen": -2.347231, "d_teacher": 2.330401, "g_logit": -2.323787, "gnorm": 968.408723}
72
+ {"it": 720, "gen_updates": 670, "s_per_it": 5.6, "mem_gib": 24.9, "c_acc": 0.95, "c_loss": 0.300756, "d_gen": -3.302272, "d_teacher": 2.674137, "g_logit": -3.417291, "gnorm": 67.733886}
73
+ {"it": 730, "gen_updates": 680, "s_per_it": 5.64, "mem_gib": 24.9, "c_acc": 0.9375, "c_loss": 0.221562, "d_gen": -3.521256, "d_teacher": 3.014115, "g_logit": -3.507561, "gnorm": 100.00077}
74
+ {"it": 740, "gen_updates": 690, "s_per_it": 5.75, "mem_gib": 24.9, "c_acc": 0.971875, "c_loss": 0.138255, "d_gen": -3.753518, "d_teacher": 3.362483, "g_logit": -3.768429, "gnorm": 553.137035}
75
+ {"it": 750, "gen_updates": 700, "s_per_it": 5.58, "mem_gib": 24.9, "c_acc": 0.96875, "c_loss": 0.169519, "d_gen": -3.683156, "d_teacher": 3.372113, "g_logit": -3.977836, "gnorm": 65.564101}
76
+ {"it": 760, "gen_updates": 710, "s_per_it": 5.65, "mem_gib": 24.9, "c_acc": 0.971875, "c_loss": 0.13752, "d_gen": -3.875997, "d_teacher": 3.534403, "g_logit": -3.927255, "gnorm": 141.72445}
77
+ {"it": 770, "gen_updates": 720, "s_per_it": 5.71, "mem_gib": 24.9, "c_acc": 0.95625, "c_loss": 0.233508, "d_gen": -3.549302, "d_teacher": 3.582107, "g_logit": -3.492271, "gnorm": 81.573391}
78
+ {"it": 780, "gen_updates": 730, "s_per_it": 5.81, "mem_gib": 24.9, "c_acc": 0.95625, "c_loss": 0.179923, "d_gen": -3.765295, "d_teacher": 3.225188, "g_logit": -3.776723, "gnorm": 31.5222}
79
+ {"it": 790, "gen_updates": 740, "s_per_it": 5.59, "mem_gib": 24.9, "c_acc": 0.978125, "c_loss": 0.097448, "d_gen": -4.034114, "d_teacher": 3.69661, "g_logit": -3.898952, "gnorm": 39.593995}
80
+ {"it": 800, "gen_updates": 750, "s_per_it": 5.68, "mem_gib": 24.9, "c_acc": 0.978125, "c_loss": 0.102428, "d_gen": -4.094866, "d_teacher": 3.774902, "g_logit": -3.896441, "gnorm": 24.271654}
81
+ {"it": 810, "gen_updates": 760, "s_per_it": 7.66, "mem_gib": 24.9, "c_acc": 0.9625, "c_loss": 0.202403, "d_gen": -3.854326, "d_teacher": 3.584347, "g_logit": -3.978992, "gnorm": 40.056661}
82
+ {"it": 820, "gen_updates": 770, "s_per_it": 5.59, "mem_gib": 24.9, "c_acc": 0.971875, "c_loss": 0.156311, "d_gen": -3.827758, "d_teacher": 3.670029, "g_logit": -3.863046, "gnorm": 44.031802}
83
+ {"it": 830, "gen_updates": 780, "s_per_it": 5.62, "mem_gib": 24.9, "c_acc": 0.98125, "c_loss": 0.102383, "d_gen": -4.115957, "d_teacher": 3.809848, "g_logit": -4.091031, "gnorm": 45.254895}
84
+ {"it": 840, "gen_updates": 790, "s_per_it": 5.73, "mem_gib": 24.9, "c_acc": 0.971875, "c_loss": 0.12304, "d_gen": -4.100542, "d_teacher": 3.828115, "g_logit": -4.164267, "gnorm": 27.769859}
85
+ {"it": 850, "gen_updates": 800, "s_per_it": 5.67, "mem_gib": 24.9, "c_acc": 0.778125, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
86
+ {"it": 860, "gen_updates": 810, "s_per_it": 5.48, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
87
+ {"it": 870, "gen_updates": 820, "s_per_it": 5.65, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
88
+ {"it": 880, "gen_updates": 830, "s_per_it": 5.58, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
89
+ {"it": 890, "gen_updates": 840, "s_per_it": 5.49, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
90
+ {"it": 900, "gen_updates": 850, "s_per_it": 5.65, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
91
+ {"it": 910, "gen_updates": 860, "s_per_it": 6.28, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
92
+ {"it": 920, "gen_updates": 870, "s_per_it": 5.56, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
93
+ {"it": 930, "gen_updates": 880, "s_per_it": 5.54, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
94
+ {"it": 940, "gen_updates": 890, "s_per_it": 5.53, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
95
+ {"it": 950, "gen_updates": 900, "s_per_it": 5.5, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
96
+ {"it": 960, "gen_updates": 910, "s_per_it": 5.52, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
97
+ {"it": 970, "gen_updates": 920, "s_per_it": 5.5, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
98
+ {"it": 980, "gen_updates": 930, "s_per_it": 5.55, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
99
+ {"it": 990, "gen_updates": 940, "s_per_it": 5.57, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
100
+ {"it": 1000, "gen_updates": 950, "s_per_it": 5.5, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
101
+ {"it": 1010, "gen_updates": 960, "s_per_it": 7.44, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
102
+ {"it": 1020, "gen_updates": 970, "s_per_it": 5.56, "mem_gib": 24.9, "c_acc": 0.0, "c_loss": NaN, "d_gen": NaN, "d_teacher": NaN, "g_logit": NaN, "gnorm": NaN}
logs/probe_adm_discriminator.jsonl ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"it": 10, "gen_updates": 0, "s_per_it": 2.75, "mem_gib": 20.7, "fake": 0.27561}
2
+ {"it": 20, "gen_updates": 0, "s_per_it": 2.64, "mem_gib": 20.7, "fake": 0.375196}
3
+ {"it": 30, "gen_updates": 10, "s_per_it": 4.93, "mem_gib": 25.1, "adv_ratio": 35.733486, "d_acc": 0.500449, "d_fake": -0.10222, "d_loss": 2.126553, "d_real": -0.101114, "dmd_rms": 0.221341, "fake": 0.353202, "gnorm": 1.763772, "lam": 0.0, "ramp": 0.0}
4
+ {"it": 40, "gen_updates": 20, "s_per_it": 9.97, "mem_gib": 25.3, "adv_ratio": 48.805241, "d_acc": 0.499809, "d_fake": 0.092451, "d_loss": 2.002986, "d_real": 0.095818, "dmd_rms": 0.325674, "fake": 0.416026, "gnorm": 1.199596, "lam": 0.0, "ramp": 0.0}
5
+ {"it": 50, "gen_updates": 30, "s_per_it": 10.45, "mem_gib": 25.4, "adv_ratio": 9.468557, "d_acc": 0.503584, "d_fake": -0.081498, "d_loss": 2.012508, "d_real": -0.073537, "dmd_rms": 0.32834, "fake": 0.505973, "gnorm": 1.627691, "lam": 0.0, "ramp": 0.0}
6
+ {"it": 60, "gen_updates": 40, "s_per_it": 10.57, "mem_gib": 25.4, "adv_ratio": 16.740142, "d_acc": 0.501583, "d_fake": 0.143709, "d_loss": 1.987252, "d_real": 0.157508, "dmd_rms": 0.274366, "fake": 0.463597, "gnorm": 1.094926, "lam": 0.0, "ramp": 0.0}
7
+ {"it": 70, "gen_updates": 50, "s_per_it": 10.33, "mem_gib": 25.4, "adv_ratio": 17.995773, "d_acc": 0.502987, "d_fake": 0.218125, "d_loss": 1.988724, "d_real": 0.229557, "dmd_rms": 0.260837, "fake": 0.4254, "gnorm": 1.340356, "lam": 0.0, "ramp": 0.0}
8
+ {"it": 80, "gen_updates": 60, "s_per_it": 10.4, "mem_gib": 25.4, "adv_ratio": 21.950003, "d_acc": 0.502617, "d_fake": 0.195303, "d_loss": 1.999953, "d_real": 0.200418, "dmd_rms": 0.2464, "fake": 0.405491, "gnorm": 2.077201, "lam": 0.0, "ramp": 0.0}
9
+ {"it": 90, "gen_updates": 70, "s_per_it": 10.41, "mem_gib": 25.4, "adv_ratio": 4.992844, "d_acc": 0.512019, "d_fake": 0.056383, "d_loss": 2.005259, "d_real": 0.089869, "dmd_rms": 0.353305, "fake": 0.438201, "gnorm": 1.893705, "lam": 0.0, "ramp": 0.0}
10
+ {"it": 100, "gen_updates": 80, "s_per_it": 10.52, "mem_gib": 25.4, "adv_ratio": 22.842478, "d_acc": 0.500302, "d_fake": 0.098687, "d_loss": 1.999297, "d_real": 0.099608, "dmd_rms": 0.232808, "fake": 0.345837, "gnorm": 1.049859, "lam": 0.0, "ramp": 0.0}
11
+ {"it": 110, "gen_updates": 90, "s_per_it": 10.42, "mem_gib": 25.4, "adv_ratio": 14.325914, "d_acc": 0.504625, "d_fake": 0.015475, "d_loss": 1.990486, "d_real": 0.027477, "dmd_rms": 0.245574, "fake": 0.39951, "gnorm": 1.243515, "lam": 0.0, "ramp": 0.0}
12
+ {"it": 120, "gen_updates": 100, "s_per_it": 10.54, "mem_gib": 25.4, "adv_ratio": 14.00046, "d_acc": 0.50106, "d_fake": 0.017234, "d_loss": 1.999404, "d_real": 0.018786, "dmd_rms": 0.253277, "fake": 0.425688, "gnorm": 1.833253, "lam": 0.0, "ramp": 0.0}
13
+ {"it": 130, "gen_updates": 110, "s_per_it": 10.33, "mem_gib": 25.4, "adv_ratio": 8.808989, "d_acc": 0.506074, "d_fake": -0.007883, "d_loss": 1.982148, "d_real": 0.010178, "dmd_rms": 0.319683, "fake": 0.470164, "gnorm": 1.987517, "lam": 0.0, "ramp": 0.0}
14
+ {"it": 140, "gen_updates": 120, "s_per_it": 10.55, "mem_gib": 25.4, "adv_ratio": 30.816719, "d_acc": 0.500643, "d_fake": 0.018101, "d_loss": 1.999332, "d_real": 0.018775, "dmd_rms": 0.23059, "fake": 0.44002, "gnorm": 1.406316, "lam": 0.0, "ramp": 0.0}
15
+ {"it": 150, "gen_updates": 130, "s_per_it": 10.44, "mem_gib": 25.4, "adv_ratio": 21.776939, "d_acc": 0.50403, "d_fake": 0.049467, "d_loss": 2.006303, "d_real": 0.067547, "dmd_rms": 0.255741, "fake": 0.51779, "gnorm": 1.035783, "lam": 0.0, "ramp": 0.0}
logs/probe_teacher_discriminator.jsonl ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {"it": 10, "gen_updates": 0, "s_per_it": 2.76, "mem_gib": 20.7, "fake": 0.2758}
2
+ {"it": 20, "gen_updates": 0, "s_per_it": 2.67, "mem_gib": 20.7, "fake": 0.374674}
3
+ {"it": 30, "gen_updates": 10, "s_per_it": 4.96, "mem_gib": 25.5, "adv_ratio": 30.275976, "d_acc": 0.555563, "d_fake": -0.104033, "d_loss": 2.012383, "d_real": 0.087962, "dmd_rms": 0.205062, "fake": 0.32308, "gan_frac": 1.0, "gnorm": 0.77355, "lam": 0.0, "ramp": 0.0}
4
+ {"it": 40, "gen_updates": 20, "s_per_it": 4.96, "mem_gib": 25.5, "adv_ratio": 99.557544, "d_acc": 0.58669, "d_fake": -0.229194, "d_loss": 1.836997, "d_real": 0.012343, "dmd_rms": 0.305676, "fake": 0.440187, "gan_frac": 1.0, "gnorm": 1.42406, "lam": 0.0, "ramp": 0.0}
5
+ {"it": 50, "gen_updates": 30, "s_per_it": 4.8, "mem_gib": 25.5, "adv_ratio": 39.060537, "d_acc": 0.646695, "d_fake": -0.243651, "d_loss": 1.674095, "d_real": 0.232983, "dmd_rms": 0.284247, "fake": 0.443884, "gan_frac": 1.0, "gnorm": 3.348034, "lam": 0.0, "ramp": 0.0}
6
+ {"it": 60, "gen_updates": 40, "s_per_it": 4.85, "mem_gib": 25.5, "adv_ratio": 48.76557, "d_acc": 0.583147, "d_fake": -0.154269, "d_loss": 1.790229, "d_real": 0.145679, "dmd_rms": 0.281149, "fake": 0.425242, "gan_frac": 1.0, "gnorm": 2.247003, "lam": 0.0, "ramp": 0.0}
7
+ {"it": 70, "gen_updates": 50, "s_per_it": 4.77, "mem_gib": 25.5, "adv_ratio": 59.619794, "d_acc": 0.581655, "d_fake": -0.218971, "d_loss": 1.795289, "d_real": 0.126522, "dmd_rms": 0.310085, "fake": 0.388189, "gan_frac": 1.0, "gnorm": 1.043796, "lam": 0.0, "ramp": 0.0}
8
+ {"it": 80, "gen_updates": 60, "s_per_it": 4.86, "mem_gib": 25.5, "adv_ratio": 68.792467, "d_acc": 0.682936, "d_fake": -0.367275, "d_loss": 1.531309, "d_real": 0.305816, "dmd_rms": 0.224524, "fake": 0.357255, "gan_frac": 1.0, "gnorm": 1.901103, "lam": 0.0, "ramp": 0.0}
lora.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4a4c8e2d6244a0fcdc2822b87b3435226ae57c289aef99fc2ac54edeab9e09bb
3
+ size 1189176832
time_embedding.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2ebabc91e2a9e68c00e948dd80b05235a826e808817b5b2600b441457570d244
3
+ size 41017896