File size: 7,234 Bytes
28db2ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
---

license: apache-2.0
pipeline_tag: text-to-image
library_name: diffusers
tags:
- small
- dit
- flow-matching
- custom_code
datasets:
- QLNI/FLUX-Reason-6M-flux2-latents
language:
- en
---


<h1 align="center">Surjo-Image-Preview</h1>

<p align="center">
  Text-To-Image • 108M Parameters • Looped Latent DiT, Flow Matching
</p>

## Samples

<table>
<tr>
<td><img src="samples/sample_car.png" alt="car"></td>
<td><img src="samples/sample_vase.png" alt="vase"></td>
</tr>
<tr>
<td><img src="samples/sample_strawberries.png" alt="strawberries"></td>
<td><img src="samples/sample_sea.png" alt="sea"></td>
</tr>
</table>

---

## Model

### About the model

A tiny looped diffusion transformer (DiT) for 256px text-to-image, trained from scratch with rectified flow matching.

- Pipeline: text-to-image
- Parameter Count: 108.3M
- Encoder: frozen `LiquidAI/LFM2.5-Encoder-230M` (1024-dim, 128 tokens)
- VAE: FLUX.2 small-decoder mirror (`SurjoLabs/FLUX.2-small-decoder-vae`)
- Image resolution: 256²
- Latents: post-BN packed `(128, 16, 16)`, 256 tokens
- Weights shipped are the EMA artifact (best-equivalent)

### Model config

- `DIM`: 512
- `DEPTH`: 18 (7 pre + 4 looped ×2 + 7 post, effective 22)
- `HEADS`: 8 × 64, QK-RMSNorm
- `POS`: 2D-RoPE
- `MLP`: SwiGLU
- `TEXT_DIM`: 1024 (RMSNorm + projector)
- `REPA_DIM`: 384 (DINOv2-S, HASTE schedule, off after 70%)
- `LOOP_ITERS`: 2
- Custom architecture, loading needs `trust_remote_code=True`

---

## Training

### Dataset

~5.89M rows of prebuilt FLUX.2 latents + first-caption-per-row (`QLNI/FLUX-Reason-6M-flux2-latents`), 10 epochs (58.9M samples).

### Recipe

AdamW (0.9/0.99, no decay, clip 1.0), LR 2e-4 to ~2e-5 cosine (5k warmup; decay horizon overshot by the warmup length, so it lands at ~2.35e-5 instead of the 2e-5 floor, cosmetic, disclosed), fp32 masters / bf16 compute, EMA 0.9999. 57,510 steps, global batch 1024, final val NLL **0.30116**.

---

## How to run the model

```bash

pip install "transformers==5.13.1" "diffusers==0.39.0" accelerate safetensors pillow

```

```python

import torch

from diffusers import AutoModel, AutoencoderKLFlux2

from transformers import AutoTokenizer, AutoModelForMaskedLM



REPO = "SurjoLabs/Surjo-Image-Preview"

device = "cuda"

dtype = torch.bfloat16 if torch.cuda.get_device_capability()[0] >= 8 else torch.float16



model = AutoModel.from_pretrained(REPO, trust_remote_code=True,

                                  torch_dtype=dtype).to(device).eval()

tok = AutoTokenizer.from_pretrained("LiquidAI/LFM2.5-Encoder-230M",

                                    trust_remote_code=True)

tenc = AutoModelForMaskedLM.from_pretrained(

    "LiquidAI/LFM2.5-Encoder-230M", trust_remote_code=True,

    dtype=dtype).to(device).eval().lfm2

vae = AutoencoderKLFlux2.from_pretrained(

    "SurjoLabs/FLUX.2-small-decoder-vae", subfolder="vae",

    torch_dtype=dtype).to(device).eval()

mean, var, eps = (vae.bn.running_mean.float(), vae.bn.running_var.float(),

                  float(vae.config.batch_norm_eps))



prompt = "a cozy cabin in a snowy forest at dusk, warm light in the windows"

ids = tok([prompt], padding="max_length", truncation=True, max_length=128,

          return_tensors="pt")

ctx = tenc(**{k: v.to(device) for k, v in ids.items()}).last_hidden_state.to(dtype)

uids = tok([""], padding="max_length", truncation=True, max_length=128,

           return_tensors="pt")

uctx = tenc(**{k: v.to(device) for k, v in uids.items()}).last_hidden_state.to(dtype)

cmask = ids["attention_mask"].to(device=device, dtype=dtype)

umask = uids["attention_mask"].to(device=device, dtype=dtype)

amp = torch.bfloat16 if torch.cuda.get_device_capability()[0] >= 8 else torch.float16



steps, cfg_scale, dt = 50, 3.0, 1.0 / 50

gen = torch.Generator(device=device).manual_seed(0)

z = torch.randn(1, 128, 16, 16, device=device, generator=gen, dtype=dtype)

with torch.no_grad():

    for i in range(steps):

        t = torch.full((1,), i * dt, device=device)

        with torch.autocast(device_type="cuda", dtype=amp):

            out = model(torch.cat([z, z]), torch.cat([t, t]),

                        torch.cat([uctx, ctx]),

                        ctx_mask=torch.cat([umask, cmask]),

                        deepsup=False, loop_iters=2)

        v_u, v_c = out.float().chunk(2)

        z = (z + dt * (v_u + cfg_scale * (v_c - v_u))).to(dtype)

    std = torch.sqrt(var.view(1, -1, 1, 1) + eps)

    lat = (z.float() * std.to(z.device)

           + mean.view(1, -1, 1, 1).to(z.device)).to(dtype)

    img = vae.decode(lat.view(1, 32, 2, 2, 16, 16

                              ).permute(0, 1, 4, 2, 5, 3).reshape(1, 32, 32, 32)).sample

    img = (img.clamp(-1, 1) + 1) / 2



from PIL import Image

Image.fromarray(

    ((img[0].permute(1, 2, 0).float().cpu().numpy()) * 255).astype("uint8")

).save("out.png")

```

### Recommended settings for sampling

- `--steps`: 50
- `--cfg`: 3.0
- `loop_iters`: 2 (model default)
- fp16 on pre-sm80 GPUs (e.g. T4), bf16 otherwise

---

## Evaluation

GenEval2 ([Kamath et al., 2025](https://arxiv.org/abs/2512.16853)): 800 prompts, Soft-TIFA with Qwen3-VL-8B-Instruct, 1 image per prompt (256px, 50 steps, CFG 3.0, seed 42). Headline is Soft-TIFA GM.

| metric | Surjo-Image-Preview | Supra2-IMG |
|---|---|---|
| Soft-TIFA GM | **8.07** | 6.80 |
| Soft-TIFA AM | **42.30** | 36.38 |
| object | **61.03** | 46.77 |
| attribute | **45.80** | 43.56 |
| count | **22.30** | 22.14 |
| position | 28.91 | **31.07** |
| verb | **2.03** | 1.51 |

Takes objects/attributes/count; cedes position. Both models floor on verbs (~2%) and struggle counting (~22%): small-model limits, not a gap.

---

## Future

These are the final weights for this preview. Surjo-Image (full release) will bring more data, refined architecture, and higher optimization.

---

![Agni](agni-logo-dark.svg)

Trained using Agni

---

## Limitations

- Renders legible text poorly; faces and hands degrade out-of-distribution.
- English-only captions; 256px single-scale; small-model weak draws on complex multi-subject scenes.
- Web-scale source biases inherited.

---

## AI Usage

SurjoLabs has never trained a Text2Image model prior. AI was used heavily compared to previous models to write code. Research and designing the model was done by a human.

---

## Acknowledgments

- Thanks to [SupraLabs](https://huggingface.co/SupraLabs/Supra2-IMG) for proving the tiny-T2I lane, and for the inspiration.
- Thanks to the [FLUX-Reason-6M](https://huggingface.co/datasets/LucasFang/FLUX-Reason-6M) team for the training dataset, and to [QLNI](https://huggingface.co/datasets/QLNI/FLUX-Reason-6M-flux2-latents) for the precomputed latents.
- Thanks to Black Forest Labs for the [FLUX.2 VAE](https://huggingface.co/black-forest-labs/FLUX.2-small-decoder).
- Thanks to the authors of [i1](https://arxiv.org/abs/2606.11289) (configuration), [Looped-DiT](https://arxiv.org/abs/2609.40305) (looping), [XSA](https://arxiv.org/abs/2603.09078) (exclusive self-attention), and [PixelModel-v6](https://huggingface.co/bench-labs/PixelModel-v6) (training recipe).