multimodalart HF Staff commited on
Commit
bf77163
·
verified ·
1 Parent(s): 83c8f19

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ 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
+ examples/dog.jpg filter=lfs diff=lfs merge=lfs -text
37
+ examples/man_beach.jpg filter=lfs diff=lfs merge=lfs -text
38
+ examples/tent.jpg filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,13 +1,27 @@
1
  ---
2
- title: Krea 2 Depth Controlnet
3
- emoji: 🚀
4
- colorFrom: green
5
- colorTo: purple
6
  sdk: gradio
7
  sdk_version: 6.19.0
8
- python_version: '3.12'
9
  app_file: app.py
10
- pinned: false
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Krea-2 Depth ControlNet
3
+ emoji: 🏔️
4
+ colorFrom: gray
5
+ colorTo: yellow
6
  sdk: gradio
7
  sdk_version: 6.19.0
 
8
  app_file: app.py
9
+ short_description: Depth-controlled image generation with Krea-2 Turbo
10
+ python_version: "3.12"
11
+ startup_duration_timeout: 1h
12
  ---
13
 
14
+ # Krea-2 Depth ControlNet-LoRA
15
+
16
+ Depth-conditioned image generation for [Krea-2](https://huggingface.co/krea/Krea-2-Turbo).
17
+ Give it any image and a prompt — it extracts the depth map with
18
+ **Depth-Anything-V2** and generates a new image with the **same 3D structure and
19
+ composition**, but whatever content and style you ask for.
20
+
21
+ - Base: [krea/Krea-2-Turbo](https://huggingface.co/krea/Krea-2-Turbo) (8-step)
22
+ - Control: [Patil/Krea-2-depth-controlnet](https://huggingface.co/Patil/Krea-2-depth-controlnet)
23
+ (rank-64 LoRA + expanded input projection)
24
+ - Depth: `depth-anything/Depth-Anything-V2-Large-hf`
25
+
26
+ Model weights are subject to the
27
+ [Krea 2 community license](https://www.krea.ai/krea-2-licensing).
app.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ # Neutralize torch.compile decorators inside mmdit.py (not supported on ZeroGPU
4
+ # forked workers) and reduce allocator fragmentation for the 13B DiT.
5
+ os.environ.setdefault("TORCHDYNAMO_DISABLE", "1")
6
+ os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
7
+
8
+ import random
9
+
10
+ import spaces # noqa: E402 MUST come before torch / CUDA-touching imports
11
+ import torch # noqa: E402
12
+ import gradio as gr # noqa: E402
13
+ from huggingface_hub import hf_hub_download # noqa: E402
14
+
15
+ from pipeline import DepthLoRAPipeline # noqa: E402
16
+
17
+ MAX_SEED = 2**31 - 1
18
+
19
+ # --------------------------------------------------------------------- loading
20
+ # Turbo base (8-step, no CFG) — the author's recommended fast configuration.
21
+ # Krea-2 base checkpoint (~26GB) + depth-control LoRA + Qwen3-VL-4B text encoder
22
+ # + Qwen-Image VAE + Depth-Anything-V2-Large. Loaded once at module scope so
23
+ # ZeroGPU packs the weights and streams them to VRAM on the first GPU call.
24
+ print("Resolving Krea-2-Turbo base checkpoint...")
25
+ BASE_CKPT = os.path.realpath(hf_hub_download("krea/Krea-2-Turbo", "turbo.safetensors"))
26
+ LORA_CKPT = os.path.realpath(
27
+ hf_hub_download("Patil/Krea-2-depth-controlnet", "depth-control-lora.safetensors")
28
+ )
29
+
30
+ print("Building DepthLoRAPipeline (13B DiT + Qwen3-VL-4B + VAE + DepthAnything)...")
31
+ pipe = DepthLoRAPipeline(BASE_CKPT, LORA_CKPT, device="cuda")
32
+ print("Pipeline ready.")
33
+
34
+
35
+ # --------------------------------------------------------------------- inference
36
+ @spaces.GPU(duration=120)
37
+ def generate(
38
+ image,
39
+ prompt: str = "",
40
+ steps: int = 8,
41
+ lora_scale: float = 1.0,
42
+ seed: int = 0,
43
+ randomize_seed: bool = True,
44
+ progress=gr.Progress(track_tqdm=True),
45
+ ):
46
+ """Generate a new image that keeps the 3D structure of an input image.
47
+
48
+ Extracts a depth map from the input image with Depth-Anything-V2 and
49
+ generates a new image following the same depth/composition but with the
50
+ content and style described by the prompt (Krea-2-Turbo, 8-step).
51
+
52
+ Args:
53
+ image: The input image whose depth/structure is preserved.
54
+ prompt: What to generate. Leave empty for depth-only generation.
55
+ steps: Number of sampling steps (8 recommended for Turbo).
56
+ lora_scale: Control strength. <1.0 relaxes structure adherence.
57
+ seed: RNG seed for reproducibility.
58
+ randomize_seed: If True, pick a random seed each run.
59
+
60
+ Returns:
61
+ A tuple of (generated image, extracted depth map, used seed).
62
+ """
63
+ if image is None:
64
+ raise gr.Error("Please provide an input image.")
65
+ if randomize_seed:
66
+ seed = random.randint(0, MAX_SEED)
67
+ seed = int(seed)
68
+
69
+ # Turbo config: cfg=0.0, mu=1.15. lora_scale is applied to the loaded LoRA
70
+ # layers in place (they were built with scale=1.0), so scale the effective
71
+ # weight by mutating each LoRALinear's scale before the run.
72
+ from pipeline import LoRALinear
73
+
74
+ for module in pipe.model.modules():
75
+ if isinstance(module, LoRALinear):
76
+ module.scale = (64 / 64) * float(lora_scale)
77
+
78
+ out, depth = pipe(
79
+ image,
80
+ prompt=prompt or "",
81
+ steps=int(steps),
82
+ cfg=0.0,
83
+ mu=1.15,
84
+ seed=seed,
85
+ )
86
+ return out, depth, seed
87
+
88
+
89
+ # --------------------------------------------------------------------- UI
90
+ CSS = """
91
+ #col-container { max-width: 1200px; margin: 0 auto; }
92
+ .dark .gradio-container { color: var(--body-text-color); }
93
+ """
94
+
95
+ with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo:
96
+ with gr.Column(elem_id="col-container"):
97
+ gr.Markdown(
98
+ """
99
+ # Krea-2 Depth ControlNet-LoRA
100
+
101
+ Give it any image and a prompt — it extracts the depth map with
102
+ **Depth-Anything-V2** and generates a new image with the **same 3D
103
+ structure and composition**, but whatever content and style you ask
104
+ for. Powered by [Krea-2-Turbo](https://huggingface.co/krea/Krea-2-Turbo)
105
+ (8-step) + the
106
+ [depth-control LoRA](https://huggingface.co/Patil/Krea-2-depth-controlnet).
107
+
108
+ *Best with photos / renders that have real perspective. Flat 2D
109
+ illustrations give weak control. Empty prompt = depth-only generation.*
110
+ """
111
+ )
112
+ with gr.Row():
113
+ with gr.Column():
114
+ image = gr.Image(label="Input image (depth source)", type="pil")
115
+ prompt = gr.Textbox(
116
+ label="Prompt",
117
+ placeholder="a futuristic spaceship interior, cinematic lighting",
118
+ lines=2,
119
+ )
120
+ run = gr.Button("Generate", variant="primary")
121
+ with gr.Accordion("Advanced settings", open=False):
122
+ steps = gr.Slider(
123
+ 4, 16, value=8, step=1, label="Sampling steps"
124
+ )
125
+ lora_scale = gr.Slider(
126
+ 0.3,
127
+ 1.4,
128
+ value=1.0,
129
+ step=0.05,
130
+ label="Control strength (LoRA scale)",
131
+ )
132
+ randomize_seed = gr.Checkbox(
133
+ label="Randomize seed", value=True
134
+ )
135
+ seed = gr.Slider(
136
+ 0, MAX_SEED, value=0, step=1, label="Seed"
137
+ )
138
+ with gr.Column():
139
+ output = gr.Image(label="Generated image")
140
+ depth_out = gr.Image(label="Extracted depth map")
141
+
142
+ gr.Examples(
143
+ examples=[
144
+ ["examples/dog.jpg", "a majestic lion, golden hour, photorealistic"],
145
+ [
146
+ "examples/landscape.jpg",
147
+ "an alien planet landscape, purple sky, sci-fi",
148
+ ],
149
+ [
150
+ "examples/man_beach.jpg",
151
+ "an astronaut on the moon, cinematic lighting",
152
+ ],
153
+ [
154
+ "examples/tent.jpg",
155
+ "a cozy cabin in a snowy forest at dusk",
156
+ ],
157
+ ],
158
+ inputs=[image, prompt],
159
+ outputs=[output, depth_out, seed],
160
+ fn=generate,
161
+ cache_examples=True,
162
+ cache_mode="lazy",
163
+ )
164
+
165
+ run.click(
166
+ fn=generate,
167
+ inputs=[image, prompt, steps, lora_scale, seed, randomize_seed],
168
+ outputs=[output, depth_out, seed],
169
+ api_name="generate",
170
+ )
171
+
172
+ if __name__ == "__main__":
173
+ demo.launch(mcp_server=True)
examples/dog.jpg ADDED

Git LFS Details

  • SHA256: 6385da70b7991fc95287c5242c3e2addc982c35bd024582978b7edb2a1370838
  • Pointer size: 131 Bytes
  • Size of remote file: 114 kB
examples/landscape.jpg ADDED
examples/man_beach.jpg ADDED

Git LFS Details

  • SHA256: c06f6906ef2e94c56221cff67268e2a8243b2f0ff9b1626081bbbea729a6e15c
  • Pointer size: 131 Bytes
  • Size of remote file: 333 kB
examples/tent.jpg ADDED

Git LFS Details

  • SHA256: 32a434795ef91e315435e927ed1568986391320351fc7c04f253e3b0074c08e3
  • Pointer size: 131 Bytes
  • Size of remote file: 175 kB
mmdit.py ADDED
@@ -0,0 +1,417 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from dataclasses import dataclass
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+ from einops import rearrange
8
+ from torch import Tensor
9
+ from torch.nn.attention import SDPBackend, sdpa_kernel
10
+
11
+
12
+ def rope(pos: Tensor, dim: int, theta: float = 1e4, ntk: float = 1.0) -> Tensor:
13
+ scale = torch.arange(0, dim, 2, dtype=torch.float64, device=pos.device) / dim
14
+ omega = 1.0 / ((theta * ntk) ** scale)
15
+ out = torch.einsum("...n,d->...nd", pos, omega)
16
+ out = torch.stack(
17
+ [torch.cos(out), -torch.sin(out), torch.sin(out), torch.cos(out)], dim=-1
18
+ )
19
+ out = rearrange(out, "b n d (i j) -> b n d i j", i=2, j=2)
20
+ return out.float()
21
+
22
+
23
+ def ropeapply(xq: Tensor, xk: Tensor, freqs: Tensor) -> tuple[Tensor, Tensor]:
24
+ xq_ = xq.float().reshape(*xq.shape[:-1], -1, 1, 2)
25
+ xk_ = xk.float().reshape(*xk.shape[:-1], -1, 1, 2)
26
+ freqs = freqs[:, None, :, :, :]
27
+ xq_ = freqs[..., 0] * xq_[..., 0] + freqs[..., 1] * xq_[..., 1]
28
+ xk_ = freqs[..., 0] * xk_[..., 0] + freqs[..., 1] * xk_[..., 1]
29
+ return xq_.reshape(*xq.shape).to(xq.dtype), xk_.reshape(*xk.shape).to(xk.dtype)
30
+
31
+
32
+ def attention(
33
+ q: Tensor,
34
+ k: Tensor,
35
+ v: Tensor,
36
+ mask: Tensor | None = None,
37
+ scale: float | None = None,
38
+ gqa: bool = False,
39
+ ) -> Tensor:
40
+ with sdpa_kernel(SDPBackend.CUDNN_ATTENTION):
41
+ x = F.scaled_dot_product_attention(
42
+ q, k, v, attn_mask=mask, scale=scale, enable_gqa=gqa
43
+ )
44
+ return rearrange(x, "B H L D -> B L (H D)")
45
+
46
+
47
+ def _mask(mask: Tensor) -> Tensor:
48
+ """Expand a (B, L) key-padding mask into a (B, 1, L, L) attention mask."""
49
+ return mask.unsqueeze(1).unsqueeze(2) * mask.unsqueeze(1).unsqueeze(3)
50
+
51
+
52
+ def temb(
53
+ t: Tensor,
54
+ dim: int,
55
+ period: float = 1e4,
56
+ tfactor: float = 1e3,
57
+ device: torch.device = None,
58
+ dtype: torch.dtype = None,
59
+ ) -> Tensor:
60
+ half = dim // 2
61
+ freqs = torch.exp(
62
+ -math.log(period)
63
+ * torch.arange(half, dtype=torch.float32, device=device)
64
+ / half
65
+ )
66
+ # t: (B,) -> args: (B, 1, half), so the embedding broadcasts as a per-sample vec.
67
+ args = (t.float() * tfactor)[:, None, None] * freqs
68
+ sin, cos = torch.sin(args), torch.cos(args)
69
+ return torch.cat((cos, sin), dim=-1).to(dtype=dtype)
70
+
71
+
72
+ @dataclass
73
+ class SingleMMDiTConfig:
74
+ features: int
75
+ tdim: int
76
+ txtdim: int
77
+ heads: int
78
+ multiplier: int
79
+ layers: int
80
+ patch: int
81
+ channels: int
82
+ bias: bool = False
83
+ theta: float = 1e3
84
+ kvheads: int | None = None
85
+ txtlayers: int = 1
86
+ txtheads: int = 20
87
+ txtkvheads: int = 20
88
+
89
+
90
+ class SimpleModulation(torch.nn.Module):
91
+ def __init__(self, dim: int):
92
+ super().__init__()
93
+ self.lin = torch.nn.Parameter(torch.zeros(2, dim))
94
+ self.multiplier = 2
95
+
96
+ # vec (b d)
97
+ def forward(self, vec: Tensor):
98
+ out = vec + rearrange(self.lin, "two d -> 1 two d")
99
+ scale, shift = out.chunk(self.multiplier, dim=1)
100
+ return scale, shift
101
+
102
+
103
+ class DoubleSharedModulation(torch.nn.Module):
104
+ def __init__(self, dim: int):
105
+ super().__init__()
106
+ self.lin = torch.nn.Parameter(torch.zeros(6 * dim))
107
+
108
+ # vec (b (6 d))
109
+ def forward(self, vec: Tensor):
110
+ out = vec + self.lin
111
+ prescale, preshift, pregate, postscale, postshift, postgate = out.chunk(
112
+ 6, dim=-1
113
+ )
114
+ return prescale, preshift, pregate, postscale, postshift, postgate
115
+
116
+
117
+ class PositionalEncoding(torch.nn.Module):
118
+ def __init__(self, dim, axdims: list[int], theta: float = 1e2, ntk: float = 1.0):
119
+ super().__init__()
120
+ self.axdims = axdims # how to split the head dimension across the position axes
121
+ self.theta = theta
122
+ self.ntk = ntk
123
+
124
+ @torch.compile(fullgraph=True)
125
+ def forward(self, pos: Tensor) -> Tensor:
126
+ return torch.cat(
127
+ [
128
+ rope(pos[..., i], d, self.theta, self.ntk)
129
+ for i, d in enumerate(self.axdims)
130
+ ],
131
+ dim=-3,
132
+ )
133
+
134
+
135
+ class QKNorm(torch.nn.Module):
136
+ def __init__(self, dim: int):
137
+ super().__init__()
138
+ self.qnorm = RMSNorm(dim)
139
+ self.knorm = RMSNorm(dim)
140
+
141
+ def forward(self, q: Tensor, k: Tensor, v: Tensor) -> tuple[Tensor, Tensor, Tensor]:
142
+ return self.qnorm(q), self.knorm(k), v
143
+
144
+
145
+ class RMSNorm(torch.nn.Module):
146
+ def __init__(self, features: int, eps: float = 1e-05, device: torch.device = None):
147
+ super().__init__()
148
+ self.features = features
149
+ self.eps = eps
150
+ self.scale = torch.nn.Parameter(
151
+ torch.zeros(features, device=device, dtype=torch.float32)
152
+ )
153
+
154
+ @torch.compile(fullgraph=True)
155
+ def forward(self, x: Tensor) -> Tensor:
156
+ t, dtype = x.float(), x.dtype
157
+ t = F.rms_norm(
158
+ t, (self.features,), eps=self.eps, weight=(self.scale.float() + 1.0)
159
+ )
160
+ return t.to(dtype)
161
+
162
+
163
+ class SwiGLU(torch.nn.Module):
164
+ def __init__(
165
+ self, features: int, multiplier: int, bias: bool = False, multiple: int = 128
166
+ ):
167
+ super().__init__()
168
+
169
+ mlpdim = int(2 * features / 3) * multiplier
170
+ mlpdim = multiple * ((mlpdim + multiple - 1) // multiple)
171
+
172
+ self.gate = torch.nn.Linear(features, mlpdim, bias=bias)
173
+ self.up = torch.nn.Linear(features, mlpdim, bias=bias)
174
+ self.down = torch.nn.Linear(mlpdim, features, bias=bias)
175
+
176
+ def forward(self, x: Tensor) -> Tensor:
177
+ return self.down(F.silu(self.gate(x)) * self.up(x))
178
+
179
+
180
+ class Attention(torch.nn.Module):
181
+ def __init__(self, dim: int, heads: int, kvheads: int = None, bias: bool = False):
182
+ super().__init__()
183
+ self.heads = heads
184
+ self.kvheads = kvheads if kvheads is not None else heads
185
+ self.headdim = dim // self.heads
186
+
187
+ self.wq = torch.nn.Linear(dim, self.headdim * self.heads, bias=bias)
188
+ self.wk = torch.nn.Linear(dim, self.headdim * self.kvheads, bias=bias)
189
+ self.wv = torch.nn.Linear(dim, self.headdim * self.kvheads, bias=bias)
190
+ self.gate = torch.nn.Linear(dim, dim, bias=bias)
191
+ self.qknorm = QKNorm(self.headdim)
192
+ self.gqa = self.heads != self.kvheads
193
+ self.wo = torch.nn.Linear(dim, dim, bias=bias)
194
+
195
+ def forward(
196
+ self, qkv: Tensor, freqs: Tensor | None = None, mask: Tensor | None = None
197
+ ) -> Tensor:
198
+ q, k, v, gate = self.wq(qkv), self.wk(qkv), self.wv(qkv), self.gate(qkv)
199
+
200
+ q, k, v = (
201
+ rearrange(q, "B L (H D) -> B H L D", H=self.heads),
202
+ rearrange(k, "B L (H D) -> B H L D", H=self.kvheads),
203
+ rearrange(v, "B L (H D) -> B H L D", H=self.kvheads),
204
+ )
205
+
206
+ q, k, v = self.qknorm(q, k, v)
207
+ if freqs is not None:
208
+ q, k = ropeapply(q, k, freqs)
209
+ out = self.wo(attention(q, k, v, mask=mask, gqa=self.gqa) * F.sigmoid(gate))
210
+
211
+ return out
212
+
213
+
214
+ class LastLayer(torch.nn.Module):
215
+ def __init__(self, features: int, patch: int, channels: int):
216
+ super().__init__()
217
+ self.norm = RMSNorm(features)
218
+ self.linear = torch.nn.Linear(features, patch * patch * channels, bias=True)
219
+ self.modulation = SimpleModulation(features)
220
+
221
+ @torch.compile(fullgraph=True)
222
+ def forward(self, x: Tensor, tvec: Tensor) -> Tensor:
223
+ scale, shift = self.modulation(tvec)
224
+ x = (1 + scale) * self.norm(x) + shift
225
+ x = self.linear(x)
226
+ return x
227
+
228
+
229
+ class TextFusionBlock(torch.nn.Module):
230
+ def __init__(
231
+ self,
232
+ features: int,
233
+ heads: int,
234
+ multiplier: int,
235
+ bias: bool = False,
236
+ kvheads: int = None,
237
+ ):
238
+ super().__init__()
239
+ self.prenorm = RMSNorm(features)
240
+ self.postnorm = RMSNorm(features)
241
+ self.attn = Attention(dim=features, heads=heads, bias=bias, kvheads=kvheads)
242
+ self.mlp = SwiGLU(features, multiplier, bias)
243
+
244
+ def forward(self, x: Tensor, mask: Tensor | None = None) -> Tensor:
245
+ x = x + self.attn(self.prenorm(x), mask=mask)
246
+ x = x + self.mlp(self.postnorm(x))
247
+
248
+ return x
249
+
250
+
251
+ class TextFusionTransformer(torch.nn.Module):
252
+ # num_txt_layers is the number of selected encoder hidden-state layers fed in
253
+ # (projected down to 1), NOT the transformer depth — that's fixed at 2 + 2 blocks.
254
+ def __init__(
255
+ self,
256
+ num_txt_layers: int,
257
+ txt_dim: int,
258
+ heads: int,
259
+ multiplier: int,
260
+ bias: bool = False,
261
+ kvheads: int = None,
262
+ ):
263
+ super().__init__()
264
+ self.layerwise_blocks = torch.nn.ModuleList(
265
+ [
266
+ TextFusionBlock(txt_dim, heads, multiplier, bias, kvheads)
267
+ for _ in range(2)
268
+ ]
269
+ )
270
+ self.projector = torch.nn.Linear(num_txt_layers, 1, bias=False)
271
+ self.refiner_blocks = torch.nn.ModuleList(
272
+ [
273
+ TextFusionBlock(txt_dim, heads, multiplier, bias, kvheads)
274
+ for _ in range(2)
275
+ ]
276
+ )
277
+
278
+ def forward(self, x: Tensor, mask: Tensor | None = None) -> Tensor:
279
+ b, l, n, d = x.shape
280
+ x = x.reshape(b * l, n, d)
281
+ for block in self.layerwise_blocks:
282
+ x = block(x.contiguous(), mask=None)
283
+ x = rearrange(x, "(b l) n d -> b l d n", b=b, l=l)
284
+ x = self.projector(x)
285
+ x = x.squeeze(-1)
286
+
287
+ for block in self.refiner_blocks:
288
+ x = block(x, mask=mask)
289
+
290
+ return x
291
+
292
+
293
+ class SingleStreamBlock(nn.Module):
294
+ def __init__(
295
+ self,
296
+ features: int,
297
+ heads: int,
298
+ multiplier: int,
299
+ bias: bool = False,
300
+ kvheads: int = None,
301
+ ):
302
+ super().__init__()
303
+ self.mod = DoubleSharedModulation(features)
304
+ self.prenorm = RMSNorm(features)
305
+ self.postnorm = RMSNorm(features)
306
+ self.attn = Attention(dim=features, heads=heads, bias=bias, kvheads=kvheads)
307
+ self.mlp = SwiGLU(features, multiplier, bias)
308
+
309
+ def forward(
310
+ self, x: Tensor, vec: Tensor, freqs: Tensor, mask: Tensor | None = None
311
+ ) -> Tensor:
312
+ prescale, preshift, pregate, postscale, postshift, postgate = self.mod(vec)
313
+ x = x + pregate * self.attn(
314
+ (1 + prescale) * self.prenorm(x) + preshift, freqs, mask
315
+ )
316
+ x = x + postgate * self.mlp((1 + postscale) * self.postnorm(x) + postshift)
317
+
318
+ return x
319
+
320
+
321
+ class SingleStreamDiT(nn.Module):
322
+ def __init__(self, config: SingleMMDiTConfig):
323
+ super().__init__()
324
+ self.config = config
325
+
326
+ headdim = config.features // config.heads
327
+ axes = [
328
+ headdim - 12 * (headdim // 16),
329
+ 6 * (headdim // 16),
330
+ 6 * (headdim // 16),
331
+ ]
332
+ assert sum(axes) == headdim, f"sum(axes) = {sum(axes)}, headdim = {headdim}"
333
+ assert all(a % 2 == 0 for a in axes), f"axes = {axes}"
334
+
335
+ self.posemb = PositionalEncoding(
336
+ config.features, axes, theta=config.theta, ntk=1.0
337
+ )
338
+ self.first = nn.Linear(
339
+ config.channels * config.patch**2, config.features, bias=True
340
+ )
341
+
342
+ self.blocks = nn.ModuleList(
343
+ [
344
+ SingleStreamBlock(
345
+ config.features,
346
+ config.heads,
347
+ config.multiplier,
348
+ config.bias,
349
+ config.kvheads,
350
+ )
351
+ for _ in range(config.layers)
352
+ ]
353
+ )
354
+ self.tmlp = nn.Sequential(
355
+ nn.Linear(config.tdim, config.features),
356
+ nn.GELU(approximate="tanh"),
357
+ nn.Linear(config.features, config.features),
358
+ )
359
+ self.txtfusion = TextFusionTransformer(
360
+ config.txtlayers,
361
+ config.txtdim,
362
+ config.txtheads,
363
+ config.multiplier,
364
+ config.bias,
365
+ config.txtkvheads,
366
+ )
367
+ self.txtmlp = nn.Sequential(
368
+ RMSNorm(config.txtdim),
369
+ nn.Linear(config.txtdim, config.features),
370
+ nn.GELU(approximate="tanh"),
371
+ nn.Linear(config.features, config.features),
372
+ )
373
+ self.last = LastLayer(config.features, config.patch, config.channels)
374
+
375
+ self.tproj = nn.Sequential(
376
+ nn.GELU(approximate="tanh"), nn.Linear(config.features, config.features * 6)
377
+ )
378
+
379
+ def forward(
380
+ self,
381
+ img: Tensor,
382
+ context: Tensor,
383
+ t: Tensor,
384
+ pos: Tensor,
385
+ mask: Tensor | None = None,
386
+ ) -> Tensor:
387
+ img = self.first(img)
388
+ t = self.tmlp(temb(t, self.config.tdim, device=img.device, dtype=img.dtype))
389
+ tvec = self.tproj(t)
390
+
391
+ txtmask = _mask(mask[:, : context.shape[1]])
392
+
393
+ context = self.txtfusion(context, mask=txtmask)
394
+ context = self.txtmlp(context)
395
+
396
+ txtlen, imglen = context.shape[1], img.shape[1]
397
+ combined = torch.cat((context, img), dim=1)
398
+
399
+ # Pad combined sequence to a multiple of 256 to stabilize compiled kernel shapes.
400
+ fulllen = combined.shape[1]
401
+ _padlen = (-fulllen) % 256
402
+ if _padlen > 0:
403
+ combined = F.pad(combined, (0, 0, 0, _padlen))
404
+ mask = F.pad(mask, (0, _padlen), value=False)
405
+ pos = F.pad(pos, (0, 0, 0, _padlen))
406
+
407
+ mask = _mask(mask)
408
+
409
+ freqs = self.posemb(pos)
410
+
411
+ for block in self.blocks:
412
+ combined = block(combined, tvec, freqs, mask)
413
+
414
+ final = self.last(combined, t)
415
+ output = final[:, txtlen : txtlen + imglen, :]
416
+
417
+ return output
pipeline.py ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Standalone depth-ControlNet-LoRA pipeline for Krea-2.
2
+
3
+ Everything needed for inference: LoRA surgery, text conditioning, VAE,
4
+ depth estimation, and the flow-matching sampler with control injection.
5
+ `mmdit.py` is the unmodified DiT definition from the official krea-2 repo.
6
+ """
7
+
8
+ import math
9
+ import os
10
+
11
+ os.environ.setdefault("TORCHDYNAMO_DISABLE", "1")
12
+
13
+ import numpy as np
14
+ import torch
15
+ import torch.nn as nn
16
+ import torch.nn.functional as F
17
+ from einops import rearrange, repeat
18
+ from PIL import Image
19
+ from safetensors.torch import load_file
20
+
21
+ from mmdit import SingleMMDiTConfig, SingleStreamDiT, _mask, temb
22
+
23
+ K2_CONFIG = SingleMMDiTConfig(
24
+ features=6144, tdim=256, txtdim=2560, heads=48, kvheads=12, multiplier=4,
25
+ layers=28, patch=2, channels=16, txtheads=20, txtkvheads=20, txtlayers=12,
26
+ )
27
+
28
+ BASE_CKPTS = {"raw": ("krea/Krea-2-Raw", "raw.safetensors"),
29
+ "turbo": ("krea/Krea-2-Turbo", "turbo.safetensors")}
30
+
31
+ LORA_TARGETS = ("attn.wq", "attn.wk", "attn.wv", "attn.wo", "attn.gate",
32
+ "mlp.gate", "mlp.up", "mlp.down")
33
+
34
+ # timestep-shift interpolation endpoints (image seq len -> mu), from the
35
+ # scheduler config Krea-2 was trained with
36
+ MU_X1, MU_Y1, MU_X2, MU_Y2 = 256, 0.5, 6400, 1.15
37
+
38
+ BUCKETS = [(1024, 1024), (896, 1152), (1152, 896), (832, 1216), (1216, 832),
39
+ (768, 1344), (1344, 768), (704, 1472), (1472, 704)]
40
+
41
+
42
+ # ---------------------------------------------------------------- model surgery
43
+
44
+ class LoRALinear(nn.Module):
45
+ """y = Wx + scale * B(Ax). A: rank x in, B: out x rank."""
46
+
47
+ def __init__(self, base: nn.Linear, rank: int, alpha: float, scale: float = 1.0):
48
+ super().__init__()
49
+ self.base = base
50
+ self.scale = (alpha / rank) * scale
51
+ self.A = nn.Parameter(torch.zeros(rank, base.in_features, dtype=torch.float32))
52
+ self.B = nn.Parameter(torch.zeros(base.out_features, rank, dtype=torch.float32))
53
+
54
+ def forward(self, x):
55
+ lora = (x @ self.A.T.to(x.dtype)) @ self.B.T.to(x.dtype)
56
+ return self.base(x) + lora * self.scale
57
+
58
+
59
+ class ControlInputLayer(nn.Module):
60
+ """Replaces the DiT input projection: in_features doubled (64 -> 128) to
61
+ accept [noisy latent patches ; depth latent patches] concatenated on the
62
+ channel dim. Trained weights are loaded from the LoRA checkpoint."""
63
+
64
+ def __init__(self, pretrained: nn.Linear):
65
+ super().__init__()
66
+ in_f, out_f = pretrained.in_features, pretrained.out_features
67
+ self.weight = nn.Parameter(torch.zeros(out_f, in_f * 2, dtype=torch.float32))
68
+ self.bias = nn.Parameter(pretrained.bias.detach().float().clone())
69
+ with torch.no_grad():
70
+ self.weight[:, :in_f] = pretrained.weight.detach().float()
71
+
72
+ def forward(self, x):
73
+ return F.linear(x, self.weight.to(x.dtype), self.bias.to(x.dtype))
74
+
75
+
76
+ def _get(root, path):
77
+ for p in path.split("."):
78
+ root = getattr(root, p)
79
+ return root
80
+
81
+
82
+ def _set(root, path, new):
83
+ parts = path.split(".")
84
+ setattr(_get(root, ".".join(parts[:-1])) if len(parts) > 1 else root,
85
+ parts[-1], new)
86
+
87
+
88
+ def build_model(base_ckpt: str, lora_ckpt: str, rank: int = 64,
89
+ lora_scale: float = 1.0, device: str = "cuda",
90
+ dtype: torch.dtype = torch.bfloat16) -> SingleStreamDiT:
91
+ with torch.device("meta"):
92
+ model = SingleStreamDiT(K2_CONFIG)
93
+ model.load_state_dict(load_file(base_ckpt), strict=True, assign=True)
94
+ model = model.to(device=device, dtype=dtype).requires_grad_(False)
95
+
96
+ model.first = ControlInputLayer(model.first).to(device)
97
+ for i in range(K2_CONFIG.layers):
98
+ for t in LORA_TARGETS:
99
+ path = f"blocks.{i}.{t}"
100
+ _set(model, path, LoRALinear(_get(model, path), rank, rank,
101
+ lora_scale).to(device))
102
+
103
+ sd = load_file(lora_ckpt)
104
+ missing, unexpected = model.load_state_dict(sd, strict=False)
105
+ assert not unexpected, f"unexpected keys: {unexpected[:5]}"
106
+ return model.eval()
107
+
108
+
109
+ # ---------------------------------------------------------------- conditioning
110
+
111
+ class TextConditioner(nn.Module):
112
+ """Qwen3-VL-4B encoder exactly as Krea-2 uses it: hidden states from 12
113
+ selected layers, stacked, with the chat-template prefix sliced off."""
114
+
115
+ PREFIX = (
116
+ "<|im_start|>system\nDescribe the image by detailing the color, shape, size, "
117
+ "texture, quantity, text, spatial relationships of the objects and background:"
118
+ "<|im_end|>\n<|im_start|>user\n"
119
+ )
120
+ SUFFIX = "<|im_end|>\n<|im_start|>assistant\n"
121
+ PREFIX_IDX = 34
122
+ SELECT_LAYERS = (2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35)
123
+
124
+ def __init__(self, model_id="Qwen/Qwen3-VL-4B-Instruct", max_length=512,
125
+ device="cuda", dtype=torch.bfloat16):
126
+ super().__init__()
127
+ from transformers import AutoTokenizer, Qwen3VLForConditionalGeneration
128
+
129
+ self.qwen = (Qwen3VLForConditionalGeneration
130
+ .from_pretrained(model_id, torch_dtype=dtype)
131
+ .to(device).eval().requires_grad_(False))
132
+ self.tokenizer = AutoTokenizer.from_pretrained(model_id)
133
+ self.max_length = max_length
134
+ self.device = device
135
+
136
+ @torch.no_grad()
137
+ def forward(self, prompts):
138
+ text = [self.PREFIX + p for p in prompts]
139
+ inputs = self.tokenizer(
140
+ text, truncation=True, padding="longest",
141
+ max_length=self.max_length + self.PREFIX_IDX,
142
+ return_tensors="pt", padding_side="right").to(self.device)
143
+ suffix = self.tokenizer([self.SUFFIX] * len(prompts),
144
+ return_tensors="pt").to(self.device)
145
+ ids = torch.cat([inputs["input_ids"], suffix["input_ids"]], dim=1)
146
+ mask = torch.cat([inputs["attention_mask"].bool(),
147
+ suffix["attention_mask"].bool()], dim=1)
148
+ states = self.qwen(input_ids=ids, attention_mask=mask,
149
+ output_hidden_states=True)
150
+ hiddens = torch.stack([states.hidden_states[i]
151
+ for i in self.SELECT_LAYERS], dim=2)
152
+ return hiddens[:, self.PREFIX_IDX:], mask[:, self.PREFIX_IDX:]
153
+
154
+
155
+ class VAE(nn.Module):
156
+ """Qwen-Image VAE (f8, 16ch) with Krea-2's latent normalization."""
157
+
158
+ def __init__(self, device="cuda", dtype=torch.bfloat16):
159
+ super().__init__()
160
+ from diffusers import AutoencoderKLQwenImage
161
+
162
+ self.ae = (AutoencoderKLQwenImage
163
+ .from_pretrained("Qwen/Qwen-Image", subfolder="vae",
164
+ torch_dtype=dtype)
165
+ .to(device).eval().requires_grad_(False))
166
+ self.mean = torch.tensor(self.ae.config.latents_mean,
167
+ device=device).view(1, -1, 1, 1, 1)
168
+ self.std = torch.tensor(self.ae.config.latents_std,
169
+ device=device).view(1, -1, 1, 1, 1)
170
+
171
+ @torch.no_grad()
172
+ def encode(self, x): # (b,3,h,w) in [-1,1] -> (b,16,h/8,w/8) normalized
173
+ z = self.ae.encode(x.unsqueeze(2)).latent_dist.sample()
174
+ return ((z - self.mean) / self.std).squeeze(2)
175
+
176
+ @torch.no_grad()
177
+ def decode(self, z): # normalized latent -> (b,3,h,w) in [-1,1]
178
+ z = (z.unsqueeze(2) * self.std + self.mean).to(next(self.ae.parameters()).dtype)
179
+ return rearrange(self.ae.decode(z).sample, "b c 1 h w -> b c h w")
180
+
181
+
182
+ class DepthEstimator:
183
+ """Depth-Anything-V2-Large. Returns inverse depth in [0,1], near = 1."""
184
+
185
+ def __init__(self, device="cuda"):
186
+ from transformers import AutoImageProcessor, AutoModelForDepthEstimation
187
+
188
+ mid = "depth-anything/Depth-Anything-V2-Large-hf"
189
+ self.processor = AutoImageProcessor.from_pretrained(mid)
190
+ self.model = (AutoModelForDepthEstimation
191
+ .from_pretrained(mid, torch_dtype=torch.float16)
192
+ .to(device).eval().requires_grad_(False))
193
+ self.device = device
194
+
195
+ @torch.no_grad()
196
+ def __call__(self, image: Image.Image) -> torch.Tensor:
197
+ inputs = self.processor(images=[image], return_tensors="pt").to(self.device)
198
+ d = self.model(**inputs).predicted_depth[None].float()
199
+ d = F.interpolate(d, size=(image.height, image.width),
200
+ mode="bilinear", align_corners=False)[0, 0]
201
+ return (d - d.min()) / (d.max() - d.min() + 1e-6)
202
+
203
+
204
+ # ---------------------------------------------------------------- sampling
205
+
206
+ def pick_bucket(w, h):
207
+ ar = math.log(w / h)
208
+ return min(BUCKETS, key=lambda b: abs(math.log(b[0] / b[1]) - ar))
209
+
210
+
211
+ def resize_center_crop(img, tw, th):
212
+ w, h = img.size
213
+ s = max(tw / w, th / h)
214
+ img = img.resize((round(w * s), round(h * s)), Image.LANCZOS)
215
+ w, h = img.size
216
+ l, t = (w - tw) // 2, (h - th) // 2
217
+ return img.crop((l, t, l + tw, t + th))
218
+
219
+
220
+ def prepare(img, txtlen, patch, txtmask):
221
+ """Patchify a latent and build combined text+image position/mask tensors."""
222
+ b, _, h, w = img.shape
223
+ h_, w_ = h // patch, w // patch
224
+ ids = torch.zeros((h_, w_, 3), device=img.device)
225
+ ids[..., 1] = torch.arange(h_, device=img.device)[:, None]
226
+ ids[..., 2] = torch.arange(w_, device=img.device)[None, :]
227
+ pos = repeat(ids, "h w c -> b (h w) c", b=b)
228
+ imgmask = torch.ones(b, h_ * w_, device=img.device, dtype=torch.bool)
229
+ img = rearrange(img, "b c (h p) (w q) -> b (h w) (c p q)", p=patch, q=patch)
230
+ txtpos = torch.zeros(b, txtlen, 3, device=img.device)
231
+ return img, torch.cat((txtpos, pos), 1), torch.cat((txtmask, imgmask), 1)
232
+
233
+
234
+ def timesteps(seq_len, steps, mu=None):
235
+ """Resolution-shifted flow schedule, t: 1 -> 0."""
236
+ ts = torch.linspace(1, 0, steps + 1)
237
+ if mu is None:
238
+ slope = (MU_Y2 - MU_Y1) / (MU_X2 - MU_X1)
239
+ mu = slope * seq_len + (MU_Y1 - slope * MU_X1)
240
+ return (math.exp(mu) / (math.exp(mu) + (1.0 / ts - 1.0))).tolist()
241
+
242
+
243
+ def forward_control(model, img, ctrl, context, t, pos, mask):
244
+ """DiT forward with the depth latent concatenated on the channel dim."""
245
+ x = model.first(torch.cat([img, ctrl], dim=-1))
246
+ tv = model.tmlp(temb(t, model.config.tdim, device=x.device, dtype=x.dtype))
247
+ tvec = model.tproj(tv)
248
+
249
+ txtmask = _mask(mask[:, : context.shape[1]])
250
+ context = model.txtmlp(model.txtfusion(context, mask=txtmask))
251
+
252
+ txtlen, imglen = context.shape[1], x.shape[1]
253
+ combined = torch.cat((context, x), dim=1)
254
+ pad = (-combined.shape[1]) % 256
255
+ if pad:
256
+ combined = F.pad(combined, (0, 0, 0, pad))
257
+ mask = F.pad(mask, (0, pad), value=False)
258
+ pos = F.pad(pos, (0, 0, 0, pad))
259
+
260
+ mask, freqs = _mask(mask), model.posemb(pos)
261
+ for block in model.blocks:
262
+ combined = block(combined, tvec, freqs, mask)
263
+ return model.last(combined, tv)[:, txtlen: txtlen + imglen]
264
+
265
+
266
+ class DepthLoRAPipeline:
267
+ def __init__(self, base_ckpt, lora_ckpt, rank=64, lora_scale=1.0, device="cuda"):
268
+ self.device = device
269
+ self.model = build_model(base_ckpt, lora_ckpt, rank, lora_scale, device)
270
+ self.text = TextConditioner(device=device)
271
+ self.vae = VAE(device=device)
272
+ self.depth = DepthEstimator(device=device)
273
+
274
+ @torch.no_grad()
275
+ def __call__(self, image: Image.Image, prompt: str = "", steps: int = 8,
276
+ cfg: float = 0.0, mu: float | None = None, seed: int = 0):
277
+ """Returns (output PIL, depth PIL). Turbo: steps=8 cfg=0 mu=1.15;
278
+ Raw: steps=28-52 cfg=3.5 mu=None."""
279
+ bw, bh = pick_bucket(*image.size)
280
+ image = resize_center_crop(image.convert("RGB"), bw, bh)
281
+
282
+ d = self.depth(image)
283
+ depth_img = Image.fromarray((d.cpu().numpy() * 255).astype(np.uint8))
284
+ depth_rgb = (d[None, None].repeat(1, 3, 1, 1).to(self.device) * 2 - 1)
285
+ ctrl_lat = self.vae.encode(depth_rgb.to(torch.bfloat16))
286
+
287
+ patch = self.model.config.patch
288
+ noise = torch.randn(ctrl_lat.shape, device=self.device, dtype=torch.bfloat16,
289
+ generator=torch.Generator(self.device).manual_seed(seed))
290
+
291
+ txt, tmask = self.text([prompt])
292
+ x, pos, mask = prepare(noise, txt.shape[1], patch, tmask)
293
+ ctrl, _, _ = prepare(ctrl_lat.to(torch.bfloat16), txt.shape[1], patch, tmask)
294
+ if cfg > 0:
295
+ untxt, unmask_t = self.text([""])
296
+ _, unpos, unmask = prepare(noise, untxt.shape[1], patch, unmask_t)
297
+
298
+ ts = timesteps(x.shape[1], steps, mu)
299
+ img = x
300
+ for tc, tp in zip(ts[:-1], ts[1:]):
301
+ t = torch.full((1,), tc, dtype=img.dtype, device=self.device)
302
+ v = forward_control(self.model, img, ctrl, txt, t, pos, mask)
303
+ if cfg > 0:
304
+ un = forward_control(self.model, img, ctrl, untxt, t, unpos, unmask)
305
+ v = v + cfg * (v - un)
306
+ img = img + (tp - tc) * v
307
+
308
+ h, w = ctrl_lat.shape[-2:]
309
+ img = rearrange(img, "b (h w) (c p q) -> b c (h p) (w q)",
310
+ p=patch, q=patch, h=h // patch, w=w // patch)
311
+ px = (self.vae.decode(img).clamp(-1, 1) * 0.5 + 0.5) * 255
312
+ out = Image.fromarray(px[0].permute(1, 2, 0).float().cpu().byte().numpy())
313
+ return out, depth_img
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ torchvision
2
+ transformers>=5.0
3
+ diffusers>=0.36
4
+ accelerate
5
+ safetensors
6
+ einops
7
+ sentencepiece
8
+ pillow
9
+ numpy