Files changed (3) hide show
  1. app.py +108 -4
  2. audio_dit.py +137 -0
  3. requirements.txt +1 -0
app.py CHANGED
@@ -1,5 +1,6 @@
1
  from __future__ import annotations
2
 
 
3
  import tempfile
4
  import os
5
 
@@ -7,14 +8,19 @@ os.environ.setdefault("HF_HOME", r"D:\hf-cache")
7
  os.environ.setdefault("HUGGINGFACE_HUB_CACHE", r"D:\hf-cache\hub")
8
 
9
  import gradio as gr
 
10
  import numpy as np
11
  import torch
12
  import trimesh
13
  from diffusers import AutoencoderKL
 
14
  from huggingface_hub import hf_hub_download
 
15
  from safetensors.torch import load_file
16
- from transformers import CLIPTextModel, CLIPTokenizer, T5EncoderModel, T5TokenizerFast
 
17
 
 
18
  from pixel_dit import DiT
19
  from voxel_dit import VoxelDiT
20
  from mmdit import MMDiT
@@ -50,6 +56,27 @@ vm1_weights = hf_hub_download("bench-labs/VoxelModel-v1", "model.safetensors")
50
  voxel_model = VoxelDiT().to(DEV).eval()
51
  voxel_model.load_state_dict(load_file(vm1_weights))
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  print("[boot] loading PixelModel v6...")
54
  v6_weights = hf_hub_download("bench-labs/PixelModel-v6", "model.safetensors")
55
  pixel_model_v6 = MMDiT().to(DEV).eval()
@@ -129,6 +156,56 @@ def sample_image_v6(prompt: str, steps: int, cfg: float, seed: int, progress=gr.
129
  return (((img.clamp(-1, 1) + 1) / 2).permute(0, 2, 3, 1).numpy()[0] * 255).round().astype(np.uint8)
130
 
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  def grid_to_glb(grid: np.ndarray) -> str:
133
  voxel = trimesh.voxel.VoxelGrid(encoding=grid)
134
  mesh = voxel.as_boxes()
@@ -141,8 +218,8 @@ def grid_to_glb(grid: np.ndarray) -> str:
141
  with gr.Blocks(title="BenchLabs Models") as demo:
142
  gr.Markdown(
143
  "# BenchLabs Models\n"
144
- "Two tiny diffusion models, running live on CPU, no GPU behind this Space. "
145
- "Both are under 45M trained parameters, so generation is slower than a hosted API "
146
  "but the whole model fits in a PNG image if you're curious — see the model pages linked below."
147
  )
148
  with gr.Tab("Text → Image (PixelModel v6)"):
@@ -188,6 +265,31 @@ with gr.Blocks(title="BenchLabs Models") as demo:
188
  [vox_prompt, vox_steps, vox_cfg, vox_thresh, vox_seed],
189
  )
190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  with gr.Tab("Text → Image (PixelModel v5)"):
192
  gr.Markdown(
193
  "Good at material and light: food, landscapes, skies, interiors. "
@@ -212,7 +314,9 @@ with gr.Blocks(title="BenchLabs Models") as demo:
212
 
213
  gr.Markdown(
214
  "Models: [PixelModel v5](https://huggingface.co/bench-labs/PixelModel-v5) · "
215
- "[VoxelModel v1](https://huggingface.co/bench-labs/VoxelModel-v1)"
 
 
216
  )
217
 
218
  if __name__ == "__main__":
 
1
  from __future__ import annotations
2
 
3
+ import json
4
  import tempfile
5
  import os
6
 
 
8
  os.environ.setdefault("HUGGINGFACE_HUB_CACHE", r"D:\hf-cache\hub")
9
 
10
  import gradio as gr
11
+ import librosa
12
  import numpy as np
13
  import torch
14
  import trimesh
15
  from diffusers import AutoencoderKL
16
+ from diffusers.pipelines.deprecated.audio_diffusion.mel import Mel
17
  from huggingface_hub import hf_hub_download
18
+ from PIL import Image
19
  from safetensors.torch import load_file
20
+ from transformers import AutoTokenizer, CLIPTextModel, CLIPTokenizer, T5EncoderModel, T5TokenizerFast
21
+ from transformers import ClapTextModelWithProjection
22
 
23
+ from audio_dit import AudioDiT
24
  from pixel_dit import DiT
25
  from voxel_dit import VoxelDiT
26
  from mmdit import MMDiT
 
56
  voxel_model = VoxelDiT().to(DEV).eval()
57
  voxel_model.load_state_dict(load_file(vm1_weights))
58
 
59
+ print("[boot] loading AudioModel v1...")
60
+ audio_cfg = json.load(open(hf_hub_download("bench-labs/AudioModel-v1", "config.json")))
61
+ a_dit = audio_cfg["dit"]
62
+ audio_model = AudioDiT(x_res=a_dit["x_res"], y_res=a_dit["y_res"],
63
+ text_seq_dim=a_dit["text_seq_dim"], text_pool_dim=a_dit["text_pool_dim"]).to(DEV).eval()
64
+ audio_model.load_state_dict(load_file(hf_hub_download("bench-labs/AudioModel-v1", "model_best.safetensors")))
65
+ audio_mel = Mel(x_res=a_dit["x_res"], y_res=a_dit["y_res"],
66
+ sample_rate=audio_cfg["mel"]["sample_rate"], n_fft=audio_cfg["mel"]["n_fft"],
67
+ hop_length=audio_cfg["mel"]["hop_length"], top_db=audio_cfg["mel"]["top_db"])
68
+ # Fast mel->STFT pseudo-inverse (librosa's pre-0.10 behavior). librosa 0.10+ uses an
69
+ # NNLS/L-BFGS solver here that allocates ~2GB and takes minutes on CPU for this
70
+ # 384x256 spectrogram; the pinv gives the same Griffin-Lim output in about a second.
71
+ audio_mel_pinv = np.linalg.pinv(
72
+ librosa.filters.mel(sr=audio_mel.sr, n_fft=audio_mel.n_fft, n_mels=audio_mel.y_res, dtype=np.float32)
73
+ )
74
+ # Text-only CLAP tower: runs the same text_model + text_projection the reference
75
+ # sample.py uses, but skips the unused HTSAT audio tower (~150M params of dead weight).
76
+ audio_tokenizer = AutoTokenizer.from_pretrained("laion/clap-htsat-unfused")
77
+ audio_clap = ClapTextModelWithProjection.from_pretrained("laion/clap-htsat-unfused").to(DEV).eval()
78
+ audio_null_seq = audio_null_pool = None
79
+
80
  print("[boot] loading PixelModel v6...")
81
  v6_weights = hf_hub_download("bench-labs/PixelModel-v6", "model.safetensors")
82
  pixel_model_v6 = MMDiT().to(DEV).eval()
 
156
  return (((img.clamp(-1, 1) + 1) / 2).permute(0, 2, 3, 1).numpy()[0] * 255).round().astype(np.uint8)
157
 
158
 
159
+ AUDIO_MAX_TOKENS = 32
160
+
161
+
162
+ @torch.no_grad()
163
+ def encode_audio(strings):
164
+ t = audio_tokenizer(strings, padding="max_length", max_length=AUDIO_MAX_TOKENS,
165
+ truncation=True, return_tensors="pt").to(DEV)
166
+ out = audio_clap(**t)
167
+ return out.last_hidden_state.float(), out.text_embeds.float()
168
+
169
+
170
+ def spectrogram_to_audio(mel, img):
171
+ """Invert a mel spectrogram image back to audio (Griffin-Lim, 32 iters).
172
+
173
+ Same 0..255 -> dB mapping as diffusers' Mel.image_to_audio, with the fast
174
+ pseudo-inverse mel->STFT step (see the boot comment on audio_mel_pinv).
175
+ """
176
+ log_S = (np.frombuffer(img.tobytes(), dtype="uint8").reshape((img.height, img.width)).astype(np.float32)
177
+ * mel.top_db / 255 - mel.top_db)
178
+ power = librosa.db_to_power(log_S)
179
+ stft_mag = np.clip(audio_mel_pinv @ power, 0, None) ** 0.5 # power=2.0 -> magnitude
180
+ audio = librosa.griffinlim(stft_mag, n_iter=mel.n_iter, hop_length=mel.hop_length,
181
+ n_fft=mel.n_fft, window="hann")
182
+ peak = np.abs(audio).max()
183
+ return audio if peak == 0 else audio / peak * 0.9
184
+
185
+
186
+ @torch.no_grad()
187
+ def sample_audio(prompt: str, steps: int, cfg: float, seed: int, progress=gr.Progress()):
188
+ global audio_null_seq, audio_null_pool
189
+ if not prompt.strip():
190
+ raise gr.Error("Type a prompt first.")
191
+ steps = int(steps)
192
+ g = torch.Generator(device=DEV).manual_seed(int(seed))
193
+ seq, pool = encode_audio([prompt])
194
+ if audio_null_seq is None:
195
+ audio_null_seq, audio_null_pool = encode_audio([""])
196
+ x = torch.randn(1, 1, audio_model.y_res, audio_model.x_res, device=DEV, generator=g)
197
+ dt = 1.0 / steps
198
+ for i in progress.tqdm(range(steps), desc="sampling"):
199
+ t = torch.full((1,), i * dt, device=DEV)
200
+ vc = audio_model(x, t, seq, pool)
201
+ vu = audio_model(x, t, audio_null_seq, audio_null_pool)
202
+ x = x + (vu + cfg * (vc - vu)) * dt
203
+ row = x[0, 0].clamp(-1, 1).float().cpu().numpy()
204
+ img = Image.fromarray(((row + 1) * 127.5 + 0.5).astype(np.uint8))
205
+ audio = spectrogram_to_audio(audio_mel, img)
206
+ return audio_mel.get_sample_rate(), audio
207
+
208
+
209
  def grid_to_glb(grid: np.ndarray) -> str:
210
  voxel = trimesh.voxel.VoxelGrid(encoding=grid)
211
  mesh = voxel.as_boxes()
 
218
  with gr.Blocks(title="BenchLabs Models") as demo:
219
  gr.Markdown(
220
  "# BenchLabs Models\n"
221
+ "Three tiny diffusion models, running live on CPU, no GPU behind this Space. "
222
+ "All are under 45M trained parameters, so generation is slower than a hosted API "
223
  "but the whole model fits in a PNG image if you're curious — see the model pages linked below."
224
  )
225
  with gr.Tab("Text → Image (PixelModel v6)"):
 
265
  [vox_prompt, vox_steps, vox_cfg, vox_thresh, vox_seed],
266
  )
267
 
268
+ with gr.Tab("Text → Audio (AudioModel v1)"):
269
+ gr.Markdown(
270
+ "17.8 seconds of sound at 22 kHz from a text prompt. The same tiny DiT + "
271
+ "rectified-flow recipe as the other tabs, applied to a mel spectrogram "
272
+ "image instead of pixels. Conditioned by the CLAP text tower; audio comes "
273
+ "back via Griffin-Lim (32 iterations), so expect lo-fi, slightly phasey "
274
+ "sound — there's no learned vocoder in v1."
275
+ )
276
+ with gr.Row():
277
+ with gr.Column():
278
+ aud_prompt = gr.Textbox(label="Prompt", placeholder="a dog barking")
279
+ aud_steps = gr.Slider(10, 100, value=50, step=5, label="Detail (sampling steps)")
280
+ aud_cfg = gr.Slider(1.0, 10.0, value=4.0, step=0.5, label="Prompt strength (CFG)")
281
+ aud_seed = gr.Number(value=0, precision=0, label="Seed")
282
+ aud_btn = gr.Button("Generate audio", variant="primary")
283
+ with gr.Column():
284
+ aud_out = gr.Audio(label="Result")
285
+ aud_btn.click(sample_audio, [aud_prompt, aud_steps, aud_cfg, aud_seed], aud_out)
286
+ gr.Examples(
287
+ [["a dog barking", 50, 4.0, 0],
288
+ ["rain falling on a roof", 50, 4.0, 0],
289
+ ["footsteps on gravel", 50, 4.0, 0]],
290
+ [aud_prompt, aud_steps, aud_cfg, aud_seed],
291
+ )
292
+
293
  with gr.Tab("Text → Image (PixelModel v5)"):
294
  gr.Markdown(
295
  "Good at material and light: food, landscapes, skies, interiors. "
 
314
 
315
  gr.Markdown(
316
  "Models: [PixelModel v5](https://huggingface.co/bench-labs/PixelModel-v5) · "
317
+ "[PixelModel v6](https://huggingface.co/bench-labs/PixelModel-v6) · "
318
+ "[VoxelModel v1](https://huggingface.co/bench-labs/VoxelModel-v1) · "
319
+ "[AudioModel v1](https://huggingface.co/bench-labs/AudioModel-v1)"
320
  )
321
 
322
  if __name__ == "__main__":
audio_dit.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ import numpy as np
5
+ import torch
6
+ import torch.nn as nn
7
+ import torch.nn.functional as F
8
+
9
+ def modulate(x, shift, scale):
10
+ return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
11
+
12
+ def timestep_embedding(t, dim, max_period=10000):
13
+ half = dim // 2
14
+ freqs = torch.exp(-math.log(max_period) * torch.arange(half, device=t.device) / half)
15
+ args = t[:, None].float() * freqs[None]
16
+ emb = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
17
+ if dim % 2:
18
+ emb = torch.cat([emb, torch.zeros_like(emb[:, :1])], dim=-1)
19
+ return emb
20
+
21
+ def sincos_2d(dim, grid_h, grid_w):
22
+ assert dim % 4 == 0
23
+ gy = np.arange(grid_h, dtype=np.float32)
24
+ gx = np.arange(grid_w, dtype=np.float32)
25
+ gyy, gxx = np.meshgrid(gy, gx, indexing="ij")
26
+ d4 = dim // 4
27
+ omega = 1.0 / (10000 ** (np.arange(d4, dtype=np.float32) / d4))
28
+ def emb1(p):
29
+ out = p.reshape(-1)[:, None] * omega[None]
30
+ return np.concatenate([np.sin(out), np.cos(out)], axis=1)
31
+ pe = np.concatenate([emb1(gyy), emb1(gxx)], axis=1)
32
+ return torch.from_numpy(pe).float()
33
+
34
+ class Attention(nn.Module):
35
+ def __init__(self, dim, heads):
36
+ super().__init__()
37
+ self.heads = heads
38
+ self.q = nn.Linear(dim, dim)
39
+ self.kv = nn.Linear(dim, dim * 2)
40
+ self.proj = nn.Linear(dim, dim)
41
+
42
+ def forward(self, x, ctx=None):
43
+ ctx = x if ctx is None else ctx
44
+ B, N, C = x.shape
45
+ M = ctx.shape[1]
46
+ h = self.heads
47
+ q = self.q(x).reshape(B, N, h, C // h).transpose(1, 2)
48
+ kv = self.kv(ctx).reshape(B, M, 2, h, C // h).permute(2, 0, 3, 1, 4)
49
+ k, v = kv[0], kv[1]
50
+ o = F.scaled_dot_product_attention(q, k, v)
51
+ o = o.transpose(1, 2).reshape(B, N, C)
52
+ return self.proj(o)
53
+
54
+ class Block(nn.Module):
55
+ def __init__(self, dim, heads, mlp_ratio=4.0):
56
+ super().__init__()
57
+ self.norm1 = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
58
+ self.attn = Attention(dim, heads)
59
+ self.norm_ca = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
60
+ self.cross = Attention(dim, heads)
61
+ self.norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
62
+ hidden = int(dim * mlp_ratio)
63
+ self.mlp = nn.Sequential(nn.Linear(dim, hidden), nn.GELU(approximate="tanh"),
64
+ nn.Linear(hidden, dim))
65
+ self.ada = nn.Sequential(nn.SiLU(), nn.Linear(dim, 6 * dim))
66
+ self.cross_gate = nn.Parameter(torch.zeros(1))
67
+
68
+ def forward(self, x, c, text):
69
+ shift1, scale1, gate1, shift2, scale2, gate2 = self.ada(c).chunk(6, dim=1)
70
+ x = x + gate1.unsqueeze(1) * self.attn(modulate(self.norm1(x), shift1, scale1))
71
+ x = x + self.cross_gate * self.cross(self.norm_ca(x), text)
72
+ x = x + gate2.unsqueeze(1) * self.mlp(modulate(self.norm2(x), shift2, scale2))
73
+ return x
74
+
75
+ class AudioDiT(nn.Module):
76
+ def __init__(self, mel_ch=1, x_res=384, y_res=256, patch=16, dim=384, depth=12,
77
+ heads=6, text_seq_dim=768, text_pool_dim=512, mlp_ratio=4.0):
78
+ super().__init__()
79
+ assert x_res % patch == 0 and y_res % patch == 0
80
+ self.mel_ch = mel_ch
81
+ self.x_res = x_res
82
+ self.y_res = y_res
83
+ self.patch = patch
84
+ self.grid_h = y_res // patch
85
+ self.grid_w = x_res // patch
86
+ self.patch_dim = mel_ch * patch * patch
87
+ self.x_embed = nn.Linear(self.patch_dim, dim)
88
+ self.register_buffer("pos", sincos_2d(dim, self.grid_h, self.grid_w).unsqueeze(0))
89
+ self.t_mlp = nn.Sequential(nn.Linear(dim, dim), nn.SiLU(), nn.Linear(dim, dim))
90
+ self.text_proj = nn.Linear(text_seq_dim, dim)
91
+ self.text_pool = nn.Linear(text_pool_dim, dim)
92
+ self.blocks = nn.ModuleList([Block(dim, heads, mlp_ratio) for _ in range(depth)])
93
+ self.norm_out = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
94
+ self.ada_out = nn.Sequential(nn.SiLU(), nn.Linear(dim, 2 * dim))
95
+ self.head = nn.Linear(dim, self.patch_dim)
96
+ self.dim = dim
97
+ self._init()
98
+
99
+ def _init(self):
100
+ for m in self.modules():
101
+ if isinstance(m, nn.Linear):
102
+ nn.init.xavier_uniform_(m.weight)
103
+ if m.bias is not None:
104
+ nn.init.zeros_(m.bias)
105
+ for b in self.blocks:
106
+ nn.init.zeros_(b.ada[-1].weight); nn.init.zeros_(b.ada[-1].bias)
107
+ nn.init.zeros_(self.ada_out[-1].weight); nn.init.zeros_(self.ada_out[-1].bias)
108
+ nn.init.zeros_(self.head.weight); nn.init.zeros_(self.head.bias)
109
+
110
+ def patchify(self, x):
111
+ B, C, H, W = x.shape
112
+ p = self.patch
113
+ x = x.reshape(B, C, H // p, p, W // p, p)
114
+ x = x.permute(0, 2, 4, 1, 3, 5).reshape(B, (H // p) * (W // p), C * p * p)
115
+ return x
116
+
117
+ def unpatchify(self, x):
118
+ B, N, _ = x.shape
119
+ p = self.patch
120
+ gh, gw = self.grid_h, self.grid_w
121
+ C = self.mel_ch
122
+ x = x.reshape(B, gh, gw, C, p, p).permute(0, 3, 1, 4, 2, 5)
123
+ return x.reshape(B, C, gh * p, gw * p)
124
+
125
+ def forward(self, x, t, text_seq, text_pool):
126
+ x = self.x_embed(self.patchify(x)) + self.pos
127
+ c = self.t_mlp(timestep_embedding(t, self.dim)) + self.text_pool(text_pool)
128
+ text = self.text_proj(text_seq)
129
+ for blk in self.blocks:
130
+ x = blk(x, c, text)
131
+ shift, scale = self.ada_out(c).chunk(2, dim=1)
132
+ x = modulate(self.norm_out(x), shift, scale)
133
+ x = self.head(x)
134
+ return self.unpatchify(x)
135
+
136
+ def num_params(self):
137
+ return sum(p.numel() for p in self.parameters())
requirements.txt CHANGED
@@ -11,3 +11,4 @@ scipy
11
  audioop-lts
12
  sentencepiece
13
  accelerate
 
 
11
  audioop-lts
12
  sentencepiece
13
  accelerate
14
+ librosa