yujiepan commited on
Commit
7ff060e
·
verified ·
1 Parent(s): 2553191

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ 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
+ tokenizer/tokenizer.json filter=lfs diff=lfs merge=lfs -text
.meta.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source_model": {
3
+ "name": "OpenMOSS-Team/MOSS-Image-Base",
4
+ "commit_id": "85975793d8b3d432f1b6e27f352abaa4c2a4c0d3"
5
+ },
6
+ "env": {
7
+ "python": "3.12.13",
8
+ "packages": {
9
+ "Pillow": "12.3.0",
10
+ "diffusers": "0.40.0.dev0",
11
+ "huggingface_hub": "1.27.0",
12
+ "safetensors": "0.8.0",
13
+ "tokenizers": "0.23.0rc0",
14
+ "torch": "2.13.0+cu126",
15
+ "transformers": "5.15.0.dev0"
16
+ },
17
+ "cuda": "12.6"
18
+ }
19
+ }
README.md ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: diffusers
3
+ base_model:
4
+ - OpenMOSS-Team/MOSS-Image-Base
5
+ ---
6
+
7
+ This tiny model is intended for debugging. It is randomly initialized using the configuration adapted from [OpenMOSS-Team/MOSS-Image-Base](https://huggingface.co/OpenMOSS-Team/MOSS-Image-Base).
8
+
9
+ Structural comparison with `OpenMOSS-Team/MOSS-Image-Base`:
10
+
11
+ | Feature | Source | Tiny |
12
+ |---|---:|---:|
13
+ | Pipeline | custom MOSS Image | same |
14
+ | Text vocabulary / precision | 248,320 / BF16 | same |
15
+ | Qwen3.5 text layers and cadence | 32; 3 linear + 1 full | same |
16
+ | Text width / attention head dimension | 4,096 / 256 | 32 / 32 |
17
+ | Text vision tower | 27 layers | same depth; width 32 |
18
+ | DiT blocks | 42 | same |
19
+ | DiT width / heads / FFN | 3,072 / 24 / 8,192 | 64 / 2 / 128 |
20
+ | DiT latent channels | 128 | same |
21
+ | Flux2 VAE blocks / layers per block | 4 / 2 | same; widths reduced |
22
+ | Checkpoint precision | BF16 text/DiT; FP32 VAE/RoPE | same |
23
+
24
+ The source's custom scheduler, DiT, tokenizer and pipeline code are copied from
25
+ the pinned revision. The transformer keeps all 42 blocks, and the VAE stays
26
+ FP32 because that is the source checkpoint representation and decoding path.
27
+
28
+ | File path | Size |
29
+ |------|------|
30
+ | text_encoder/model.safetensors | 17.5MB |
31
+ | transformer/diffusion_pytorch_model.safetensors | 3.8MB |
32
+ | vae/diffusion_pytorch_model.safetensors | 5.8MB |
33
+
34
+
35
+ ### Example usage:
36
+
37
+ - Transformers (✅ verified)
38
+
39
+ ```python
40
+ import torch
41
+ from diffusers import DiffusionPipeline
42
+
43
+ model_id = "tiny-random/moss-image"
44
+ device = "cuda" if torch.cuda.is_available() else "cpu"
45
+ pipe = DiffusionPipeline.from_pretrained(
46
+ model_id,
47
+ custom_pipeline="pipeline_moss_image",
48
+ trust_remote_code=True,
49
+ torch_dtype={"default": torch.bfloat16, "vae": torch.float32},
50
+ )
51
+ pipe.to(device)
52
+ image = pipe(
53
+ "A red fox in a snowy pine forest",
54
+ height=32,
55
+ width=32,
56
+ num_inference_steps=1,
57
+ guidance_scale=1.0,
58
+ generator=torch.Generator("cpu").manual_seed(42),
59
+ ).images[0]
60
+ print(image.size)
61
+ ```
62
+
63
+ ### Codes to create this repo (✅ verified):
64
+
65
+ <details><summary>Click to expand</summary>
66
+
67
+ ```python
68
+ import importlib.util
69
+ import json
70
+ from pathlib import Path
71
+ from shutil import copyfile
72
+
73
+ import torch
74
+ from diffusers import AutoencoderKLFlux2
75
+ from huggingface_hub import hf_hub_download, list_repo_files
76
+ from transformers import AutoConfig, Qwen3_5Model, set_seed
77
+
78
+ source_model_id = "OpenMOSS-Team/MOSS-Image-Base"
79
+ save_folder = "/tmp/tiny-random/moss-image"
80
+ source_revision = "85975793d8b3d432f1b6e27f352abaa4c2a4c0d3"
81
+ set_seed(42)
82
+ folder = Path(save_folder)
83
+ folder.mkdir(parents=True, exist_ok=True)
84
+
85
+ def load_source_module():
86
+ path = hf_hub_download(source_model_id, "pipeline_moss_image.py", revision=source_revision)
87
+ spec = importlib.util.spec_from_file_location("moss_image_source", path)
88
+ module = importlib.util.module_from_spec(spec)
89
+ if spec.loader is None:
90
+ raise RuntimeError("The pinned MOSS pipeline has no import loader")
91
+ spec.loader.exec_module(module)
92
+ return module
93
+
94
+ def initialize(module):
95
+ with torch.no_grad():
96
+ for name, parameter in sorted(module.named_parameters()):
97
+ if not parameter.is_floating_point():
98
+ continue
99
+ if name.endswith("A_log") or name.endswith("dt_bias"):
100
+ continue
101
+ if name.startswith("language_model") and (
102
+ name.endswith("input_layernorm.weight")
103
+ or name.endswith("post_attention_layernorm.weight")
104
+ or name.endswith(".linear_attn.norm.weight")
105
+ or name.endswith("language_model.norm.weight")
106
+ or name.endswith(".self_attn.q_norm.weight")
107
+ or name.endswith(".self_attn.k_norm.weight")
108
+ ):
109
+ parameter.zero_()
110
+ elif ("norm" in name or ".bn." in name) and name.endswith("weight"):
111
+ parameter.fill_(1)
112
+ else:
113
+ torch.nn.init.normal_(parameter, mean=0.0, std=0.02)
114
+
115
+ source = load_source_module()
116
+
117
+ raw_text = json.loads(Path(hf_hub_download(
118
+ source_model_id, "text_encoder/config.json", revision=source_revision,
119
+ )).read_text(encoding="utf-8"))
120
+ text = raw_text["text_config"]
121
+ text.update(
122
+ hidden_size=32,
123
+ head_dim=32,
124
+ num_attention_heads=1,
125
+ num_key_value_heads=1,
126
+ intermediate_size=64,
127
+ linear_key_head_dim=32,
128
+ linear_value_head_dim=32,
129
+ linear_num_key_heads=1,
130
+ linear_num_value_heads=1,
131
+ num_hidden_layers=32,
132
+ layer_types=["linear_attention", "linear_attention", "linear_attention", "full_attention"] * 8,
133
+ )
134
+ text["rope_parameters"]["mrope_section"] = [1, 1, 2]
135
+ raw_text["vision_config"].update(
136
+ hidden_size=32,
137
+ intermediate_size=64,
138
+ num_heads=1,
139
+ out_hidden_size=32,
140
+ depth=27,
141
+ )
142
+ text_folder = folder / "text_encoder"
143
+ text_folder.mkdir(exist_ok=True)
144
+ (text_folder / "config.json").write_text(json.dumps(raw_text, indent=2), encoding="utf-8")
145
+ text_encoder = Qwen3_5Model(AutoConfig.from_pretrained(text_folder)).to(torch.bfloat16)
146
+ initialize(text_encoder)
147
+ text_encoder.save_pretrained(text_folder, safe_serialization=True)
148
+
149
+ # The source stores a 42-block custom DiT in BF16. Its VAE-facing 128
150
+ # channels remain fixed: the pipeline unshuffles them to the VAE's 32
151
+ # latent channels before decoding.
152
+ transformer = source.MOSSImageTransformer(
153
+ dim=64,
154
+ ffn_dim=128,
155
+ num_heads=2,
156
+ num_layers=42,
157
+ text_dim=32,
158
+ freq_dim=32,
159
+ latent_channels=128,
160
+ ).to(torch.bfloat16)
161
+ initialize(transformer)
162
+ transformer.save_pretrained(folder / "transformer", safe_serialization=True)
163
+
164
+ raw_vae = json.loads(Path(hf_hub_download(
165
+ source_model_id, "vae/config.json", revision=source_revision,
166
+ )).read_text(encoding="utf-8"))
167
+ raw_vae.update(
168
+ block_out_channels=[32, 32, 64, 64],
169
+ decoder_block_out_channels=[32, 32, 64, 64],
170
+ norm_num_groups=32,
171
+ sample_size=32,
172
+ )
173
+ vae = AutoencoderKLFlux2.from_config(raw_vae).to(torch.float32)
174
+ initialize(vae)
175
+ vae.save_pretrained(folder / "vae", safe_serialization=True)
176
+
177
+ scheduler = source.MOSSImageScheduler()
178
+ scheduler.save_pretrained(folder / "scheduler")
179
+
180
+ # Preserve all executable source code plus tokenizer assets byte-for-byte.
181
+ assets = {
182
+ "model_index.json", "pipeline_moss_image.py", "scheduler/pipeline_moss_image.py",
183
+ "scheduler/scheduler_config.json", "tokenizer/chat_template.jinja",
184
+ "tokenizer/tokenizer.json", "tokenizer/tokenizer_config.json",
185
+ "transformer/pipeline_moss_image.py",
186
+ }
187
+ for filename in sorted(set(list_repo_files(source_model_id, revision=source_revision)) & assets):
188
+ destination = folder / filename
189
+ destination.parent.mkdir(parents=True, exist_ok=True)
190
+ copyfile(hf_hub_download(source_model_id, filename, revision=source_revision), destination)
191
+
192
+ pipe = source.MOSSImagePipeline(transformer, scheduler, text_encoder, None, vae)
193
+ ```
194
+
195
+ </details>
196
+
197
+ ### Printing the model:
198
+
199
+ <details><summary>Click to expand</summary>
200
+
201
+ ```text
202
+ MOSSImagePipeline {
203
+ "_class_name": "MOSSImagePipeline",
204
+ "_diffusers_version": "0.40.0.dev0",
205
+ "scheduler": [
206
+ "moss_image_source",
207
+ "MOSSImageScheduler"
208
+ ],
209
+ "text_encoder": [
210
+ "transformers",
211
+ "Qwen3_5Model"
212
+ ],
213
+ "tokenizer": [
214
+ null,
215
+ null
216
+ ],
217
+ "transformer": [
218
+ "moss_image_source",
219
+ "MOSSImageTransformer"
220
+ ],
221
+ "vae": [
222
+ "diffusers",
223
+ "AutoencoderKLFlux2"
224
+ ]
225
+ }
226
+ ```
227
+
228
+ </details>
229
+
230
+ ### Test environment:
231
+
232
+ - Pillow: 12.3.0
233
+ - diffusers: 0.40.0.dev0
234
+ - huggingface_hub: 1.27.0
235
+ - safetensors: 0.8.0
236
+ - tokenizers: 0.23.0rc0
237
+ - torch: 2.13.0+cu126
238
+ - transformers: 5.15.0.dev0
239
+
240
+ ### Change log:
241
+
242
+ - 2026-10-23: Initial version.
243
+
244
+ This repo is generated by GPT-6.1-Sol
model_index.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "MOSSImagePipeline",
3
+ "_diffusers_version": "0.38.0",
4
+ "scheduler": [
5
+ "pipeline_moss_image",
6
+ "MOSSImageScheduler"
7
+ ],
8
+ "text_encoder": [
9
+ "transformers",
10
+ "Qwen3_5Model"
11
+ ],
12
+ "tokenizer": [
13
+ "transformers",
14
+ "Qwen2Tokenizer"
15
+ ],
16
+ "transformer": [
17
+ "pipeline_moss_image",
18
+ "MOSSImageTransformer"
19
+ ],
20
+ "vae": [
21
+ "diffusers",
22
+ "AutoencoderKLFlux2"
23
+ ]
24
+ }
pipeline_moss_image.py ADDED
@@ -0,0 +1,440 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MOSSImage modeling and text-to-image pipeline (no Motor/TorchTitan imports).
2
+
3
+ Dependencies: torch>=2.9, diffusers>=0.38, transformers>=5.3, safetensors,
4
+ accelerate, Pillow. Copy this file anywhere and import it normally.
5
+
6
+ import torch
7
+ # Import MOSSImagePipeline from this file for component-level loading.
8
+ pipe = MOSSImagePipeline.from_components_pretrained(
9
+ transformer_path="exported_transformer", # save_pretrained directory
10
+ text_encoder_path="Qwen/Qwen3.5-9B",
11
+ vae_path="black-forest-labs/FLUX.2-klein-4B",
12
+ ).to("cuda")
13
+ pipe("A cat", generator=torch.Generator("cuda").manual_seed(42)).images[0].save("cat.png")
14
+
15
+ Default guidance uses CFG 3 and skips block 22 only in the negative branch
16
+ (zero-based main DiT block indices; two forwards per sampling step):
17
+ pipe("A cat", guidance_scale=3.0, uncond_skip_layers=[22])
18
+ pipe("A cat", uncond_skip_layers=[]) # disable negative-branch skipping
19
+ Optional additional positive-branch SLG adds a third forward:
20
+ pipe("A cat", slg_layers=[24], slg_scale=3.0)
21
+ The additional SLG layer/scale above illustrates the API, not a quality-tuned setting.
22
+
23
+ For a consolidated training state dict, create MOSSImageTransformer and
24
+ call load_training_state_dict(state), then save_pretrained("exported_transformer").
25
+ Select raw or EMA weights explicitly before calling it; distributed checkpoint
26
+ shards/optimizer states are not model state dicts. Defaults match the finalized dense MOSSImage model,
27
+ not MoE, CFG-distilled, video, or reference-image variants. Prompt strings are
28
+ already-rendered captions (dataset-specific quality/domain tags are not added).
29
+
30
+ First-release defaults: bf16 DiT/text-encoder weights, fp32 RoPE frequencies
31
+ and phase/sin/cos calculation, then RoPE cast to the video activation dtype.
32
+ Initial Gaussian noise is sampled directly in fp32, including with bf16 weights.
33
+ CFG and Euler latent updates use fp32; VAE decoding defaults to fp32. Scheduler
34
+ shift defaults to 5.0. Exact numerical parity with the original fp32-master
35
+ inference pipeline is not a release requirement.
36
+ """
37
+ from __future__ import annotations
38
+
39
+ import math
40
+ from pathlib import Path
41
+
42
+ import torch
43
+ from torch import nn
44
+ from torch.nn import functional as F
45
+ from diffusers import AutoencoderKLFlux2, DiffusionPipeline, FlowMatchEulerDiscreteScheduler
46
+ from diffusers.schedulers.scheduling_utils import SchedulerMixin # Required by custom-component discovery.
47
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
48
+ from diffusers.image_processor import VaeImageProcessor
49
+ from diffusers.models.modeling_utils import ModelMixin
50
+ from diffusers.models.modeling_outputs import Transformer2DModelOutput
51
+ from diffusers.pipelines.pipeline_utils import ImagePipelineOutput
52
+ from diffusers.utils.torch_utils import randn_tensor
53
+ from transformers import AutoModel, AutoTokenizer
54
+
55
+
56
+ class _RMSNorm(nn.Module):
57
+ def __init__(self, dim):
58
+ super().__init__()
59
+ self.weight = nn.Parameter(torch.ones(dim))
60
+
61
+ def forward(self, x):
62
+ y = x.float() * torch.rsqrt(x.float().square().mean(-1, keepdim=True) + 1e-6)
63
+ return y.to(x.dtype) * self.weight
64
+
65
+
66
+ def _situ(hidden, gate):
67
+ return (4.0 * torch.tanh(gate / 4.0) * torch.sigmoid(gate)) * (25.0 * torch.tanh(hidden / 25.0))
68
+
69
+
70
+ def _validate_skip_layers(layers, depth, name='skip_layers'):
71
+ layers = () if layers is None else tuple(layers)
72
+ if any(type(index) is not int or not 0 <= index < depth for index in layers):
73
+ raise ValueError(f'{name} must contain integer block indices in [0, {depth}).')
74
+ return frozenset(layers)
75
+
76
+
77
+ def _rotate(x, rope):
78
+ sin, cos = rope.chunk(2, -1)
79
+ sin = torch.cat((sin, sin), -1).unsqueeze(-2)
80
+ cos = torch.cat((cos, cos), -1).unsqueeze(-2)
81
+ a, b = x.chunk(2, -1)
82
+ return x * cos + torch.cat((-b, a), -1) * sin
83
+
84
+
85
+ def _attention(q, k, v, rope, heads):
86
+ shape = (*q.shape[:2], heads, q.shape[-1] // heads)
87
+ q = _rotate(q.reshape(shape), rope).transpose(1, 2)
88
+ k = _rotate(k.reshape(shape), rope).transpose(1, 2)
89
+ v = v.reshape(shape).transpose(1, 2)
90
+ return F.scaled_dot_product_attention(q.to(v.dtype), k.to(v.dtype), v).transpose(1, 2).flatten(2)
91
+
92
+
93
+ class _TextBlock(nn.Module):
94
+ def __init__(self, dim, ffn_dim, heads):
95
+ super().__init__()
96
+ self.heads = heads
97
+ self.norm1, self.norm2 = _RMSNorm(dim), _RMSNorm(dim)
98
+ self.q_norm, self.k_norm = _RMSNorm(dim // heads), _RMSNorm(dim // heads)
99
+ self.qkv = nn.Linear(dim, 3 * dim)
100
+ self.proj = nn.Linear(dim, dim)
101
+ self.ffn_gate, self.ffn_up = nn.Linear(dim, ffn_dim), nn.Linear(dim, ffn_dim)
102
+ self.ffn_down = nn.Linear(ffn_dim, dim)
103
+
104
+ def forward(self, x, rope):
105
+ q, k, v = self.qkv(self.norm1(x)).chunk(3, -1)
106
+ q = self.q_norm(q.unflatten(-1, (self.heads, -1))).flatten(2)
107
+ k = self.k_norm(k.unflatten(-1, (self.heads, -1))).flatten(2)
108
+ x = x + self.proj(_attention(q, k, v, rope, self.heads))
109
+ y = self.norm2(x)
110
+ return x + self.ffn_down(_situ(self.ffn_up(y), self.ffn_gate(y)))
111
+
112
+
113
+ class _TextProjection(nn.Module):
114
+ def __init__(self, text_dim, dim, ffn_dim, heads):
115
+ super().__init__()
116
+ self.proj = nn.Linear(text_dim, dim)
117
+ self.blocks = nn.ModuleList([_TextBlock(dim, ffn_dim, heads) for _ in range(2)])
118
+
119
+ def forward(self, x, rope):
120
+ x = self.proj(x.to(self.proj.weight.dtype))
121
+ for block in self.blocks:
122
+ x = block(x, rope)
123
+ return x
124
+
125
+
126
+ class _Block(nn.Module):
127
+ def __init__(self, dim, ffn_dim, heads):
128
+ super().__init__()
129
+ self.heads = heads
130
+ self.norm1, self.norm2 = _RMSNorm(dim), _RMSNorm(dim)
131
+ self.q_norm, self.k_norm = _RMSNorm(dim // heads), _RMSNorm(dim // heads)
132
+ self.proj = nn.ModuleDict({
133
+ name: nn.Linear(dim, dim, bias=False) for name in ('q', 'k', 'v', 'o')
134
+ })
135
+ self.proj['ffn_up_gate'] = nn.Linear(dim, 2 * ffn_dim, bias=False)
136
+ self.proj['ffn_down'] = nn.Linear(ffn_dim, dim, bias=False)
137
+
138
+ def forward(self, x, rope, modulation):
139
+ shift_a, scale_a, gate_a, shift_f, scale_f, gate_f = modulation.unbind(1)
140
+ y = self.norm1(x) * (1 + scale_a[:, None]) + shift_a[:, None]
141
+ dtype = self.proj['q'].weight.dtype
142
+ y = y.to(dtype)
143
+ q = self.q_norm(self.proj['q'](y).unflatten(-1, (self.heads, -1))).flatten(2)
144
+ k = self.k_norm(self.proj['k'](y).unflatten(-1, (self.heads, -1))).flatten(2)
145
+ x = x + self.proj['o'](_attention(q, k, self.proj['v'](y), rope, self.heads)) * gate_a[:, None]
146
+ y = self.norm2(x) * (1 + scale_f[:, None]) + shift_f[:, None]
147
+ hidden, gate = self.proj['ffn_up_gate'](y.to(dtype)).chunk(2, -1)
148
+ return x + self.proj['ffn_down'](_situ(hidden, gate)) * gate_f[:, None]
149
+
150
+
151
+ class _RoPE(nn.Module):
152
+ def __init__(self, head_dim, base):
153
+ super().__init__()
154
+ self.num_bands = head_dim // 8
155
+ self.base = base
156
+ self.register_buffer('bands', self._default_bands(torch.device('cpu')))
157
+
158
+ def _default_bands(self, device):
159
+ # Match training's CPU fp32 construction, including under meta init.
160
+ exponent = torch.arange(self.num_bands, device='cpu', dtype=torch.float32) / self.num_bands
161
+ return (1 / (self.base ** exponent)).to(device)
162
+
163
+ def _apply(self, fn, recurse=True):
164
+ super()._apply(fn, recurse=recurse)
165
+ # .float() cannot recover frequencies already rounded by .to(bf16).
166
+ self.bands = self._default_bands(self.bands.device)
167
+ return self
168
+
169
+ def _load_from_state_dict(self, *args, **kwargs):
170
+ super()._load_from_state_dict(*args, **kwargs)
171
+ # Frequencies are derived constants; retain the key for checkpoint compatibility.
172
+ self.bands = self._default_bands(self.bands.device)
173
+
174
+ def forward(self, height, width, text_length, device):
175
+ # Diffusers/Accelerate can install tensors directly, bypassing load hooks.
176
+ if self.bands.dtype != torch.float32:
177
+ self.bands = self._default_bands(device)
178
+ # Coordinates: (r, f, h, w); sequence order is image followed by text.
179
+ h, w = torch.meshgrid(torch.arange(height, device=device), torch.arange(width, device=device), indexing='ij')
180
+ image = torch.stack((torch.zeros_like(h), torch.full_like(h, text_length), h, w), -1).flatten(0, 1).float()
181
+ image[:, 2] -= (height - 1) / 2
182
+ image[:, 3] -= (width - 1) / 2
183
+ text = torch.zeros(text_length, 4, device=device, dtype=torch.float32)
184
+ text[:, 1] = torch.arange(text_length, device=device)
185
+ phase = torch.cat((image, text), 0).unsqueeze(-1) * self.bands
186
+ return torch.cat((phase.sin(), phase.cos()), -2).flatten(-2).unsqueeze(0)
187
+
188
+
189
+ class MOSSImageTransformer(ModelMixin, ConfigMixin):
190
+ """MOSSImage DiT with training-compatible state-dict names and SDPA attention.
191
+
192
+ Each sample is evaluated at its true text length, including text refinement;
193
+ padded tokens cannot influence either text or joint attention.
194
+ """
195
+ @register_to_config
196
+ def __init__(self, dim=3072, ffn_dim=8192, num_heads=24, num_layers=42,
197
+ text_dim=4096, freq_dim=256, latent_channels=128, rope_base=10000.0):
198
+ super().__init__()
199
+ if dim % num_heads or (dim // num_heads) % 8 or freq_dim % 2:
200
+ raise ValueError('Require dim divisible by heads, head_dim divisible by 8, and even freq_dim.')
201
+ self.video_embedder = nn.Linear(latent_channels, dim)
202
+ self.text_embedder = _TextProjection(text_dim, dim, ffn_dim, num_heads)
203
+ self.rope = _RoPE(dim // num_heads, rope_base)
204
+ self.time_embedding = nn.Sequential(nn.Linear(freq_dim, dim), nn.SiLU(), nn.Linear(dim, dim))
205
+ self.shared_block_modulation = nn.Linear(dim, 6 * dim)
206
+ self.layers = nn.ModuleList([_Block(dim, ffn_dim, num_heads) for _ in range(num_layers)])
207
+ self.final_norm_video = _RMSNorm(dim)
208
+ self.final_linear_video = nn.Linear(dim, latent_channels, bias=False)
209
+ self.video_head_modulation = nn.Linear(dim, 2 * dim)
210
+
211
+ def load_training_state_dict(self, state_dict, *, prefix=''):
212
+ """Load a consolidated raw/EMA model mapping, strictly; no silent key drops."""
213
+ if prefix:
214
+ if any(not key.startswith(prefix) for key in state_dict):
215
+ raise ValueError('All keys must start with the explicit prefix.')
216
+ state_dict = {key[len(prefix):]: value for key, value in state_dict.items()}
217
+ return self.load_state_dict(state_dict, strict=True)
218
+
219
+ def forward(self, hidden_states, timestep, encoder_hidden_states,
220
+ encoder_attention_mask=None, return_dict=True, skip_layers=None):
221
+ skip_layers = _validate_skip_layers(skip_layers, len(self.layers))
222
+ if hidden_states.ndim != 4 or hidden_states.shape[1] != self.config.latent_channels:
223
+ raise ValueError('hidden_states must be [B, latent_channels, H, W].')
224
+ batch, _, height, width = hidden_states.shape
225
+ if encoder_hidden_states.ndim != 3 or encoder_hidden_states.shape[0] != batch:
226
+ raise ValueError('encoder_hidden_states must be [B, S, text_dim].')
227
+ mask = encoder_attention_mask
228
+ if mask is None:
229
+ mask = torch.ones(encoder_hidden_states.shape[:2], dtype=torch.bool, device=hidden_states.device)
230
+ if mask.shape != encoder_hidden_states.shape[:2] or not torch.all((mask == 0) | (mask == 1)):
231
+ raise ValueError('encoder_attention_mask must be a binary [B, S] mask.')
232
+ times = torch.as_tensor(timestep, device=hidden_states.device, dtype=torch.float32).reshape(-1)
233
+ times = times.expand(batch)
234
+ # Training computes the sinusoid in float64 and feeds float32 into its MLP.
235
+ half = self.config.freq_dim // 2
236
+ freq = 10000 ** (-torch.arange(half, device=times.device, dtype=torch.float64) / half)
237
+ phase = times.double()[:, None] * freq
238
+ embedding = torch.cat((phase.cos(), phase.sin()), -1).float()
239
+ time = self.time_embedding(embedding.to(self.time_embedding[0].weight.dtype)).float()
240
+ mod = self.shared_block_modulation(time.to(self.shared_block_modulation.weight.dtype)).view(batch, 6, -1)
241
+ head = self.video_head_modulation(time.to(self.video_head_modulation.weight.dtype)).view(batch, 2, -1)
242
+ outputs = []
243
+ for index in range(batch):
244
+ context = encoder_hidden_states[index:index + 1, mask[index].bool()]
245
+ if context.shape[1] == 0:
246
+ raise ValueError('Each sample needs at least one valid text token.')
247
+ video = hidden_states[index:index + 1].flatten(2).transpose(1, 2)
248
+ video = self.video_embedder(video.to(self.video_embedder.weight.dtype).contiguous())
249
+ # Match the reference activation dtype under fp32 weights + autocast.
250
+ rope = self.rope(height, width, context.shape[1], hidden_states.device).to(video.dtype)
251
+ text = self.text_embedder(context, rope[:, height * width:])
252
+ x = torch.cat((video, text), 1)
253
+ for layer_index, layer in enumerate(self.layers):
254
+ if layer_index not in skip_layers:
255
+ x = layer(x, rope, mod[index:index + 1])
256
+ x = self.final_norm_video(x[:, :height * width].to(self.final_norm_video.weight.dtype))
257
+ x = x * (1 + head[index:index + 1, 1, None]) + head[index:index + 1, 0, None]
258
+ x = self.final_linear_video(x.to(self.final_linear_video.weight.dtype))
259
+ outputs.append(x.transpose(1, 2).reshape(1, -1, height, width))
260
+ sample = torch.cat(outputs)
261
+ return Transformer2DModelOutput(sample=sample) if return_dict else (sample,)
262
+
263
+
264
+ class MOSSImageScheduler(FlowMatchEulerDiscreteScheduler):
265
+ """Euler integration using the training project's V2, nearest-grid schedule."""
266
+ @register_to_config
267
+ def __init__(self, num_train_timesteps=1000, shift=5.0, training_shift=1.0):
268
+ super().__init__(num_train_timesteps=num_train_timesteps, shift=1.0)
269
+ self.register_to_config(shift=shift, training_shift=training_shift)
270
+
271
+ def set_timesteps(self, num_inference_steps=50, device=None):
272
+ if num_inference_steps < 1 or self.config.shift <= 0 or self.config.training_shift <= 0:
273
+ raise ValueError('Steps and shifts must be positive.')
274
+ total = self.config.num_train_timesteps
275
+ def grid(count, shift):
276
+ lower = shift / (total + shift - 1)
277
+ values = torch.linspace(1, lower, count)
278
+ return shift * values / (1 + (shift - 1) * values)
279
+ sigmas = grid(num_inference_steps, self.config.shift)
280
+ training = grid(total, self.config.training_shift).flip(0)
281
+ upper = torch.searchsorted(training, sigmas).clamp(max=total - 1)
282
+ lower = (upper - 1).clamp(min=0)
283
+ nearest = torch.where((sigmas - training[lower]).abs() <= (training[upper] - sigmas).abs(), lower, upper)
284
+ sigmas = torch.unique_consecutive(training[nearest])
285
+ # Our sigmas have already been shifted and snapped. Avoid a second shift.
286
+ self._shift = 1.0
287
+ super().set_timesteps(device=device, sigmas=sigmas.tolist())
288
+
289
+
290
+ class MOSSImagePipeline(DiffusionPipeline):
291
+ model_cpu_offload_seq = 'text_encoder->transformer->vae'
292
+ _optional_components = ['text_encoder', 'tokenizer', 'vae']
293
+
294
+ def __init__(self, transformer: MOSSImageTransformer,
295
+ scheduler: MOSSImageScheduler, text_encoder=None, tokenizer=None, vae=None):
296
+ super().__init__()
297
+ self.register_modules(transformer=transformer, scheduler=scheduler,
298
+ text_encoder=text_encoder, tokenizer=tokenizer, vae=vae)
299
+ self.image_processor = VaeImageProcessor(vae_scale_factor=16)
300
+
301
+ @classmethod
302
+ def from_components_pretrained(cls, transformer_path, text_encoder_path, vae_path,
303
+ torch_dtype=torch.bfloat16, vae_dtype=torch.float32,
304
+ local_files_only=False):
305
+ common = dict(local_files_only=local_files_only)
306
+ transformer = MOSSImageTransformer.from_pretrained(transformer_path, torch_dtype=torch_dtype, **common)
307
+ tokenizer = AutoTokenizer.from_pretrained(text_encoder_path, **common)
308
+ text_encoder = AutoModel.from_pretrained(text_encoder_path, dtype=torch_dtype, **common).eval()
309
+ subfolder = None if (Path(vae_path) / 'config.json').is_file() else 'vae'
310
+ vae = AutoencoderKLFlux2.from_pretrained(vae_path, subfolder=subfolder, torch_dtype=vae_dtype, **common).eval()
311
+ return cls(transformer.eval(), MOSSImageScheduler(), text_encoder, tokenizer, vae)
312
+
313
+ @torch.no_grad()
314
+ def encode_prompt(self, prompt, max_sequence_length=4096):
315
+ if self.text_encoder is None or self.tokenizer is None:
316
+ raise ValueError('Text encoder/tokenizer required for string prompts.')
317
+ prompts = [prompt] if isinstance(prompt, str) else list(prompt)
318
+ if not prompts or not all(isinstance(p, str) for p in prompts):
319
+ raise ValueError('prompt must be a string or a nonempty list of strings.')
320
+ rows = []
321
+ # Unpadded individual forwards also avoid padding effects in Qwen GDN.
322
+ for value in prompts:
323
+ formatted = self.tokenizer.apply_chat_template(
324
+ [{'role': 'user', 'content': value}], tokenize=False,
325
+ add_generation_prompt=True, enable_thinking=True)
326
+ inputs = self.tokenizer(formatted, return_tensors='pt', truncation=True,
327
+ max_length=max_sequence_length).to(self._execution_device)
328
+ output = self.text_encoder(**inputs, use_cache=False, return_dict=True)
329
+ rows.append(output.last_hidden_state[0])
330
+ length = max(row.shape[0] for row in rows)
331
+ embeds = rows[0].new_zeros(len(rows), length, rows[0].shape[-1])
332
+ mask = torch.zeros(len(rows), length, device=embeds.device, dtype=torch.bool)
333
+ for index, row in enumerate(rows):
334
+ embeds[index, :row.shape[0]] = row
335
+ mask[index, :row.shape[0]] = True
336
+ return embeds, mask
337
+
338
+ @torch.no_grad()
339
+ def __call__(self, prompt=None, *, height=1024, width=1024, num_inference_steps=50,
340
+ guidance_scale=3.0, negative_prompt='', num_images_per_prompt=1,
341
+ generator=None, latents=None, prompt_embeds=None, prompt_attention_mask=None,
342
+ negative_prompt_embeds=None, negative_prompt_attention_mask=None,
343
+ max_sequence_length=4096, output_type='pil', return_dict=True,
344
+ uncond_skip_layers=(22,), slg_layers=None, slg_scale=0.0,
345
+ slg_start=0.0, slg_stop=1.0):
346
+ """Generate images with optional full-block skipping (zero-based indices).
347
+
348
+ uncond_skip_layers defaults to (22,) and replaces CFG's negative-prompt
349
+ prediction with a skipped-block prediction, retaining two forwards per
350
+ step. Pass None or an empty sequence to disable skipping. slg_layers
351
+ adds a skipped positive-prompt forward and the fp32 correction
352
+ slg_scale * (cond - cond_skip) to CFG. With guidance_scale=1, active
353
+ SLG uses cond_skip + slg_scale * (cond - cond_skip), matching Diffusers'
354
+ default SkipLayerGuidance formulation, with two forwards and no negative
355
+ branch. Both options can combine. slg_scale=1 then gives normal cond.
356
+ Both uncond skipping and SLG are active where
357
+ slg_start <= step_index / num_steps < slg_stop;
358
+ the interval follows denoising order, from initial noise to final image.
359
+ Outside it, inference uses normal CFG without skipping.
360
+ slg_scale=0 or empty slg_layers disables the additional forward.
361
+ """
362
+ uncond_skip_layers = _validate_skip_layers(
363
+ uncond_skip_layers, len(self.transformer.layers), 'uncond_skip_layers')
364
+ slg_layers = _validate_skip_layers(slg_layers, len(self.transformer.layers), 'slg_layers')
365
+ if not math.isfinite(slg_scale) or slg_scale < 0:
366
+ raise ValueError('slg_scale must be finite and nonnegative.')
367
+ if not 0 <= slg_start <= slg_stop <= 1:
368
+ raise ValueError('Require 0 <= slg_start <= slg_stop <= 1.')
369
+ if height <= 0 or width <= 0 or height % 16 or width % 16:
370
+ raise ValueError('height and width must be positive multiples of 16.')
371
+ if output_type not in {'pil', 'np', 'pt', 'latent'}:
372
+ raise ValueError('output_type must be pil, np, pt or latent.')
373
+ if num_images_per_prompt < 1 or not math.isfinite(guidance_scale) or guidance_scale < 0:
374
+ raise ValueError('Invalid image count or guidance scale.')
375
+ if not 1 <= max_sequence_length <= 4096:
376
+ raise ValueError('max_sequence_length must be between 1 and 4096.')
377
+ if (prompt is None) == (prompt_embeds is None):
378
+ raise ValueError('Provide exactly one of prompt and prompt_embeds.')
379
+ device = self._execution_device
380
+ if prompt_embeds is None:
381
+ prompt_embeds, prompt_attention_mask = self.encode_prompt(prompt, max_sequence_length)
382
+ batch = prompt_embeds.shape[0]
383
+ def prepare(embeds, mask):
384
+ if embeds.ndim != 3 or embeds.shape[0] != batch:
385
+ raise ValueError('Prompt embeddings must be [B, S, C] with matching batch size.')
386
+ if mask is None:
387
+ mask = torch.ones(embeds.shape[:2], dtype=torch.bool, device=embeds.device)
388
+ return (embeds.to(device).repeat_interleave(num_images_per_prompt, 0),
389
+ mask.to(device).repeat_interleave(num_images_per_prompt, 0))
390
+ positive, positive_mask = prepare(prompt_embeds, prompt_attention_mask)
391
+ do_cfg = guidance_scale != 1.0
392
+ if do_cfg:
393
+ if negative_prompt_embeds is None:
394
+ negatives = [negative_prompt] * batch if isinstance(negative_prompt, str) else negative_prompt
395
+ if len(negatives) != batch:
396
+ raise ValueError('negative_prompt must match prompt batch size.')
397
+ negative_prompt_embeds, negative_prompt_attention_mask = self.encode_prompt(negatives, max_sequence_length)
398
+ negative, negative_mask = prepare(negative_prompt_embeds, negative_prompt_attention_mask)
399
+ shape = (batch * num_images_per_prompt, self.transformer.config.latent_channels, height // 16, width // 16)
400
+ if latents is None:
401
+ # Sample in fp32: promoting bf16 noise afterward cannot restore its precision.
402
+ latents = randn_tensor(shape, generator=generator, device=device, dtype=torch.float32)
403
+ elif tuple(latents.shape) != shape:
404
+ raise ValueError(f'latents must have shape {shape}.')
405
+ else:
406
+ latents = latents.to(device=device, dtype=torch.float32)
407
+ self.scheduler.set_timesteps(num_inference_steps, device=device)
408
+ num_steps = len(self.scheduler.timesteps)
409
+ for step_index, timestep in enumerate(self.progress_bar(self.scheduler.timesteps)):
410
+ skip_active = slg_start <= step_index / num_steps < slg_stop
411
+ # CFG must combine fp32 predictions, even when the DiT runs in bf16.
412
+ # Converting only the combined velocity would preserve rounding errors.
413
+ prediction = self.transformer(latents, timestep, positive, positive_mask).sample.float()
414
+ conditional = prediction
415
+ if do_cfg:
416
+ skip_kwargs = {'skip_layers': uncond_skip_layers} if uncond_skip_layers and skip_active else {}
417
+ uncond = self.transformer(latents, timestep, negative, negative_mask, **skip_kwargs).sample.float()
418
+ prediction = uncond + guidance_scale * (prediction - uncond)
419
+ if slg_layers and slg_scale > 0 and skip_active:
420
+ skipped = self.transformer(
421
+ latents, timestep, positive, positive_mask, skip_layers=slg_layers).sample.float()
422
+ if not do_cfg:
423
+ prediction = skipped
424
+ prediction = prediction + slg_scale * (conditional - skipped)
425
+ latents = self.scheduler.step(prediction, timestep, latents, return_dict=False)[0]
426
+ if output_type == 'latent':
427
+ images = latents
428
+ else:
429
+ if self.vae is None:
430
+ raise ValueError('A FLUX.2 VAE is required for decoded output.')
431
+ z = latents.to(dtype=self.vae.dtype)
432
+ bn = self.vae.bn
433
+ eps = self.vae.config.batch_norm_eps
434
+ z = z * (bn.running_var + eps).sqrt().to(z).view(1, -1, 1, 1) + bn.running_mean.to(z).view(1, -1, 1, 1)
435
+ b, c, h, w = z.shape
436
+ 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)
437
+ images = self.vae.decode(z, return_dict=False)[0].clamp(-1, 1)
438
+ images = self.image_processor.postprocess(images, output_type=output_type)
439
+ self.maybe_free_model_hooks()
440
+ return ImagePipelineOutput(images=images) if return_dict else (images,)
scheduler/pipeline_moss_image.py ADDED
@@ -0,0 +1,440 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MOSSImage modeling and text-to-image pipeline (no Motor/TorchTitan imports).
2
+
3
+ Dependencies: torch>=2.9, diffusers>=0.38, transformers>=5.3, safetensors,
4
+ accelerate, Pillow. Copy this file anywhere and import it normally.
5
+
6
+ import torch
7
+ # Import MOSSImagePipeline from this file for component-level loading.
8
+ pipe = MOSSImagePipeline.from_components_pretrained(
9
+ transformer_path="exported_transformer", # save_pretrained directory
10
+ text_encoder_path="Qwen/Qwen3.5-9B",
11
+ vae_path="black-forest-labs/FLUX.2-klein-4B",
12
+ ).to("cuda")
13
+ pipe("A cat", generator=torch.Generator("cuda").manual_seed(42)).images[0].save("cat.png")
14
+
15
+ Default guidance uses CFG 3 and skips block 22 only in the negative branch
16
+ (zero-based main DiT block indices; two forwards per sampling step):
17
+ pipe("A cat", guidance_scale=3.0, uncond_skip_layers=[22])
18
+ pipe("A cat", uncond_skip_layers=[]) # disable negative-branch skipping
19
+ Optional additional positive-branch SLG adds a third forward:
20
+ pipe("A cat", slg_layers=[24], slg_scale=3.0)
21
+ The additional SLG layer/scale above illustrates the API, not a quality-tuned setting.
22
+
23
+ For a consolidated training state dict, create MOSSImageTransformer and
24
+ call load_training_state_dict(state), then save_pretrained("exported_transformer").
25
+ Select raw or EMA weights explicitly before calling it; distributed checkpoint
26
+ shards/optimizer states are not model state dicts. Defaults match the finalized dense MOSSImage model,
27
+ not MoE, CFG-distilled, video, or reference-image variants. Prompt strings are
28
+ already-rendered captions (dataset-specific quality/domain tags are not added).
29
+
30
+ First-release defaults: bf16 DiT/text-encoder weights, fp32 RoPE frequencies
31
+ and phase/sin/cos calculation, then RoPE cast to the video activation dtype.
32
+ Initial Gaussian noise is sampled directly in fp32, including with bf16 weights.
33
+ CFG and Euler latent updates use fp32; VAE decoding defaults to fp32. Scheduler
34
+ shift defaults to 5.0. Exact numerical parity with the original fp32-master
35
+ inference pipeline is not a release requirement.
36
+ """
37
+ from __future__ import annotations
38
+
39
+ import math
40
+ from pathlib import Path
41
+
42
+ import torch
43
+ from torch import nn
44
+ from torch.nn import functional as F
45
+ from diffusers import AutoencoderKLFlux2, DiffusionPipeline, FlowMatchEulerDiscreteScheduler
46
+ from diffusers.schedulers.scheduling_utils import SchedulerMixin # Required by custom-component discovery.
47
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
48
+ from diffusers.image_processor import VaeImageProcessor
49
+ from diffusers.models.modeling_utils import ModelMixin
50
+ from diffusers.models.modeling_outputs import Transformer2DModelOutput
51
+ from diffusers.pipelines.pipeline_utils import ImagePipelineOutput
52
+ from diffusers.utils.torch_utils import randn_tensor
53
+ from transformers import AutoModel, AutoTokenizer
54
+
55
+
56
+ class _RMSNorm(nn.Module):
57
+ def __init__(self, dim):
58
+ super().__init__()
59
+ self.weight = nn.Parameter(torch.ones(dim))
60
+
61
+ def forward(self, x):
62
+ y = x.float() * torch.rsqrt(x.float().square().mean(-1, keepdim=True) + 1e-6)
63
+ return y.to(x.dtype) * self.weight
64
+
65
+
66
+ def _situ(hidden, gate):
67
+ return (4.0 * torch.tanh(gate / 4.0) * torch.sigmoid(gate)) * (25.0 * torch.tanh(hidden / 25.0))
68
+
69
+
70
+ def _validate_skip_layers(layers, depth, name='skip_layers'):
71
+ layers = () if layers is None else tuple(layers)
72
+ if any(type(index) is not int or not 0 <= index < depth for index in layers):
73
+ raise ValueError(f'{name} must contain integer block indices in [0, {depth}).')
74
+ return frozenset(layers)
75
+
76
+
77
+ def _rotate(x, rope):
78
+ sin, cos = rope.chunk(2, -1)
79
+ sin = torch.cat((sin, sin), -1).unsqueeze(-2)
80
+ cos = torch.cat((cos, cos), -1).unsqueeze(-2)
81
+ a, b = x.chunk(2, -1)
82
+ return x * cos + torch.cat((-b, a), -1) * sin
83
+
84
+
85
+ def _attention(q, k, v, rope, heads):
86
+ shape = (*q.shape[:2], heads, q.shape[-1] // heads)
87
+ q = _rotate(q.reshape(shape), rope).transpose(1, 2)
88
+ k = _rotate(k.reshape(shape), rope).transpose(1, 2)
89
+ v = v.reshape(shape).transpose(1, 2)
90
+ return F.scaled_dot_product_attention(q.to(v.dtype), k.to(v.dtype), v).transpose(1, 2).flatten(2)
91
+
92
+
93
+ class _TextBlock(nn.Module):
94
+ def __init__(self, dim, ffn_dim, heads):
95
+ super().__init__()
96
+ self.heads = heads
97
+ self.norm1, self.norm2 = _RMSNorm(dim), _RMSNorm(dim)
98
+ self.q_norm, self.k_norm = _RMSNorm(dim // heads), _RMSNorm(dim // heads)
99
+ self.qkv = nn.Linear(dim, 3 * dim)
100
+ self.proj = nn.Linear(dim, dim)
101
+ self.ffn_gate, self.ffn_up = nn.Linear(dim, ffn_dim), nn.Linear(dim, ffn_dim)
102
+ self.ffn_down = nn.Linear(ffn_dim, dim)
103
+
104
+ def forward(self, x, rope):
105
+ q, k, v = self.qkv(self.norm1(x)).chunk(3, -1)
106
+ q = self.q_norm(q.unflatten(-1, (self.heads, -1))).flatten(2)
107
+ k = self.k_norm(k.unflatten(-1, (self.heads, -1))).flatten(2)
108
+ x = x + self.proj(_attention(q, k, v, rope, self.heads))
109
+ y = self.norm2(x)
110
+ return x + self.ffn_down(_situ(self.ffn_up(y), self.ffn_gate(y)))
111
+
112
+
113
+ class _TextProjection(nn.Module):
114
+ def __init__(self, text_dim, dim, ffn_dim, heads):
115
+ super().__init__()
116
+ self.proj = nn.Linear(text_dim, dim)
117
+ self.blocks = nn.ModuleList([_TextBlock(dim, ffn_dim, heads) for _ in range(2)])
118
+
119
+ def forward(self, x, rope):
120
+ x = self.proj(x.to(self.proj.weight.dtype))
121
+ for block in self.blocks:
122
+ x = block(x, rope)
123
+ return x
124
+
125
+
126
+ class _Block(nn.Module):
127
+ def __init__(self, dim, ffn_dim, heads):
128
+ super().__init__()
129
+ self.heads = heads
130
+ self.norm1, self.norm2 = _RMSNorm(dim), _RMSNorm(dim)
131
+ self.q_norm, self.k_norm = _RMSNorm(dim // heads), _RMSNorm(dim // heads)
132
+ self.proj = nn.ModuleDict({
133
+ name: nn.Linear(dim, dim, bias=False) for name in ('q', 'k', 'v', 'o')
134
+ })
135
+ self.proj['ffn_up_gate'] = nn.Linear(dim, 2 * ffn_dim, bias=False)
136
+ self.proj['ffn_down'] = nn.Linear(ffn_dim, dim, bias=False)
137
+
138
+ def forward(self, x, rope, modulation):
139
+ shift_a, scale_a, gate_a, shift_f, scale_f, gate_f = modulation.unbind(1)
140
+ y = self.norm1(x) * (1 + scale_a[:, None]) + shift_a[:, None]
141
+ dtype = self.proj['q'].weight.dtype
142
+ y = y.to(dtype)
143
+ q = self.q_norm(self.proj['q'](y).unflatten(-1, (self.heads, -1))).flatten(2)
144
+ k = self.k_norm(self.proj['k'](y).unflatten(-1, (self.heads, -1))).flatten(2)
145
+ x = x + self.proj['o'](_attention(q, k, self.proj['v'](y), rope, self.heads)) * gate_a[:, None]
146
+ y = self.norm2(x) * (1 + scale_f[:, None]) + shift_f[:, None]
147
+ hidden, gate = self.proj['ffn_up_gate'](y.to(dtype)).chunk(2, -1)
148
+ return x + self.proj['ffn_down'](_situ(hidden, gate)) * gate_f[:, None]
149
+
150
+
151
+ class _RoPE(nn.Module):
152
+ def __init__(self, head_dim, base):
153
+ super().__init__()
154
+ self.num_bands = head_dim // 8
155
+ self.base = base
156
+ self.register_buffer('bands', self._default_bands(torch.device('cpu')))
157
+
158
+ def _default_bands(self, device):
159
+ # Match training's CPU fp32 construction, including under meta init.
160
+ exponent = torch.arange(self.num_bands, device='cpu', dtype=torch.float32) / self.num_bands
161
+ return (1 / (self.base ** exponent)).to(device)
162
+
163
+ def _apply(self, fn, recurse=True):
164
+ super()._apply(fn, recurse=recurse)
165
+ # .float() cannot recover frequencies already rounded by .to(bf16).
166
+ self.bands = self._default_bands(self.bands.device)
167
+ return self
168
+
169
+ def _load_from_state_dict(self, *args, **kwargs):
170
+ super()._load_from_state_dict(*args, **kwargs)
171
+ # Frequencies are derived constants; retain the key for checkpoint compatibility.
172
+ self.bands = self._default_bands(self.bands.device)
173
+
174
+ def forward(self, height, width, text_length, device):
175
+ # Diffusers/Accelerate can install tensors directly, bypassing load hooks.
176
+ if self.bands.dtype != torch.float32:
177
+ self.bands = self._default_bands(device)
178
+ # Coordinates: (r, f, h, w); sequence order is image followed by text.
179
+ h, w = torch.meshgrid(torch.arange(height, device=device), torch.arange(width, device=device), indexing='ij')
180
+ image = torch.stack((torch.zeros_like(h), torch.full_like(h, text_length), h, w), -1).flatten(0, 1).float()
181
+ image[:, 2] -= (height - 1) / 2
182
+ image[:, 3] -= (width - 1) / 2
183
+ text = torch.zeros(text_length, 4, device=device, dtype=torch.float32)
184
+ text[:, 1] = torch.arange(text_length, device=device)
185
+ phase = torch.cat((image, text), 0).unsqueeze(-1) * self.bands
186
+ return torch.cat((phase.sin(), phase.cos()), -2).flatten(-2).unsqueeze(0)
187
+
188
+
189
+ class MOSSImageTransformer(ModelMixin, ConfigMixin):
190
+ """MOSSImage DiT with training-compatible state-dict names and SDPA attention.
191
+
192
+ Each sample is evaluated at its true text length, including text refinement;
193
+ padded tokens cannot influence either text or joint attention.
194
+ """
195
+ @register_to_config
196
+ def __init__(self, dim=3072, ffn_dim=8192, num_heads=24, num_layers=42,
197
+ text_dim=4096, freq_dim=256, latent_channels=128, rope_base=10000.0):
198
+ super().__init__()
199
+ if dim % num_heads or (dim // num_heads) % 8 or freq_dim % 2:
200
+ raise ValueError('Require dim divisible by heads, head_dim divisible by 8, and even freq_dim.')
201
+ self.video_embedder = nn.Linear(latent_channels, dim)
202
+ self.text_embedder = _TextProjection(text_dim, dim, ffn_dim, num_heads)
203
+ self.rope = _RoPE(dim // num_heads, rope_base)
204
+ self.time_embedding = nn.Sequential(nn.Linear(freq_dim, dim), nn.SiLU(), nn.Linear(dim, dim))
205
+ self.shared_block_modulation = nn.Linear(dim, 6 * dim)
206
+ self.layers = nn.ModuleList([_Block(dim, ffn_dim, num_heads) for _ in range(num_layers)])
207
+ self.final_norm_video = _RMSNorm(dim)
208
+ self.final_linear_video = nn.Linear(dim, latent_channels, bias=False)
209
+ self.video_head_modulation = nn.Linear(dim, 2 * dim)
210
+
211
+ def load_training_state_dict(self, state_dict, *, prefix=''):
212
+ """Load a consolidated raw/EMA model mapping, strictly; no silent key drops."""
213
+ if prefix:
214
+ if any(not key.startswith(prefix) for key in state_dict):
215
+ raise ValueError('All keys must start with the explicit prefix.')
216
+ state_dict = {key[len(prefix):]: value for key, value in state_dict.items()}
217
+ return self.load_state_dict(state_dict, strict=True)
218
+
219
+ def forward(self, hidden_states, timestep, encoder_hidden_states,
220
+ encoder_attention_mask=None, return_dict=True, skip_layers=None):
221
+ skip_layers = _validate_skip_layers(skip_layers, len(self.layers))
222
+ if hidden_states.ndim != 4 or hidden_states.shape[1] != self.config.latent_channels:
223
+ raise ValueError('hidden_states must be [B, latent_channels, H, W].')
224
+ batch, _, height, width = hidden_states.shape
225
+ if encoder_hidden_states.ndim != 3 or encoder_hidden_states.shape[0] != batch:
226
+ raise ValueError('encoder_hidden_states must be [B, S, text_dim].')
227
+ mask = encoder_attention_mask
228
+ if mask is None:
229
+ mask = torch.ones(encoder_hidden_states.shape[:2], dtype=torch.bool, device=hidden_states.device)
230
+ if mask.shape != encoder_hidden_states.shape[:2] or not torch.all((mask == 0) | (mask == 1)):
231
+ raise ValueError('encoder_attention_mask must be a binary [B, S] mask.')
232
+ times = torch.as_tensor(timestep, device=hidden_states.device, dtype=torch.float32).reshape(-1)
233
+ times = times.expand(batch)
234
+ # Training computes the sinusoid in float64 and feeds float32 into its MLP.
235
+ half = self.config.freq_dim // 2
236
+ freq = 10000 ** (-torch.arange(half, device=times.device, dtype=torch.float64) / half)
237
+ phase = times.double()[:, None] * freq
238
+ embedding = torch.cat((phase.cos(), phase.sin()), -1).float()
239
+ time = self.time_embedding(embedding.to(self.time_embedding[0].weight.dtype)).float()
240
+ mod = self.shared_block_modulation(time.to(self.shared_block_modulation.weight.dtype)).view(batch, 6, -1)
241
+ head = self.video_head_modulation(time.to(self.video_head_modulation.weight.dtype)).view(batch, 2, -1)
242
+ outputs = []
243
+ for index in range(batch):
244
+ context = encoder_hidden_states[index:index + 1, mask[index].bool()]
245
+ if context.shape[1] == 0:
246
+ raise ValueError('Each sample needs at least one valid text token.')
247
+ video = hidden_states[index:index + 1].flatten(2).transpose(1, 2)
248
+ video = self.video_embedder(video.to(self.video_embedder.weight.dtype).contiguous())
249
+ # Match the reference activation dtype under fp32 weights + autocast.
250
+ rope = self.rope(height, width, context.shape[1], hidden_states.device).to(video.dtype)
251
+ text = self.text_embedder(context, rope[:, height * width:])
252
+ x = torch.cat((video, text), 1)
253
+ for layer_index, layer in enumerate(self.layers):
254
+ if layer_index not in skip_layers:
255
+ x = layer(x, rope, mod[index:index + 1])
256
+ x = self.final_norm_video(x[:, :height * width].to(self.final_norm_video.weight.dtype))
257
+ x = x * (1 + head[index:index + 1, 1, None]) + head[index:index + 1, 0, None]
258
+ x = self.final_linear_video(x.to(self.final_linear_video.weight.dtype))
259
+ outputs.append(x.transpose(1, 2).reshape(1, -1, height, width))
260
+ sample = torch.cat(outputs)
261
+ return Transformer2DModelOutput(sample=sample) if return_dict else (sample,)
262
+
263
+
264
+ class MOSSImageScheduler(FlowMatchEulerDiscreteScheduler):
265
+ """Euler integration using the training project's V2, nearest-grid schedule."""
266
+ @register_to_config
267
+ def __init__(self, num_train_timesteps=1000, shift=5.0, training_shift=1.0):
268
+ super().__init__(num_train_timesteps=num_train_timesteps, shift=1.0)
269
+ self.register_to_config(shift=shift, training_shift=training_shift)
270
+
271
+ def set_timesteps(self, num_inference_steps=50, device=None):
272
+ if num_inference_steps < 1 or self.config.shift <= 0 or self.config.training_shift <= 0:
273
+ raise ValueError('Steps and shifts must be positive.')
274
+ total = self.config.num_train_timesteps
275
+ def grid(count, shift):
276
+ lower = shift / (total + shift - 1)
277
+ values = torch.linspace(1, lower, count)
278
+ return shift * values / (1 + (shift - 1) * values)
279
+ sigmas = grid(num_inference_steps, self.config.shift)
280
+ training = grid(total, self.config.training_shift).flip(0)
281
+ upper = torch.searchsorted(training, sigmas).clamp(max=total - 1)
282
+ lower = (upper - 1).clamp(min=0)
283
+ nearest = torch.where((sigmas - training[lower]).abs() <= (training[upper] - sigmas).abs(), lower, upper)
284
+ sigmas = torch.unique_consecutive(training[nearest])
285
+ # Our sigmas have already been shifted and snapped. Avoid a second shift.
286
+ self._shift = 1.0
287
+ super().set_timesteps(device=device, sigmas=sigmas.tolist())
288
+
289
+
290
+ class MOSSImagePipeline(DiffusionPipeline):
291
+ model_cpu_offload_seq = 'text_encoder->transformer->vae'
292
+ _optional_components = ['text_encoder', 'tokenizer', 'vae']
293
+
294
+ def __init__(self, transformer: MOSSImageTransformer,
295
+ scheduler: MOSSImageScheduler, text_encoder=None, tokenizer=None, vae=None):
296
+ super().__init__()
297
+ self.register_modules(transformer=transformer, scheduler=scheduler,
298
+ text_encoder=text_encoder, tokenizer=tokenizer, vae=vae)
299
+ self.image_processor = VaeImageProcessor(vae_scale_factor=16)
300
+
301
+ @classmethod
302
+ def from_components_pretrained(cls, transformer_path, text_encoder_path, vae_path,
303
+ torch_dtype=torch.bfloat16, vae_dtype=torch.float32,
304
+ local_files_only=False):
305
+ common = dict(local_files_only=local_files_only)
306
+ transformer = MOSSImageTransformer.from_pretrained(transformer_path, torch_dtype=torch_dtype, **common)
307
+ tokenizer = AutoTokenizer.from_pretrained(text_encoder_path, **common)
308
+ text_encoder = AutoModel.from_pretrained(text_encoder_path, dtype=torch_dtype, **common).eval()
309
+ subfolder = None if (Path(vae_path) / 'config.json').is_file() else 'vae'
310
+ vae = AutoencoderKLFlux2.from_pretrained(vae_path, subfolder=subfolder, torch_dtype=vae_dtype, **common).eval()
311
+ return cls(transformer.eval(), MOSSImageScheduler(), text_encoder, tokenizer, vae)
312
+
313
+ @torch.no_grad()
314
+ def encode_prompt(self, prompt, max_sequence_length=4096):
315
+ if self.text_encoder is None or self.tokenizer is None:
316
+ raise ValueError('Text encoder/tokenizer required for string prompts.')
317
+ prompts = [prompt] if isinstance(prompt, str) else list(prompt)
318
+ if not prompts or not all(isinstance(p, str) for p in prompts):
319
+ raise ValueError('prompt must be a string or a nonempty list of strings.')
320
+ rows = []
321
+ # Unpadded individual forwards also avoid padding effects in Qwen GDN.
322
+ for value in prompts:
323
+ formatted = self.tokenizer.apply_chat_template(
324
+ [{'role': 'user', 'content': value}], tokenize=False,
325
+ add_generation_prompt=True, enable_thinking=True)
326
+ inputs = self.tokenizer(formatted, return_tensors='pt', truncation=True,
327
+ max_length=max_sequence_length).to(self._execution_device)
328
+ output = self.text_encoder(**inputs, use_cache=False, return_dict=True)
329
+ rows.append(output.last_hidden_state[0])
330
+ length = max(row.shape[0] for row in rows)
331
+ embeds = rows[0].new_zeros(len(rows), length, rows[0].shape[-1])
332
+ mask = torch.zeros(len(rows), length, device=embeds.device, dtype=torch.bool)
333
+ for index, row in enumerate(rows):
334
+ embeds[index, :row.shape[0]] = row
335
+ mask[index, :row.shape[0]] = True
336
+ return embeds, mask
337
+
338
+ @torch.no_grad()
339
+ def __call__(self, prompt=None, *, height=1024, width=1024, num_inference_steps=50,
340
+ guidance_scale=3.0, negative_prompt='', num_images_per_prompt=1,
341
+ generator=None, latents=None, prompt_embeds=None, prompt_attention_mask=None,
342
+ negative_prompt_embeds=None, negative_prompt_attention_mask=None,
343
+ max_sequence_length=4096, output_type='pil', return_dict=True,
344
+ uncond_skip_layers=(22,), slg_layers=None, slg_scale=0.0,
345
+ slg_start=0.0, slg_stop=1.0):
346
+ """Generate images with optional full-block skipping (zero-based indices).
347
+
348
+ uncond_skip_layers defaults to (22,) and replaces CFG's negative-prompt
349
+ prediction with a skipped-block prediction, retaining two forwards per
350
+ step. Pass None or an empty sequence to disable skipping. slg_layers
351
+ adds a skipped positive-prompt forward and the fp32 correction
352
+ slg_scale * (cond - cond_skip) to CFG. With guidance_scale=1, active
353
+ SLG uses cond_skip + slg_scale * (cond - cond_skip), matching Diffusers'
354
+ default SkipLayerGuidance formulation, with two forwards and no negative
355
+ branch. Both options can combine. slg_scale=1 then gives normal cond.
356
+ Both uncond skipping and SLG are active where
357
+ slg_start <= step_index / num_steps < slg_stop;
358
+ the interval follows denoising order, from initial noise to final image.
359
+ Outside it, inference uses normal CFG without skipping.
360
+ slg_scale=0 or empty slg_layers disables the additional forward.
361
+ """
362
+ uncond_skip_layers = _validate_skip_layers(
363
+ uncond_skip_layers, len(self.transformer.layers), 'uncond_skip_layers')
364
+ slg_layers = _validate_skip_layers(slg_layers, len(self.transformer.layers), 'slg_layers')
365
+ if not math.isfinite(slg_scale) or slg_scale < 0:
366
+ raise ValueError('slg_scale must be finite and nonnegative.')
367
+ if not 0 <= slg_start <= slg_stop <= 1:
368
+ raise ValueError('Require 0 <= slg_start <= slg_stop <= 1.')
369
+ if height <= 0 or width <= 0 or height % 16 or width % 16:
370
+ raise ValueError('height and width must be positive multiples of 16.')
371
+ if output_type not in {'pil', 'np', 'pt', 'latent'}:
372
+ raise ValueError('output_type must be pil, np, pt or latent.')
373
+ if num_images_per_prompt < 1 or not math.isfinite(guidance_scale) or guidance_scale < 0:
374
+ raise ValueError('Invalid image count or guidance scale.')
375
+ if not 1 <= max_sequence_length <= 4096:
376
+ raise ValueError('max_sequence_length must be between 1 and 4096.')
377
+ if (prompt is None) == (prompt_embeds is None):
378
+ raise ValueError('Provide exactly one of prompt and prompt_embeds.')
379
+ device = self._execution_device
380
+ if prompt_embeds is None:
381
+ prompt_embeds, prompt_attention_mask = self.encode_prompt(prompt, max_sequence_length)
382
+ batch = prompt_embeds.shape[0]
383
+ def prepare(embeds, mask):
384
+ if embeds.ndim != 3 or embeds.shape[0] != batch:
385
+ raise ValueError('Prompt embeddings must be [B, S, C] with matching batch size.')
386
+ if mask is None:
387
+ mask = torch.ones(embeds.shape[:2], dtype=torch.bool, device=embeds.device)
388
+ return (embeds.to(device).repeat_interleave(num_images_per_prompt, 0),
389
+ mask.to(device).repeat_interleave(num_images_per_prompt, 0))
390
+ positive, positive_mask = prepare(prompt_embeds, prompt_attention_mask)
391
+ do_cfg = guidance_scale != 1.0
392
+ if do_cfg:
393
+ if negative_prompt_embeds is None:
394
+ negatives = [negative_prompt] * batch if isinstance(negative_prompt, str) else negative_prompt
395
+ if len(negatives) != batch:
396
+ raise ValueError('negative_prompt must match prompt batch size.')
397
+ negative_prompt_embeds, negative_prompt_attention_mask = self.encode_prompt(negatives, max_sequence_length)
398
+ negative, negative_mask = prepare(negative_prompt_embeds, negative_prompt_attention_mask)
399
+ shape = (batch * num_images_per_prompt, self.transformer.config.latent_channels, height // 16, width // 16)
400
+ if latents is None:
401
+ # Sample in fp32: promoting bf16 noise afterward cannot restore its precision.
402
+ latents = randn_tensor(shape, generator=generator, device=device, dtype=torch.float32)
403
+ elif tuple(latents.shape) != shape:
404
+ raise ValueError(f'latents must have shape {shape}.')
405
+ else:
406
+ latents = latents.to(device=device, dtype=torch.float32)
407
+ self.scheduler.set_timesteps(num_inference_steps, device=device)
408
+ num_steps = len(self.scheduler.timesteps)
409
+ for step_index, timestep in enumerate(self.progress_bar(self.scheduler.timesteps)):
410
+ skip_active = slg_start <= step_index / num_steps < slg_stop
411
+ # CFG must combine fp32 predictions, even when the DiT runs in bf16.
412
+ # Converting only the combined velocity would preserve rounding errors.
413
+ prediction = self.transformer(latents, timestep, positive, positive_mask).sample.float()
414
+ conditional = prediction
415
+ if do_cfg:
416
+ skip_kwargs = {'skip_layers': uncond_skip_layers} if uncond_skip_layers and skip_active else {}
417
+ uncond = self.transformer(latents, timestep, negative, negative_mask, **skip_kwargs).sample.float()
418
+ prediction = uncond + guidance_scale * (prediction - uncond)
419
+ if slg_layers and slg_scale > 0 and skip_active:
420
+ skipped = self.transformer(
421
+ latents, timestep, positive, positive_mask, skip_layers=slg_layers).sample.float()
422
+ if not do_cfg:
423
+ prediction = skipped
424
+ prediction = prediction + slg_scale * (conditional - skipped)
425
+ latents = self.scheduler.step(prediction, timestep, latents, return_dict=False)[0]
426
+ if output_type == 'latent':
427
+ images = latents
428
+ else:
429
+ if self.vae is None:
430
+ raise ValueError('A FLUX.2 VAE is required for decoded output.')
431
+ z = latents.to(dtype=self.vae.dtype)
432
+ bn = self.vae.bn
433
+ eps = self.vae.config.batch_norm_eps
434
+ z = z * (bn.running_var + eps).sqrt().to(z).view(1, -1, 1, 1) + bn.running_mean.to(z).view(1, -1, 1, 1)
435
+ b, c, h, w = z.shape
436
+ 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)
437
+ images = self.vae.decode(z, return_dict=False)[0].clamp(-1, 1)
438
+ images = self.image_processor.postprocess(images, output_type=output_type)
439
+ self.maybe_free_model_hooks()
440
+ return ImagePipelineOutput(images=images) if return_dict else (images,)
scheduler/scheduler_config.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "MOSSImageScheduler",
3
+ "_diffusers_version": "0.38.0",
4
+ "base_image_seq_len": 256,
5
+ "base_shift": 0.5,
6
+ "invert_sigmas": false,
7
+ "max_image_seq_len": 4096,
8
+ "max_shift": 1.15,
9
+ "num_train_timesteps": 1000,
10
+ "shift": 5.0,
11
+ "shift_terminal": null,
12
+ "stochastic_sampling": false,
13
+ "time_shift_type": "exponential",
14
+ "training_shift": 1.0,
15
+ "use_beta_sigmas": false,
16
+ "use_dynamic_shifting": false,
17
+ "use_exponential_sigmas": false,
18
+ "use_karras_sigmas": false
19
+ }
text_encoder/config.json ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen3_5Model"
4
+ ],
5
+ "dtype": "bfloat16",
6
+ "image_token_id": 248056,
7
+ "model_type": "qwen3_5",
8
+ "text_config": {
9
+ "attention_bias": false,
10
+ "attention_dropout": 0.0,
11
+ "attn_output_gate": true,
12
+ "bos_token_id": null,
13
+ "dtype": "bfloat16",
14
+ "eos_token_id": 248044,
15
+ "full_attention_interval": 4,
16
+ "head_dim": 32,
17
+ "hidden_act": "silu",
18
+ "hidden_size": 32,
19
+ "initializer_range": 0.02,
20
+ "intermediate_size": 64,
21
+ "layer_types": [
22
+ "linear_attention",
23
+ "linear_attention",
24
+ "linear_attention",
25
+ "full_attention",
26
+ "linear_attention",
27
+ "linear_attention",
28
+ "linear_attention",
29
+ "full_attention",
30
+ "linear_attention",
31
+ "linear_attention",
32
+ "linear_attention",
33
+ "full_attention",
34
+ "linear_attention",
35
+ "linear_attention",
36
+ "linear_attention",
37
+ "full_attention",
38
+ "linear_attention",
39
+ "linear_attention",
40
+ "linear_attention",
41
+ "full_attention",
42
+ "linear_attention",
43
+ "linear_attention",
44
+ "linear_attention",
45
+ "full_attention",
46
+ "linear_attention",
47
+ "linear_attention",
48
+ "linear_attention",
49
+ "full_attention",
50
+ "linear_attention",
51
+ "linear_attention",
52
+ "linear_attention",
53
+ "full_attention"
54
+ ],
55
+ "linear_conv_kernel_dim": 4,
56
+ "linear_key_head_dim": 32,
57
+ "linear_num_key_heads": 1,
58
+ "linear_num_value_heads": 1,
59
+ "linear_value_head_dim": 32,
60
+ "mamba_ssm_dtype": "float32",
61
+ "max_position_embeddings": 262144,
62
+ "mlp_only_layers": [],
63
+ "model_type": "qwen3_5_text",
64
+ "mtp_num_hidden_layers": 1,
65
+ "mtp_use_dedicated_embeddings": false,
66
+ "num_attention_heads": 1,
67
+ "num_hidden_layers": 32,
68
+ "num_key_value_heads": 1,
69
+ "pad_token_id": null,
70
+ "partial_rotary_factor": 0.25,
71
+ "rms_norm_eps": 1e-06,
72
+ "rope_parameters": {
73
+ "mrope_interleaved": true,
74
+ "mrope_section": [
75
+ 1,
76
+ 1,
77
+ 2
78
+ ],
79
+ "partial_rotary_factor": 0.25,
80
+ "rope_theta": 10000000,
81
+ "rope_type": "default"
82
+ },
83
+ "tie_word_embeddings": false,
84
+ "use_cache": true,
85
+ "vocab_size": 248320
86
+ },
87
+ "tie_word_embeddings": false,
88
+ "transformers_version": "5.15.0.dev0",
89
+ "video_token_id": 248057,
90
+ "vision_config": {
91
+ "deepstack_visual_indexes": [],
92
+ "depth": 27,
93
+ "dtype": "bfloat16",
94
+ "hidden_act": "gelu_pytorch_tanh",
95
+ "hidden_size": 32,
96
+ "in_channels": 3,
97
+ "initializer_range": 0.02,
98
+ "intermediate_size": 64,
99
+ "model_type": "qwen3_5_vision",
100
+ "num_heads": 1,
101
+ "num_position_embeddings": 2304,
102
+ "out_hidden_size": 32,
103
+ "patch_size": 16,
104
+ "spatial_merge_size": 2,
105
+ "temporal_patch_size": 2
106
+ },
107
+ "vision_end_token_id": 248054,
108
+ "vision_start_token_id": 248053
109
+ }
text_encoder/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e978b54dce1147620893ccfc0e6b4ea09e1612b318a7c7eb8c2a22fa6396495b
3
+ size 17474184
tokenizer/chat_template.jinja ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- set image_count = namespace(value=0) %}
2
+ {%- set video_count = namespace(value=0) %}
3
+ {%- macro render_content(content, do_vision_count, is_system_content=false) %}
4
+ {%- if content is string %}
5
+ {{- content }}
6
+ {%- elif content is iterable and content is not mapping %}
7
+ {%- for item in content %}
8
+ {%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
9
+ {%- if is_system_content %}
10
+ {{- raise_exception('System message cannot contain images.') }}
11
+ {%- endif %}
12
+ {%- if do_vision_count %}
13
+ {%- set image_count.value = image_count.value + 1 %}
14
+ {%- endif %}
15
+ {%- if add_vision_id %}
16
+ {{- 'Picture ' ~ image_count.value ~ ': ' }}
17
+ {%- endif %}
18
+ {{- '<|vision_start|><|image_pad|><|vision_end|>' }}
19
+ {%- elif 'video' in item or item.type == 'video' %}
20
+ {%- if is_system_content %}
21
+ {{- raise_exception('System message cannot contain videos.') }}
22
+ {%- endif %}
23
+ {%- if do_vision_count %}
24
+ {%- set video_count.value = video_count.value + 1 %}
25
+ {%- endif %}
26
+ {%- if add_vision_id %}
27
+ {{- 'Video ' ~ video_count.value ~ ': ' }}
28
+ {%- endif %}
29
+ {{- '<|vision_start|><|video_pad|><|vision_end|>' }}
30
+ {%- elif 'text' in item %}
31
+ {{- item.text }}
32
+ {%- else %}
33
+ {{- raise_exception('Unexpected item type in content.') }}
34
+ {%- endif %}
35
+ {%- endfor %}
36
+ {%- elif content is none or content is undefined %}
37
+ {{- '' }}
38
+ {%- else %}
39
+ {{- raise_exception('Unexpected content type.') }}
40
+ {%- endif %}
41
+ {%- endmacro %}
42
+ {%- if not messages %}
43
+ {{- raise_exception('No messages provided.') }}
44
+ {%- endif %}
45
+ {%- if tools and tools is iterable and tools is not mapping %}
46
+ {{- '<|im_start|>system\n' }}
47
+ {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
48
+ {%- for tool in tools %}
49
+ {{- "\n" }}
50
+ {{- tool | tojson }}
51
+ {%- endfor %}
52
+ {{- "\n</tools>" }}
53
+ {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
54
+ {%- if messages[0].role == 'system' %}
55
+ {%- set content = render_content(messages[0].content, false, true)|trim %}
56
+ {%- if content %}
57
+ {{- '\n\n' + content }}
58
+ {%- endif %}
59
+ {%- endif %}
60
+ {{- '<|im_end|>\n' }}
61
+ {%- else %}
62
+ {%- if messages[0].role == 'system' %}
63
+ {%- set content = render_content(messages[0].content, false, true)|trim %}
64
+ {{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
65
+ {%- endif %}
66
+ {%- endif %}
67
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
68
+ {%- for message in messages[::-1] %}
69
+ {%- set index = (messages|length - 1) - loop.index0 %}
70
+ {%- if ns.multi_step_tool and message.role == "user" %}
71
+ {%- set content = render_content(message.content, false)|trim %}
72
+ {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
73
+ {%- set ns.multi_step_tool = false %}
74
+ {%- set ns.last_query_index = index %}
75
+ {%- endif %}
76
+ {%- endif %}
77
+ {%- endfor %}
78
+ {%- if ns.multi_step_tool %}
79
+ {{- raise_exception('No user query found in messages.') }}
80
+ {%- endif %}
81
+ {%- for message in messages %}
82
+ {%- set content = render_content(message.content, true)|trim %}
83
+ {%- if message.role == "system" %}
84
+ {%- if not loop.first %}
85
+ {{- raise_exception('System message must be at the beginning.') }}
86
+ {%- endif %}
87
+ {%- elif message.role == "user" %}
88
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
89
+ {%- elif message.role == "assistant" %}
90
+ {%- set reasoning_content = '' %}
91
+ {%- if message.reasoning_content is string %}
92
+ {%- set reasoning_content = message.reasoning_content %}
93
+ {%- else %}
94
+ {%- if '</think>' in content %}
95
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
96
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
97
+ {%- endif %}
98
+ {%- endif %}
99
+ {%- set reasoning_content = reasoning_content|trim %}
100
+ {%- if loop.index0 > ns.last_query_index %}
101
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
102
+ {%- else %}
103
+ {{- '<|im_start|>' + message.role + '\n' + content }}
104
+ {%- endif %}
105
+ {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
106
+ {%- for tool_call in message.tool_calls %}
107
+ {%- if tool_call.function is defined %}
108
+ {%- set tool_call = tool_call.function %}
109
+ {%- endif %}
110
+ {%- if loop.first %}
111
+ {%- if content|trim %}
112
+ {{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
113
+ {%- else %}
114
+ {{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
115
+ {%- endif %}
116
+ {%- else %}
117
+ {{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
118
+ {%- endif %}
119
+ {%- if tool_call.arguments is defined %}
120
+ {%- for args_name, args_value in tool_call.arguments|items %}
121
+ {{- '<parameter=' + args_name + '>\n' }}
122
+ {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
123
+ {{- args_value }}
124
+ {{- '\n</parameter>\n' }}
125
+ {%- endfor %}
126
+ {%- endif %}
127
+ {{- '</function>\n</tool_call>' }}
128
+ {%- endfor %}
129
+ {%- endif %}
130
+ {{- '<|im_end|>\n' }}
131
+ {%- elif message.role == "tool" %}
132
+ {%- if loop.previtem and loop.previtem.role != "tool" %}
133
+ {{- '<|im_start|>user' }}
134
+ {%- endif %}
135
+ {{- '\n<tool_response>\n' }}
136
+ {{- content }}
137
+ {{- '\n</tool_response>' }}
138
+ {%- if not loop.last and loop.nextitem.role != "tool" %}
139
+ {{- '<|im_end|>\n' }}
140
+ {%- elif loop.last %}
141
+ {{- '<|im_end|>\n' }}
142
+ {%- endif %}
143
+ {%- else %}
144
+ {{- raise_exception('Unexpected message role.') }}
145
+ {%- endif %}
146
+ {%- endfor %}
147
+ {%- if add_generation_prompt %}
148
+ {{- '<|im_start|>assistant\n' }}
149
+ {%- if enable_thinking is defined and enable_thinking is false %}
150
+ {{- '<think>\n\n</think>\n\n' }}
151
+ {%- else %}
152
+ {{- '<think>\n' }}
153
+ {%- endif %}
154
+ {%- endif %}
tokenizer/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06b9509352d2af50381ab2247e083b80d32d5c0aba91c272ca9ff729b6a0e523
3
+ size 19989325
tokenizer/tokenizer_config.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "audio_bos_token": "<|audio_start|>",
4
+ "audio_eos_token": "<|audio_end|>",
5
+ "audio_token": "<|audio_pad|>",
6
+ "backend": "tokenizers",
7
+ "bos_token": null,
8
+ "clean_up_tokenization_spaces": false,
9
+ "eos_token": "<|im_end|>",
10
+ "errors": "replace",
11
+ "image_token": "<|image_pad|>",
12
+ "is_local": true,
13
+ "local_files_only": true,
14
+ "model_max_length": 262144,
15
+ "model_specific_special_tokens": {
16
+ "audio_bos_token": "<|audio_start|>",
17
+ "audio_eos_token": "<|audio_end|>",
18
+ "audio_token": "<|audio_pad|>",
19
+ "image_token": "<|image_pad|>",
20
+ "video_token": "<|video_pad|>",
21
+ "vision_bos_token": "<|vision_start|>",
22
+ "vision_eos_token": "<|vision_end|>"
23
+ },
24
+ "pad_token": "<|endoftext|>",
25
+ "pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
26
+ "split_special_tokens": false,
27
+ "tokenizer_class": "Qwen2Tokenizer",
28
+ "unk_token": null,
29
+ "video_token": "<|video_pad|>",
30
+ "vision_bos_token": "<|vision_start|>",
31
+ "vision_eos_token": "<|vision_end|>"
32
+ }
transformer/config.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "MOSSImageTransformer",
3
+ "_diffusers_version": "0.40.0.dev0",
4
+ "dim": 64,
5
+ "ffn_dim": 128,
6
+ "freq_dim": 32,
7
+ "latent_channels": 128,
8
+ "num_heads": 2,
9
+ "num_layers": 42,
10
+ "rope_base": 10000.0,
11
+ "text_dim": 32
12
+ }
transformer/diffusion_pytorch_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:86214daf0570af1eeee4e1fdcba9cfe63181e538f685e652dd02663e74e5c0c7
3
+ size 3782792
transformer/pipeline_moss_image.py ADDED
@@ -0,0 +1,440 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MOSSImage modeling and text-to-image pipeline (no Motor/TorchTitan imports).
2
+
3
+ Dependencies: torch>=2.9, diffusers>=0.38, transformers>=5.3, safetensors,
4
+ accelerate, Pillow. Copy this file anywhere and import it normally.
5
+
6
+ import torch
7
+ # Import MOSSImagePipeline from this file for component-level loading.
8
+ pipe = MOSSImagePipeline.from_components_pretrained(
9
+ transformer_path="exported_transformer", # save_pretrained directory
10
+ text_encoder_path="Qwen/Qwen3.5-9B",
11
+ vae_path="black-forest-labs/FLUX.2-klein-4B",
12
+ ).to("cuda")
13
+ pipe("A cat", generator=torch.Generator("cuda").manual_seed(42)).images[0].save("cat.png")
14
+
15
+ Default guidance uses CFG 3 and skips block 22 only in the negative branch
16
+ (zero-based main DiT block indices; two forwards per sampling step):
17
+ pipe("A cat", guidance_scale=3.0, uncond_skip_layers=[22])
18
+ pipe("A cat", uncond_skip_layers=[]) # disable negative-branch skipping
19
+ Optional additional positive-branch SLG adds a third forward:
20
+ pipe("A cat", slg_layers=[24], slg_scale=3.0)
21
+ The additional SLG layer/scale above illustrates the API, not a quality-tuned setting.
22
+
23
+ For a consolidated training state dict, create MOSSImageTransformer and
24
+ call load_training_state_dict(state), then save_pretrained("exported_transformer").
25
+ Select raw or EMA weights explicitly before calling it; distributed checkpoint
26
+ shards/optimizer states are not model state dicts. Defaults match the finalized dense MOSSImage model,
27
+ not MoE, CFG-distilled, video, or reference-image variants. Prompt strings are
28
+ already-rendered captions (dataset-specific quality/domain tags are not added).
29
+
30
+ First-release defaults: bf16 DiT/text-encoder weights, fp32 RoPE frequencies
31
+ and phase/sin/cos calculation, then RoPE cast to the video activation dtype.
32
+ Initial Gaussian noise is sampled directly in fp32, including with bf16 weights.
33
+ CFG and Euler latent updates use fp32; VAE decoding defaults to fp32. Scheduler
34
+ shift defaults to 5.0. Exact numerical parity with the original fp32-master
35
+ inference pipeline is not a release requirement.
36
+ """
37
+ from __future__ import annotations
38
+
39
+ import math
40
+ from pathlib import Path
41
+
42
+ import torch
43
+ from torch import nn
44
+ from torch.nn import functional as F
45
+ from diffusers import AutoencoderKLFlux2, DiffusionPipeline, FlowMatchEulerDiscreteScheduler
46
+ from diffusers.schedulers.scheduling_utils import SchedulerMixin # Required by custom-component discovery.
47
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
48
+ from diffusers.image_processor import VaeImageProcessor
49
+ from diffusers.models.modeling_utils import ModelMixin
50
+ from diffusers.models.modeling_outputs import Transformer2DModelOutput
51
+ from diffusers.pipelines.pipeline_utils import ImagePipelineOutput
52
+ from diffusers.utils.torch_utils import randn_tensor
53
+ from transformers import AutoModel, AutoTokenizer
54
+
55
+
56
+ class _RMSNorm(nn.Module):
57
+ def __init__(self, dim):
58
+ super().__init__()
59
+ self.weight = nn.Parameter(torch.ones(dim))
60
+
61
+ def forward(self, x):
62
+ y = x.float() * torch.rsqrt(x.float().square().mean(-1, keepdim=True) + 1e-6)
63
+ return y.to(x.dtype) * self.weight
64
+
65
+
66
+ def _situ(hidden, gate):
67
+ return (4.0 * torch.tanh(gate / 4.0) * torch.sigmoid(gate)) * (25.0 * torch.tanh(hidden / 25.0))
68
+
69
+
70
+ def _validate_skip_layers(layers, depth, name='skip_layers'):
71
+ layers = () if layers is None else tuple(layers)
72
+ if any(type(index) is not int or not 0 <= index < depth for index in layers):
73
+ raise ValueError(f'{name} must contain integer block indices in [0, {depth}).')
74
+ return frozenset(layers)
75
+
76
+
77
+ def _rotate(x, rope):
78
+ sin, cos = rope.chunk(2, -1)
79
+ sin = torch.cat((sin, sin), -1).unsqueeze(-2)
80
+ cos = torch.cat((cos, cos), -1).unsqueeze(-2)
81
+ a, b = x.chunk(2, -1)
82
+ return x * cos + torch.cat((-b, a), -1) * sin
83
+
84
+
85
+ def _attention(q, k, v, rope, heads):
86
+ shape = (*q.shape[:2], heads, q.shape[-1] // heads)
87
+ q = _rotate(q.reshape(shape), rope).transpose(1, 2)
88
+ k = _rotate(k.reshape(shape), rope).transpose(1, 2)
89
+ v = v.reshape(shape).transpose(1, 2)
90
+ return F.scaled_dot_product_attention(q.to(v.dtype), k.to(v.dtype), v).transpose(1, 2).flatten(2)
91
+
92
+
93
+ class _TextBlock(nn.Module):
94
+ def __init__(self, dim, ffn_dim, heads):
95
+ super().__init__()
96
+ self.heads = heads
97
+ self.norm1, self.norm2 = _RMSNorm(dim), _RMSNorm(dim)
98
+ self.q_norm, self.k_norm = _RMSNorm(dim // heads), _RMSNorm(dim // heads)
99
+ self.qkv = nn.Linear(dim, 3 * dim)
100
+ self.proj = nn.Linear(dim, dim)
101
+ self.ffn_gate, self.ffn_up = nn.Linear(dim, ffn_dim), nn.Linear(dim, ffn_dim)
102
+ self.ffn_down = nn.Linear(ffn_dim, dim)
103
+
104
+ def forward(self, x, rope):
105
+ q, k, v = self.qkv(self.norm1(x)).chunk(3, -1)
106
+ q = self.q_norm(q.unflatten(-1, (self.heads, -1))).flatten(2)
107
+ k = self.k_norm(k.unflatten(-1, (self.heads, -1))).flatten(2)
108
+ x = x + self.proj(_attention(q, k, v, rope, self.heads))
109
+ y = self.norm2(x)
110
+ return x + self.ffn_down(_situ(self.ffn_up(y), self.ffn_gate(y)))
111
+
112
+
113
+ class _TextProjection(nn.Module):
114
+ def __init__(self, text_dim, dim, ffn_dim, heads):
115
+ super().__init__()
116
+ self.proj = nn.Linear(text_dim, dim)
117
+ self.blocks = nn.ModuleList([_TextBlock(dim, ffn_dim, heads) for _ in range(2)])
118
+
119
+ def forward(self, x, rope):
120
+ x = self.proj(x.to(self.proj.weight.dtype))
121
+ for block in self.blocks:
122
+ x = block(x, rope)
123
+ return x
124
+
125
+
126
+ class _Block(nn.Module):
127
+ def __init__(self, dim, ffn_dim, heads):
128
+ super().__init__()
129
+ self.heads = heads
130
+ self.norm1, self.norm2 = _RMSNorm(dim), _RMSNorm(dim)
131
+ self.q_norm, self.k_norm = _RMSNorm(dim // heads), _RMSNorm(dim // heads)
132
+ self.proj = nn.ModuleDict({
133
+ name: nn.Linear(dim, dim, bias=False) for name in ('q', 'k', 'v', 'o')
134
+ })
135
+ self.proj['ffn_up_gate'] = nn.Linear(dim, 2 * ffn_dim, bias=False)
136
+ self.proj['ffn_down'] = nn.Linear(ffn_dim, dim, bias=False)
137
+
138
+ def forward(self, x, rope, modulation):
139
+ shift_a, scale_a, gate_a, shift_f, scale_f, gate_f = modulation.unbind(1)
140
+ y = self.norm1(x) * (1 + scale_a[:, None]) + shift_a[:, None]
141
+ dtype = self.proj['q'].weight.dtype
142
+ y = y.to(dtype)
143
+ q = self.q_norm(self.proj['q'](y).unflatten(-1, (self.heads, -1))).flatten(2)
144
+ k = self.k_norm(self.proj['k'](y).unflatten(-1, (self.heads, -1))).flatten(2)
145
+ x = x + self.proj['o'](_attention(q, k, self.proj['v'](y), rope, self.heads)) * gate_a[:, None]
146
+ y = self.norm2(x) * (1 + scale_f[:, None]) + shift_f[:, None]
147
+ hidden, gate = self.proj['ffn_up_gate'](y.to(dtype)).chunk(2, -1)
148
+ return x + self.proj['ffn_down'](_situ(hidden, gate)) * gate_f[:, None]
149
+
150
+
151
+ class _RoPE(nn.Module):
152
+ def __init__(self, head_dim, base):
153
+ super().__init__()
154
+ self.num_bands = head_dim // 8
155
+ self.base = base
156
+ self.register_buffer('bands', self._default_bands(torch.device('cpu')))
157
+
158
+ def _default_bands(self, device):
159
+ # Match training's CPU fp32 construction, including under meta init.
160
+ exponent = torch.arange(self.num_bands, device='cpu', dtype=torch.float32) / self.num_bands
161
+ return (1 / (self.base ** exponent)).to(device)
162
+
163
+ def _apply(self, fn, recurse=True):
164
+ super()._apply(fn, recurse=recurse)
165
+ # .float() cannot recover frequencies already rounded by .to(bf16).
166
+ self.bands = self._default_bands(self.bands.device)
167
+ return self
168
+
169
+ def _load_from_state_dict(self, *args, **kwargs):
170
+ super()._load_from_state_dict(*args, **kwargs)
171
+ # Frequencies are derived constants; retain the key for checkpoint compatibility.
172
+ self.bands = self._default_bands(self.bands.device)
173
+
174
+ def forward(self, height, width, text_length, device):
175
+ # Diffusers/Accelerate can install tensors directly, bypassing load hooks.
176
+ if self.bands.dtype != torch.float32:
177
+ self.bands = self._default_bands(device)
178
+ # Coordinates: (r, f, h, w); sequence order is image followed by text.
179
+ h, w = torch.meshgrid(torch.arange(height, device=device), torch.arange(width, device=device), indexing='ij')
180
+ image = torch.stack((torch.zeros_like(h), torch.full_like(h, text_length), h, w), -1).flatten(0, 1).float()
181
+ image[:, 2] -= (height - 1) / 2
182
+ image[:, 3] -= (width - 1) / 2
183
+ text = torch.zeros(text_length, 4, device=device, dtype=torch.float32)
184
+ text[:, 1] = torch.arange(text_length, device=device)
185
+ phase = torch.cat((image, text), 0).unsqueeze(-1) * self.bands
186
+ return torch.cat((phase.sin(), phase.cos()), -2).flatten(-2).unsqueeze(0)
187
+
188
+
189
+ class MOSSImageTransformer(ModelMixin, ConfigMixin):
190
+ """MOSSImage DiT with training-compatible state-dict names and SDPA attention.
191
+
192
+ Each sample is evaluated at its true text length, including text refinement;
193
+ padded tokens cannot influence either text or joint attention.
194
+ """
195
+ @register_to_config
196
+ def __init__(self, dim=3072, ffn_dim=8192, num_heads=24, num_layers=42,
197
+ text_dim=4096, freq_dim=256, latent_channels=128, rope_base=10000.0):
198
+ super().__init__()
199
+ if dim % num_heads or (dim // num_heads) % 8 or freq_dim % 2:
200
+ raise ValueError('Require dim divisible by heads, head_dim divisible by 8, and even freq_dim.')
201
+ self.video_embedder = nn.Linear(latent_channels, dim)
202
+ self.text_embedder = _TextProjection(text_dim, dim, ffn_dim, num_heads)
203
+ self.rope = _RoPE(dim // num_heads, rope_base)
204
+ self.time_embedding = nn.Sequential(nn.Linear(freq_dim, dim), nn.SiLU(), nn.Linear(dim, dim))
205
+ self.shared_block_modulation = nn.Linear(dim, 6 * dim)
206
+ self.layers = nn.ModuleList([_Block(dim, ffn_dim, num_heads) for _ in range(num_layers)])
207
+ self.final_norm_video = _RMSNorm(dim)
208
+ self.final_linear_video = nn.Linear(dim, latent_channels, bias=False)
209
+ self.video_head_modulation = nn.Linear(dim, 2 * dim)
210
+
211
+ def load_training_state_dict(self, state_dict, *, prefix=''):
212
+ """Load a consolidated raw/EMA model mapping, strictly; no silent key drops."""
213
+ if prefix:
214
+ if any(not key.startswith(prefix) for key in state_dict):
215
+ raise ValueError('All keys must start with the explicit prefix.')
216
+ state_dict = {key[len(prefix):]: value for key, value in state_dict.items()}
217
+ return self.load_state_dict(state_dict, strict=True)
218
+
219
+ def forward(self, hidden_states, timestep, encoder_hidden_states,
220
+ encoder_attention_mask=None, return_dict=True, skip_layers=None):
221
+ skip_layers = _validate_skip_layers(skip_layers, len(self.layers))
222
+ if hidden_states.ndim != 4 or hidden_states.shape[1] != self.config.latent_channels:
223
+ raise ValueError('hidden_states must be [B, latent_channels, H, W].')
224
+ batch, _, height, width = hidden_states.shape
225
+ if encoder_hidden_states.ndim != 3 or encoder_hidden_states.shape[0] != batch:
226
+ raise ValueError('encoder_hidden_states must be [B, S, text_dim].')
227
+ mask = encoder_attention_mask
228
+ if mask is None:
229
+ mask = torch.ones(encoder_hidden_states.shape[:2], dtype=torch.bool, device=hidden_states.device)
230
+ if mask.shape != encoder_hidden_states.shape[:2] or not torch.all((mask == 0) | (mask == 1)):
231
+ raise ValueError('encoder_attention_mask must be a binary [B, S] mask.')
232
+ times = torch.as_tensor(timestep, device=hidden_states.device, dtype=torch.float32).reshape(-1)
233
+ times = times.expand(batch)
234
+ # Training computes the sinusoid in float64 and feeds float32 into its MLP.
235
+ half = self.config.freq_dim // 2
236
+ freq = 10000 ** (-torch.arange(half, device=times.device, dtype=torch.float64) / half)
237
+ phase = times.double()[:, None] * freq
238
+ embedding = torch.cat((phase.cos(), phase.sin()), -1).float()
239
+ time = self.time_embedding(embedding.to(self.time_embedding[0].weight.dtype)).float()
240
+ mod = self.shared_block_modulation(time.to(self.shared_block_modulation.weight.dtype)).view(batch, 6, -1)
241
+ head = self.video_head_modulation(time.to(self.video_head_modulation.weight.dtype)).view(batch, 2, -1)
242
+ outputs = []
243
+ for index in range(batch):
244
+ context = encoder_hidden_states[index:index + 1, mask[index].bool()]
245
+ if context.shape[1] == 0:
246
+ raise ValueError('Each sample needs at least one valid text token.')
247
+ video = hidden_states[index:index + 1].flatten(2).transpose(1, 2)
248
+ video = self.video_embedder(video.to(self.video_embedder.weight.dtype).contiguous())
249
+ # Match the reference activation dtype under fp32 weights + autocast.
250
+ rope = self.rope(height, width, context.shape[1], hidden_states.device).to(video.dtype)
251
+ text = self.text_embedder(context, rope[:, height * width:])
252
+ x = torch.cat((video, text), 1)
253
+ for layer_index, layer in enumerate(self.layers):
254
+ if layer_index not in skip_layers:
255
+ x = layer(x, rope, mod[index:index + 1])
256
+ x = self.final_norm_video(x[:, :height * width].to(self.final_norm_video.weight.dtype))
257
+ x = x * (1 + head[index:index + 1, 1, None]) + head[index:index + 1, 0, None]
258
+ x = self.final_linear_video(x.to(self.final_linear_video.weight.dtype))
259
+ outputs.append(x.transpose(1, 2).reshape(1, -1, height, width))
260
+ sample = torch.cat(outputs)
261
+ return Transformer2DModelOutput(sample=sample) if return_dict else (sample,)
262
+
263
+
264
+ class MOSSImageScheduler(FlowMatchEulerDiscreteScheduler):
265
+ """Euler integration using the training project's V2, nearest-grid schedule."""
266
+ @register_to_config
267
+ def __init__(self, num_train_timesteps=1000, shift=5.0, training_shift=1.0):
268
+ super().__init__(num_train_timesteps=num_train_timesteps, shift=1.0)
269
+ self.register_to_config(shift=shift, training_shift=training_shift)
270
+
271
+ def set_timesteps(self, num_inference_steps=50, device=None):
272
+ if num_inference_steps < 1 or self.config.shift <= 0 or self.config.training_shift <= 0:
273
+ raise ValueError('Steps and shifts must be positive.')
274
+ total = self.config.num_train_timesteps
275
+ def grid(count, shift):
276
+ lower = shift / (total + shift - 1)
277
+ values = torch.linspace(1, lower, count)
278
+ return shift * values / (1 + (shift - 1) * values)
279
+ sigmas = grid(num_inference_steps, self.config.shift)
280
+ training = grid(total, self.config.training_shift).flip(0)
281
+ upper = torch.searchsorted(training, sigmas).clamp(max=total - 1)
282
+ lower = (upper - 1).clamp(min=0)
283
+ nearest = torch.where((sigmas - training[lower]).abs() <= (training[upper] - sigmas).abs(), lower, upper)
284
+ sigmas = torch.unique_consecutive(training[nearest])
285
+ # Our sigmas have already been shifted and snapped. Avoid a second shift.
286
+ self._shift = 1.0
287
+ super().set_timesteps(device=device, sigmas=sigmas.tolist())
288
+
289
+
290
+ class MOSSImagePipeline(DiffusionPipeline):
291
+ model_cpu_offload_seq = 'text_encoder->transformer->vae'
292
+ _optional_components = ['text_encoder', 'tokenizer', 'vae']
293
+
294
+ def __init__(self, transformer: MOSSImageTransformer,
295
+ scheduler: MOSSImageScheduler, text_encoder=None, tokenizer=None, vae=None):
296
+ super().__init__()
297
+ self.register_modules(transformer=transformer, scheduler=scheduler,
298
+ text_encoder=text_encoder, tokenizer=tokenizer, vae=vae)
299
+ self.image_processor = VaeImageProcessor(vae_scale_factor=16)
300
+
301
+ @classmethod
302
+ def from_components_pretrained(cls, transformer_path, text_encoder_path, vae_path,
303
+ torch_dtype=torch.bfloat16, vae_dtype=torch.float32,
304
+ local_files_only=False):
305
+ common = dict(local_files_only=local_files_only)
306
+ transformer = MOSSImageTransformer.from_pretrained(transformer_path, torch_dtype=torch_dtype, **common)
307
+ tokenizer = AutoTokenizer.from_pretrained(text_encoder_path, **common)
308
+ text_encoder = AutoModel.from_pretrained(text_encoder_path, dtype=torch_dtype, **common).eval()
309
+ subfolder = None if (Path(vae_path) / 'config.json').is_file() else 'vae'
310
+ vae = AutoencoderKLFlux2.from_pretrained(vae_path, subfolder=subfolder, torch_dtype=vae_dtype, **common).eval()
311
+ return cls(transformer.eval(), MOSSImageScheduler(), text_encoder, tokenizer, vae)
312
+
313
+ @torch.no_grad()
314
+ def encode_prompt(self, prompt, max_sequence_length=4096):
315
+ if self.text_encoder is None or self.tokenizer is None:
316
+ raise ValueError('Text encoder/tokenizer required for string prompts.')
317
+ prompts = [prompt] if isinstance(prompt, str) else list(prompt)
318
+ if not prompts or not all(isinstance(p, str) for p in prompts):
319
+ raise ValueError('prompt must be a string or a nonempty list of strings.')
320
+ rows = []
321
+ # Unpadded individual forwards also avoid padding effects in Qwen GDN.
322
+ for value in prompts:
323
+ formatted = self.tokenizer.apply_chat_template(
324
+ [{'role': 'user', 'content': value}], tokenize=False,
325
+ add_generation_prompt=True, enable_thinking=True)
326
+ inputs = self.tokenizer(formatted, return_tensors='pt', truncation=True,
327
+ max_length=max_sequence_length).to(self._execution_device)
328
+ output = self.text_encoder(**inputs, use_cache=False, return_dict=True)
329
+ rows.append(output.last_hidden_state[0])
330
+ length = max(row.shape[0] for row in rows)
331
+ embeds = rows[0].new_zeros(len(rows), length, rows[0].shape[-1])
332
+ mask = torch.zeros(len(rows), length, device=embeds.device, dtype=torch.bool)
333
+ for index, row in enumerate(rows):
334
+ embeds[index, :row.shape[0]] = row
335
+ mask[index, :row.shape[0]] = True
336
+ return embeds, mask
337
+
338
+ @torch.no_grad()
339
+ def __call__(self, prompt=None, *, height=1024, width=1024, num_inference_steps=50,
340
+ guidance_scale=3.0, negative_prompt='', num_images_per_prompt=1,
341
+ generator=None, latents=None, prompt_embeds=None, prompt_attention_mask=None,
342
+ negative_prompt_embeds=None, negative_prompt_attention_mask=None,
343
+ max_sequence_length=4096, output_type='pil', return_dict=True,
344
+ uncond_skip_layers=(22,), slg_layers=None, slg_scale=0.0,
345
+ slg_start=0.0, slg_stop=1.0):
346
+ """Generate images with optional full-block skipping (zero-based indices).
347
+
348
+ uncond_skip_layers defaults to (22,) and replaces CFG's negative-prompt
349
+ prediction with a skipped-block prediction, retaining two forwards per
350
+ step. Pass None or an empty sequence to disable skipping. slg_layers
351
+ adds a skipped positive-prompt forward and the fp32 correction
352
+ slg_scale * (cond - cond_skip) to CFG. With guidance_scale=1, active
353
+ SLG uses cond_skip + slg_scale * (cond - cond_skip), matching Diffusers'
354
+ default SkipLayerGuidance formulation, with two forwards and no negative
355
+ branch. Both options can combine. slg_scale=1 then gives normal cond.
356
+ Both uncond skipping and SLG are active where
357
+ slg_start <= step_index / num_steps < slg_stop;
358
+ the interval follows denoising order, from initial noise to final image.
359
+ Outside it, inference uses normal CFG without skipping.
360
+ slg_scale=0 or empty slg_layers disables the additional forward.
361
+ """
362
+ uncond_skip_layers = _validate_skip_layers(
363
+ uncond_skip_layers, len(self.transformer.layers), 'uncond_skip_layers')
364
+ slg_layers = _validate_skip_layers(slg_layers, len(self.transformer.layers), 'slg_layers')
365
+ if not math.isfinite(slg_scale) or slg_scale < 0:
366
+ raise ValueError('slg_scale must be finite and nonnegative.')
367
+ if not 0 <= slg_start <= slg_stop <= 1:
368
+ raise ValueError('Require 0 <= slg_start <= slg_stop <= 1.')
369
+ if height <= 0 or width <= 0 or height % 16 or width % 16:
370
+ raise ValueError('height and width must be positive multiples of 16.')
371
+ if output_type not in {'pil', 'np', 'pt', 'latent'}:
372
+ raise ValueError('output_type must be pil, np, pt or latent.')
373
+ if num_images_per_prompt < 1 or not math.isfinite(guidance_scale) or guidance_scale < 0:
374
+ raise ValueError('Invalid image count or guidance scale.')
375
+ if not 1 <= max_sequence_length <= 4096:
376
+ raise ValueError('max_sequence_length must be between 1 and 4096.')
377
+ if (prompt is None) == (prompt_embeds is None):
378
+ raise ValueError('Provide exactly one of prompt and prompt_embeds.')
379
+ device = self._execution_device
380
+ if prompt_embeds is None:
381
+ prompt_embeds, prompt_attention_mask = self.encode_prompt(prompt, max_sequence_length)
382
+ batch = prompt_embeds.shape[0]
383
+ def prepare(embeds, mask):
384
+ if embeds.ndim != 3 or embeds.shape[0] != batch:
385
+ raise ValueError('Prompt embeddings must be [B, S, C] with matching batch size.')
386
+ if mask is None:
387
+ mask = torch.ones(embeds.shape[:2], dtype=torch.bool, device=embeds.device)
388
+ return (embeds.to(device).repeat_interleave(num_images_per_prompt, 0),
389
+ mask.to(device).repeat_interleave(num_images_per_prompt, 0))
390
+ positive, positive_mask = prepare(prompt_embeds, prompt_attention_mask)
391
+ do_cfg = guidance_scale != 1.0
392
+ if do_cfg:
393
+ if negative_prompt_embeds is None:
394
+ negatives = [negative_prompt] * batch if isinstance(negative_prompt, str) else negative_prompt
395
+ if len(negatives) != batch:
396
+ raise ValueError('negative_prompt must match prompt batch size.')
397
+ negative_prompt_embeds, negative_prompt_attention_mask = self.encode_prompt(negatives, max_sequence_length)
398
+ negative, negative_mask = prepare(negative_prompt_embeds, negative_prompt_attention_mask)
399
+ shape = (batch * num_images_per_prompt, self.transformer.config.latent_channels, height // 16, width // 16)
400
+ if latents is None:
401
+ # Sample in fp32: promoting bf16 noise afterward cannot restore its precision.
402
+ latents = randn_tensor(shape, generator=generator, device=device, dtype=torch.float32)
403
+ elif tuple(latents.shape) != shape:
404
+ raise ValueError(f'latents must have shape {shape}.')
405
+ else:
406
+ latents = latents.to(device=device, dtype=torch.float32)
407
+ self.scheduler.set_timesteps(num_inference_steps, device=device)
408
+ num_steps = len(self.scheduler.timesteps)
409
+ for step_index, timestep in enumerate(self.progress_bar(self.scheduler.timesteps)):
410
+ skip_active = slg_start <= step_index / num_steps < slg_stop
411
+ # CFG must combine fp32 predictions, even when the DiT runs in bf16.
412
+ # Converting only the combined velocity would preserve rounding errors.
413
+ prediction = self.transformer(latents, timestep, positive, positive_mask).sample.float()
414
+ conditional = prediction
415
+ if do_cfg:
416
+ skip_kwargs = {'skip_layers': uncond_skip_layers} if uncond_skip_layers and skip_active else {}
417
+ uncond = self.transformer(latents, timestep, negative, negative_mask, **skip_kwargs).sample.float()
418
+ prediction = uncond + guidance_scale * (prediction - uncond)
419
+ if slg_layers and slg_scale > 0 and skip_active:
420
+ skipped = self.transformer(
421
+ latents, timestep, positive, positive_mask, skip_layers=slg_layers).sample.float()
422
+ if not do_cfg:
423
+ prediction = skipped
424
+ prediction = prediction + slg_scale * (conditional - skipped)
425
+ latents = self.scheduler.step(prediction, timestep, latents, return_dict=False)[0]
426
+ if output_type == 'latent':
427
+ images = latents
428
+ else:
429
+ if self.vae is None:
430
+ raise ValueError('A FLUX.2 VAE is required for decoded output.')
431
+ z = latents.to(dtype=self.vae.dtype)
432
+ bn = self.vae.bn
433
+ eps = self.vae.config.batch_norm_eps
434
+ z = z * (bn.running_var + eps).sqrt().to(z).view(1, -1, 1, 1) + bn.running_mean.to(z).view(1, -1, 1, 1)
435
+ b, c, h, w = z.shape
436
+ 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)
437
+ images = self.vae.decode(z, return_dict=False)[0].clamp(-1, 1)
438
+ images = self.image_processor.postprocess(images, output_type=output_type)
439
+ self.maybe_free_model_hooks()
440
+ return ImagePipelineOutput(images=images) if return_dict else (images,)
vae/config.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "AutoencoderKLFlux2",
3
+ "_diffusers_version": "0.40.0.dev0",
4
+ "act_fn": "silu",
5
+ "batch_norm_eps": 0.0001,
6
+ "batch_norm_momentum": 0.1,
7
+ "block_out_channels": [
8
+ 32,
9
+ 32,
10
+ 64,
11
+ 64
12
+ ],
13
+ "decoder_block_out_channels": [
14
+ 32,
15
+ 32,
16
+ 64,
17
+ 64
18
+ ],
19
+ "down_block_types": [
20
+ "DownEncoderBlock2D",
21
+ "DownEncoderBlock2D",
22
+ "DownEncoderBlock2D",
23
+ "DownEncoderBlock2D"
24
+ ],
25
+ "force_upcast": true,
26
+ "in_channels": 3,
27
+ "latent_channels": 32,
28
+ "layers_per_block": 2,
29
+ "mid_block_add_attention": true,
30
+ "norm_num_groups": 32,
31
+ "out_channels": 3,
32
+ "patch_size": [
33
+ 2,
34
+ 2
35
+ ],
36
+ "sample_size": 32,
37
+ "up_block_types": [
38
+ "UpDecoderBlock2D",
39
+ "UpDecoderBlock2D",
40
+ "UpDecoderBlock2D",
41
+ "UpDecoderBlock2D"
42
+ ],
43
+ "use_post_quant_conv": true,
44
+ "use_quant_conv": true
45
+ }
vae/diffusion_pytorch_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d4f21cadbc038798d0c1129ebc430f61b40bfa354f29c7d9785cefdd4b9729fa
3
+ size 5841172