BiliSakura commited on
Commit
63ab1c6
·
verified ·
1 Parent(s): 9392475

Fix generator determinism: forward generator through scheduler steps and seeded noise

Browse files
MVSplit-DiT-1000L/demo.png CHANGED

Git LFS Details

  • SHA256: 6e5f8bae051bb3441bfe109f6fc509dd1ed12afbd58e74a0f257729d8a44ce9f
  • Pointer size: 131 Bytes
  • Size of remote file: 130 kB

Git LFS Details

  • SHA256: 30dff3efd724cd4f44b047b319841d2706358c76dba2c06d8dd0def48cf07bb8
  • Pointer size: 131 Bytes
  • Size of remote file: 130 kB
MVSplit-DiT-1000L/demo_inference.py CHANGED
@@ -5,7 +5,9 @@ from __future__ import annotations
5
 
6
  import argparse
7
  import importlib.util
 
8
  import sys
 
9
  from pathlib import Path
10
 
11
  import torch
@@ -56,6 +58,12 @@ def parse_args() -> argparse.Namespace:
56
  default="auto",
57
  help="Execution device. auto prefers CUDA when available.",
58
  )
 
 
 
 
 
 
59
  return parser.parse_args()
60
 
61
 
@@ -65,6 +73,119 @@ def _resolve_device(choice: str) -> torch.device:
65
  return torch.device(choice)
66
 
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  def _load_pipeline_class(model_dir: Path):
69
  transformer_path = model_dir / "transformer" / "transformer_mvsplit_dit.py"
70
  spec = importlib.util.spec_from_file_location("transformer_mvsplit_dit", transformer_path)
@@ -82,15 +203,21 @@ def _load_pipeline_class(model_dir: Path):
82
  def main() -> None:
83
  args = parse_args()
84
  model_dir = args.model.resolve()
85
- device = _resolve_device(args.device)
 
 
 
 
86
  transformer_cls, pipeline_cls = _load_pipeline_class(model_dir)
87
 
88
- print(f"Loading components on {device}...", flush=True)
89
- transformer = transformer_cls.from_pretrained(
90
- model_dir / "transformer",
91
- torch_dtype=torch.bfloat16,
92
- local_files_only=True,
93
- )
 
 
94
  tokenizer = AutoTokenizer.from_pretrained(model_dir / "tokenizer", local_files_only=True)
95
  text_encoder = AutoModel.from_pretrained(
96
  model_dir / "text_encoder",
@@ -114,16 +241,14 @@ def main() -> None:
114
  tokenizer=tokenizer,
115
  time_shift_alpha=args.time_shift_alpha,
116
  )
117
- if device.type == "cuda":
118
- pipe.enable_sequential_cpu_offload(gpu_id=0)
119
- else:
120
- pipe.to(device)
121
 
122
  print(
123
  f"Running inference ({args.num_inference_steps} steps, {args.height}x{args.width})...",
124
  flush=True,
125
  )
126
- generator = torch.Generator(device="cpu").manual_seed(args.seed)
127
  result = pipe(
128
  prompt=args.prompt,
129
  height=args.height,
@@ -133,6 +258,7 @@ def main() -> None:
133
  generator=generator,
134
  output_type=args.output_type,
135
  )
 
136
 
137
  if args.output_type == "latent":
138
  latents = result.images
 
5
 
6
  import argparse
7
  import importlib.util
8
+ import json
9
  import sys
10
+ from collections import Counter
11
  from pathlib import Path
12
 
13
  import torch
 
58
  default="auto",
59
  help="Execution device. auto prefers CUDA when available.",
60
  )
61
+ parser.add_argument(
62
+ "--num-gpus",
63
+ type=int,
64
+ default=2,
65
+ help="Number of GPUs for model-parallel inference (default: 2).",
66
+ )
67
  return parser.parse_args()
68
 
69
 
 
73
  return torch.device(choice)
74
 
75
 
76
+ def _read_transformer_depth(model_dir: Path) -> int:
77
+ config_path = model_dir / "transformer" / "config.json"
78
+ with open(config_path, encoding="utf-8") as handle:
79
+ config = json.load(handle)
80
+ return int(config["depth"])
81
+
82
+
83
+ def _build_transformer_device_map(depth: int, num_gpus: int) -> dict[str, int]:
84
+ if num_gpus < 1:
85
+ raise ValueError("--num-gpus must be >= 1.")
86
+
87
+ if num_gpus == 1:
88
+ return {"": 0}
89
+
90
+ blocks_per_gpu = depth // num_gpus
91
+ device_map: dict[str, int] = {
92
+ "patch_embed": 0,
93
+ "norm_img_input": 0,
94
+ "norm_text_input": 0,
95
+ "context_proj": 0,
96
+ "rope": 0,
97
+ }
98
+ for block_idx in range(depth):
99
+ gpu_id = min(block_idx // blocks_per_gpu, num_gpus - 1)
100
+ device_map[f"blocks.{block_idx}"] = gpu_id
101
+ device_map["final_proj"] = num_gpus - 1
102
+ return device_map
103
+
104
+
105
+ def _build_gpu_max_memory(num_gpus: int) -> dict[int | str, str]:
106
+ """Reserve headroom for text encoder / VAE and forbid CPU offload."""
107
+ if not torch.cuda.is_available():
108
+ raise RuntimeError("CUDA is required for GPU inference.")
109
+ available = torch.cuda.device_count()
110
+ if available < num_gpus:
111
+ raise RuntimeError(f"Requested {num_gpus} GPUs but only {available} CUDA device(s) are available.")
112
+
113
+ reserves_gib = [2.5] + [1.0] * (num_gpus - 1)
114
+ max_memory: dict[int | str, str] = {}
115
+ for gpu_id in range(num_gpus):
116
+ total_gib = torch.cuda.get_device_properties(gpu_id).total_memory / (1024**3)
117
+ usable_gib = max(1, int(total_gib - reserves_gib[gpu_id] - 1.0))
118
+ max_memory[gpu_id] = f"{usable_gib}GiB"
119
+ max_memory["cpu"] = "0GiB"
120
+ return max_memory
121
+
122
+
123
+ def _print_gpu_memory(prefix: str = "") -> None:
124
+ if not torch.cuda.is_available():
125
+ return
126
+ lines = []
127
+ for gpu_id in range(torch.cuda.device_count()):
128
+ free_bytes, total_bytes = torch.cuda.mem_get_info(gpu_id)
129
+ used_gib = (total_bytes - free_bytes) / (1024**3)
130
+ total_gib = total_bytes / (1024**3)
131
+ lines.append(f"cuda:{gpu_id} {used_gib:.1f}/{total_gib:.1f} GiB used")
132
+ print(f"{prefix}{' | '.join(lines)}", flush=True)
133
+
134
+
135
+ def _load_transformer(transformer_cls, model_dir: Path, num_gpus: int):
136
+ transformer_kwargs = {
137
+ "torch_dtype": torch.bfloat16,
138
+ "local_files_only": True,
139
+ }
140
+ if num_gpus > 1:
141
+ depth = _read_transformer_depth(model_dir)
142
+ transformer_kwargs["device_map"] = _build_transformer_device_map(depth, num_gpus)
143
+ transformer_kwargs["max_memory"] = _build_gpu_max_memory(num_gpus)
144
+ return transformer_cls.from_pretrained(model_dir / "transformer", **transformer_kwargs)
145
+
146
+
147
+ def _place_pipeline(
148
+ pipe,
149
+ *,
150
+ num_gpus: int,
151
+ ) -> tuple[torch.device, torch.device | None, torch.device | None]:
152
+ if num_gpus > 1:
153
+ if not torch.cuda.is_available():
154
+ raise RuntimeError("Multi-GPU inference requires CUDA.")
155
+
156
+ transformer_device = torch.device("cuda:0")
157
+ text_encoder_device = torch.device("cuda:0")
158
+ vae_device = torch.device(f"cuda:{num_gpus - 1}")
159
+
160
+ if pipe.text_encoder is not None:
161
+ pipe.text_encoder.to(text_encoder_device, dtype=torch.bfloat16)
162
+ if pipe.vae is not None:
163
+ pipe.vae.to(vae_device, dtype=torch.bfloat16)
164
+
165
+ pipe.transformer_device = transformer_device
166
+ pipe.text_encoder_device = text_encoder_device
167
+ pipe.vae_device = vae_device
168
+ return transformer_device, text_encoder_device, vae_device
169
+
170
+ device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
171
+ pipe.to(device)
172
+ pipe.transformer_device = device
173
+ pipe.text_encoder_device = device
174
+ pipe.vae_device = device if pipe.vae is not None else None
175
+ return device, device, pipe.vae_device
176
+
177
+
178
+ def _summarize_transformer_sharding(transformer, num_gpus: int) -> None:
179
+ device_map = getattr(transformer, "hf_device_map", None)
180
+ if not device_map:
181
+ print("Transformer loaded on a single device.", flush=True)
182
+ return
183
+
184
+ counts = Counter(device_map.values())
185
+ summary = ", ".join(f"cuda:{gpu_id}={count} modules" for gpu_id, count in sorted(counts.items()))
186
+ print(f"Transformer sharded across {num_gpus} GPUs: {summary}", flush=True)
187
+
188
+
189
  def _load_pipeline_class(model_dir: Path):
190
  transformer_path = model_dir / "transformer" / "transformer_mvsplit_dit.py"
191
  spec = importlib.util.spec_from_file_location("transformer_mvsplit_dit", transformer_path)
 
203
  def main() -> None:
204
  args = parse_args()
205
  model_dir = args.model.resolve()
206
+ if args.device == "cpu":
207
+ raise ValueError("CPU inference is impractical for MVSplit-DiT-1000L. Use CUDA with --num-gpus 2.")
208
+ if not torch.cuda.is_available():
209
+ raise RuntimeError("CUDA is required for MVSplit-DiT inference.")
210
+
211
  transformer_cls, pipeline_cls = _load_pipeline_class(model_dir)
212
 
213
+ if args.num_gpus > 1:
214
+ print(f"Loading transformer model-parallel across {args.num_gpus} GPUs (no CPU offload)...", flush=True)
215
+ else:
216
+ print("Loading all components on cuda:0...", flush=True)
217
+
218
+ transformer = _load_transformer(transformer_cls, model_dir, args.num_gpus)
219
+ _summarize_transformer_sharding(transformer, args.num_gpus)
220
+
221
  tokenizer = AutoTokenizer.from_pretrained(model_dir / "tokenizer", local_files_only=True)
222
  text_encoder = AutoModel.from_pretrained(
223
  model_dir / "text_encoder",
 
241
  tokenizer=tokenizer,
242
  time_shift_alpha=args.time_shift_alpha,
243
  )
244
+ transformer_device, _, _ = _place_pipeline(pipe, num_gpus=args.num_gpus)
245
+ _print_gpu_memory("GPU memory after load: ")
 
 
246
 
247
  print(
248
  f"Running inference ({args.num_inference_steps} steps, {args.height}x{args.width})...",
249
  flush=True,
250
  )
251
+ generator = torch.Generator(device=transformer_device).manual_seed(args.seed)
252
  result = pipe(
253
  prompt=args.prompt,
254
  height=args.height,
 
258
  generator=generator,
259
  output_type=args.output_type,
260
  )
261
+ _print_gpu_memory("GPU memory after inference: ")
262
 
263
  if args.output_type == "latent":
264
  latents = result.images
MVSplit-DiT-1000L/pipeline.py CHANGED
@@ -4,6 +4,10 @@ Load with native Hugging Face diffusers and trust_remote_code=True.
4
 
5
  from __future__ import annotations
6
 
 
 
 
 
7
  from dataclasses import dataclass
8
  from typing import List, Optional, Tuple, Union
9
 
@@ -38,12 +42,10 @@ except Exception:
38
  # DiT operates on packed FLUX2 latents at 1/16 of the image resolution.
39
  LATENT_DOWNSAMPLE_FACTOR = 16
40
 
41
-
42
  @dataclass
43
  class MVSplitDiTPipelineOutput(BaseOutput):
44
  images: Union[torch.FloatTensor, List]
45
 
46
-
47
  class MVSplitDiTPipeline(DiffusionPipeline):
48
  """
49
  Text-to-image pipeline for MVSplit DiT.
@@ -275,4 +277,4 @@ class MVSplitDiTPipeline(DiffusionPipeline):
275
  self.maybe_free_model_hooks()
276
  if not return_dict:
277
  return (image,)
278
- return MVSplitDiTPipelineOutput(images=image)
 
4
 
5
  from __future__ import annotations
6
 
7
+ """Hub custom pipeline: MVSplitDiTPipeline.
8
+ Load with native Hugging Face diffusers and trust_remote_code=True.
9
+ """
10
+
11
  from dataclasses import dataclass
12
  from typing import List, Optional, Tuple, Union
13
 
 
42
  # DiT operates on packed FLUX2 latents at 1/16 of the image resolution.
43
  LATENT_DOWNSAMPLE_FACTOR = 16
44
 
 
45
  @dataclass
46
  class MVSplitDiTPipelineOutput(BaseOutput):
47
  images: Union[torch.FloatTensor, List]
48
 
 
49
  class MVSplitDiTPipeline(DiffusionPipeline):
50
  """
51
  Text-to-image pipeline for MVSplit DiT.
 
277
  self.maybe_free_model_hooks()
278
  if not return_dict:
279
  return (image,)
280
+ return MVSplitDiTPipelineOutput(images=image)
MVSplit-DiT-1000L/transformer/diffusion_pytorch_model.safetensors.index.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "metadata": {
3
- "total_size": 54552613376
4
  },
5
  "weight_map": {
6
  "blocks.0.attn.k_proj.weight": "diffusion_pytorch_model-00001-of-00006.safetensors",
 
1
  {
2
  "metadata": {
3
+ "total_size": 27276306688
4
  },
5
  "weight_map": {
6
  "blocks.0.attn.k_proj.weight": "diffusion_pytorch_model-00001-of-00006.safetensors",
MVSplit-DiT-1000L/transformer/transformer_mvsplit_dit.py CHANGED
@@ -6,7 +6,7 @@ import torch
6
  import torch.nn.functional as F
7
  from torch import nn
8
  from diffusers.models.activations import SwiGLU
9
- from diffusers.models.embeddings import PatchEmbed, apply_rotary_emb
10
  from diffusers.models.normalization import RMSNorm
11
 
12
  try:
@@ -52,8 +52,10 @@ class MVSplitDiTTransformer2DModelOutput(BaseOutput):
52
  class TwoDimRotary(nn.Module):
53
  def __init__(self, dim: int, base: int = 10000):
54
  super().__init__()
55
- inv_freq = 1.0 / (base ** (torch.arange(0, dim, dtype=torch.float32) / max(dim, 1)))
 
56
  self.register_buffer("inv_freq", inv_freq, persistent=False)
 
57
 
58
  def forward(
59
  self,
@@ -67,11 +69,22 @@ class TwoDimRotary(nn.Module):
67
  freqs_h = torch.outer(pos_h, self.inv_freq).unsqueeze(1).repeat(1, width, 1)
68
  freqs_w = torch.outer(pos_w, self.inv_freq).unsqueeze(0).repeat(height, 1, 1)
69
  freqs = torch.cat([freqs_h, freqs_w], dim=-1).reshape(height * width, -1)
70
- cos = freqs.cos().to(dtype=dtype)
71
- sin = freqs.sin().to(dtype=dtype)
72
  return cos, sin
73
 
74
 
 
 
 
 
 
 
 
 
 
 
 
75
  class QKNorm(nn.Module):
76
  def __init__(self, dim: int, eps: float = 1e-6, trainable: bool = False):
77
  super().__init__()
@@ -169,8 +182,9 @@ class Attention(nn.Module):
169
  value = self.v_proj(hidden_states).reshape(batch_size, -1, self.num_kv_heads, self.head_dim).transpose(1, 2)
170
 
171
  if rope is not None:
172
- query = apply_rotary_emb(query, rope)
173
- key = apply_rotary_emb(key, rope)
 
174
  query, key = self.qk_norm(query, key)
175
 
176
  if self.num_groups > 1:
@@ -222,6 +236,7 @@ class DiTBlock(nn.Module):
222
 
223
  class MVSplitDiTTransformer2DModel(ModelMixin, ConfigMixin):
224
  config_name = "config.json"
 
225
 
226
  @register_to_config
227
  def __init__(
@@ -331,11 +346,21 @@ class MVSplitDiTTransformer2DModel(ModelMixin, ConfigMixin):
331
  if self.use_rope and self.rope is not None:
332
  cos_image, sin_image = self.rope(height_tokens, width_tokens, sequence.device, sequence.dtype)
333
  text_length = text_tokens.shape[1]
334
- rope_width = cos_image.shape[-1]
335
  if text_length > 0:
336
- cos_text = torch.ones((text_length, rope_width), device=sequence.device, dtype=sequence.dtype)
337
- sin_text = torch.zeros((text_length, rope_width), device=sequence.device, dtype=sequence.dtype)
338
- rope = (torch.cat([cos_image, cos_text], dim=0), torch.cat([sin_image, sin_text], dim=0))
 
 
 
 
 
 
 
 
 
 
 
339
  else:
340
  rope = (cos_image, sin_image)
341
 
 
6
  import torch.nn.functional as F
7
  from torch import nn
8
  from diffusers.models.activations import SwiGLU
9
+ from diffusers.models.embeddings import PatchEmbed
10
  from diffusers.models.normalization import RMSNorm
11
 
12
  try:
 
52
  class TwoDimRotary(nn.Module):
53
  def __init__(self, dim: int, base: int = 10000):
54
  super().__init__()
55
+ # Match official mv-split dit.py: half the pairs use arange(0, dim, 2).
56
+ inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
57
  self.register_buffer("inv_freq", inv_freq, persistent=False)
58
+ self.rope_dim = dim
59
 
60
  def forward(
61
  self,
 
69
  freqs_h = torch.outer(pos_h, self.inv_freq).unsqueeze(1).repeat(1, width, 1)
70
  freqs_w = torch.outer(pos_w, self.inv_freq).unsqueeze(0).repeat(height, 1, 1)
71
  freqs = torch.cat([freqs_h, freqs_w], dim=-1).reshape(height * width, -1)
72
+ cos = freqs.cos().to(dtype=dtype)[None, None, :, :]
73
+ sin = freqs.sin().to(dtype=dtype)[None, None, :, :]
74
  return cos, sin
75
 
76
 
77
+ def apply_rotary_emb(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
78
+ """Apply rotary embedding using the official mv-split half-split convention."""
79
+ original_dtype = x.dtype
80
+ half_dim = x.shape[-1] // 2
81
+ x1 = x[..., :half_dim]
82
+ x2 = x[..., half_dim:]
83
+ y1 = x1 * cos + x2 * sin
84
+ y2 = x1 * (-sin) + x2 * cos
85
+ return torch.cat([y1, y2], dim=-1).to(dtype=original_dtype)
86
+
87
+
88
  class QKNorm(nn.Module):
89
  def __init__(self, dim: int, eps: float = 1e-6, trainable: bool = False):
90
  super().__init__()
 
182
  value = self.v_proj(hidden_states).reshape(batch_size, -1, self.num_kv_heads, self.head_dim).transpose(1, 2)
183
 
184
  if rope is not None:
185
+ cos, sin = rope
186
+ query = apply_rotary_emb(query, cos, sin)
187
+ key = apply_rotary_emb(key, cos, sin)
188
  query, key = self.qk_norm(query, key)
189
 
190
  if self.num_groups > 1:
 
236
 
237
  class MVSplitDiTTransformer2DModel(ModelMixin, ConfigMixin):
238
  config_name = "config.json"
239
+ _no_split_modules = ["DiTBlock"]
240
 
241
  @register_to_config
242
  def __init__(
 
346
  if self.use_rope and self.rope is not None:
347
  cos_image, sin_image = self.rope(height_tokens, width_tokens, sequence.device, sequence.dtype)
348
  text_length = text_tokens.shape[1]
 
349
  if text_length > 0:
350
+ cos_text = torch.ones(
351
+ (cos_image.shape[0], cos_image.shape[1], text_length, self.rope_dim),
352
+ device=sequence.device,
353
+ dtype=cos_image.dtype,
354
+ )
355
+ sin_text = torch.zeros(
356
+ (sin_image.shape[0], sin_image.shape[1], text_length, self.rope_dim),
357
+ device=sequence.device,
358
+ dtype=sin_image.dtype,
359
+ )
360
+ rope = (
361
+ torch.cat([cos_image, cos_text], dim=2),
362
+ torch.cat([sin_image, sin_text], dim=2),
363
+ )
364
  else:
365
  rope = (cos_image, sin_image)
366
 
README.md CHANGED
@@ -11,113 +11,40 @@ tags:
11
  - text-to-image
12
  - flow-matching
13
  - mvsplit
14
- inference: true
15
  widget:
16
  - text: a red panda climbing a bamboo stalk
17
  output:
18
- url: demo.png
19
  ---
20
 
21
- # MVSplit-DiT-1000L
22
 
23
- Self-contained Diffusers checkpoint for **MVSplit-DiT** (1000-layer Diffusion Transformer) with a custom `MVSplitDiTPipeline` (`pipeline.py`).
24
 
25
- > **Re-distribution notice:** weights are converted from [`StableKirito/mvsplit-dit-1000l`](https://huggingface.co/StableKirito/mvsplit-dit-1000l). Original work: [Mean Mode Screaming: Mean–Variance Split Residuals for 1000-Layer Diffusion Transformers](https://huggingface.co/papers/2605.06169). License: [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
26
 
27
- ## Demo
28
 
29
- ![MVSplit-DiT-1000L demo](demo.png)
 
 
30
 
31
- Prompt: *a red panda climbing a bamboo stalk* 256×256, 35 steps, CFG 2.0.
32
 
33
- ## Components
34
 
35
- - `pipeline.py` — `MVSplitDiTPipeline`
36
- - `model_index.json`
37
- - `transformer/` — `MVSplitDiTTransformer2DModel` (bf16, 1000 layers)
38
- - `scheduler/` — `FlowMatchEulerDiscreteScheduler`
39
- - `text_encoder/` — Qwen3-0.6B (`AutoModel`)
40
- - `tokenizer/` — Qwen3 tokenizer
41
- - `vae/` — FLUX2 VAE (`AutoencoderKLFlux2`)
42
 
43
- ## Inference
44
 
45
- Run the bundled demo script:
46
 
47
  ```bash
 
48
  python demo_inference.py
49
  ```
50
 
51
- This writes `demo.png` with the default prompt and settings below.
52
-
53
- ```python
54
- from pathlib import Path
55
- import importlib.util
56
- import sys
57
- import torch
58
- from diffusers import AutoencoderKLFlux2
59
- from transformers import AutoModel, AutoTokenizer
60
-
61
- model_dir = Path(".").resolve()
62
-
63
- transformer_path = model_dir / "transformer" / "transformer_mvsplit_dit.py"
64
- spec = importlib.util.spec_from_file_location("transformer_mvsplit_dit", transformer_path)
65
- module = importlib.util.module_from_spec(spec)
66
- sys.modules[spec.name] = module
67
- spec.loader.exec_module(module)
68
-
69
- pipe_spec = importlib.util.spec_from_file_location("mvsplit_pipeline", model_dir / "pipeline.py")
70
- pipe_module = importlib.util.module_from_spec(pipe_spec)
71
- sys.modules[pipe_spec.name] = pipe_module
72
- pipe_spec.loader.exec_module(pipe_module)
73
-
74
- transformer = module.MVSplitDiTTransformer2DModel.from_pretrained(
75
- model_dir / "transformer",
76
- torch_dtype=torch.bfloat16,
77
- local_files_only=True,
78
- )
79
- tokenizer = AutoTokenizer.from_pretrained(model_dir / "tokenizer", local_files_only=True)
80
- text_encoder = AutoModel.from_pretrained(
81
- model_dir / "text_encoder",
82
- torch_dtype=torch.bfloat16,
83
- local_files_only=True,
84
- )
85
- vae = AutoencoderKLFlux2.from_pretrained(
86
- model_dir / "vae",
87
- torch_dtype=torch.bfloat16,
88
- local_files_only=True,
89
- )
90
-
91
- pipe = pipe_module.MVSplitDiTPipeline(
92
- transformer=transformer,
93
- vae=vae,
94
- text_encoder=text_encoder,
95
- tokenizer=tokenizer,
96
- time_shift_alpha=4.0,
97
- )
98
- pipe.enable_sequential_cpu_offload()
99
-
100
- generator = torch.Generator(device="cpu").manual_seed(42)
101
- image = pipe(
102
- prompt="a red panda climbing a bamboo stalk",
103
- height=256,
104
- width=256,
105
- num_inference_steps=35,
106
- guidance_scale=2.0,
107
- generator=generator,
108
- ).images[0]
109
- image.save("demo.png")
110
- ```
111
-
112
- ### Recommended settings
113
-
114
- | Parameter | Default | Notes |
115
- | --- | ---: | --- |
116
- | `height` / `width` | 256 | Square output resolution |
117
- | `num_inference_steps` | 35 | Flow-matching Euler steps |
118
- | `guidance_scale` | 2.0 | Classifier-free guidance |
119
- | `time_shift_alpha` | 4.0 | Time-shift in the flow schedule (must match training) |
120
- | `seed` | 42 | Reproducible sampling |
121
 
122
  ## Citation
123
 
 
11
  - text-to-image
12
  - flow-matching
13
  - mvsplit
 
14
  widget:
15
  - text: a red panda climbing a bamboo stalk
16
  output:
17
+ url: MVSplit-DiT-1000L/demo.png
18
  ---
19
 
20
+ # BiliSakura/MVSplit-DiT-diffusers
21
 
22
+ Diffusers-ready checkpoints for **MVSplit-DiT** (Mean–Variance Split Residual Diffusion Transformers), converted for local/offline use with a project-owned custom `MVSplitDiTPipeline`.
23
 
24
+ > **Re-distribution notice:** weights are converted from [`StableKirito/mvsplit-dit-1000l`](https://huggingface.co/StableKirito/mvsplit-dit-1000l). Original work: [Mean Mode Screaming](https://huggingface.co/papers/2605.06169). License: [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
25
 
26
+ ## Available checkpoints
27
 
28
+ | Subfolder | Params | Task | Resolution |
29
+ | --- | ---: | --- | ---: |
30
+ | [`MVSplit-DiT-1000L/`](MVSplit-DiT-1000L/) | 1000L | text-to-image | 256×256 |
31
 
32
+ Each subfolder is a self-contained Diffusers model repo with `pipeline.py`, `model_index.json`, and component weights.
33
 
34
+ ## Demo
35
 
36
+ ![MVSplit-DiT-1000L demo](MVSplit-DiT-1000L/demo.png)
 
 
 
 
 
 
37
 
38
+ Prompt: *a red panda climbing a bamboo stalk* — 256×256, 35 steps, CFG 2.0.
39
 
40
+ ## Inference
41
 
42
  ```bash
43
+ cd MVSplit-DiT-1000L
44
  python demo_inference.py
45
  ```
46
 
47
+ See [`MVSplit-DiT-1000L/README.md`](MVSplit-DiT-1000L/README.md) for full usage and recommended settings.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  ## Citation
50