multimodalart HF Staff commited on
Commit
d661d4a
·
verified ·
1 Parent(s): 0340b8f

Restore working r2v app (revert probe)

Browse files
Files changed (3) hide show
  1. README.md +41 -4
  2. app.py +451 -33
  3. requirements.txt +31 -0
README.md CHANGED
@@ -1,14 +1,51 @@
1
  ---
2
- title: Bernini Diffusers V2 Demo
3
  emoji: 🗿
4
  colorFrom: yellow
5
  colorTo: purple
6
  sdk: gradio
7
  sdk_version: 6.15.0
8
- python_version: "3.10"
9
  app_file: app.py
10
  pinned: false
11
  license: apache-2.0
12
- short_description: Probe
13
- startup_duration_timeout: 3h
 
 
 
14
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Bernini Diffusers v2
3
  emoji: 🗿
4
  colorFrom: yellow
5
  colorTo: purple
6
  sdk: gradio
7
  sdk_version: 6.15.0
 
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
+ short_description: Reference-to-video with ByteDance Bernini-Diffusers-v2
12
+ python_version: "3.12"
13
+ startup_duration_timeout: 2h
14
+ models:
15
+ - ByteDance/Bernini-Diffusers-v2
16
  ---
17
+
18
+ # Bernini-Diffusers-v2 — reference-to-video
19
+
20
+ Give it a handful of **reference images** (a subject, an outfit, a prop, a scene…) and a prompt
21
+ that points at them as `image0`, `image1`, … Bernini's Qwen2.5-VL planner reads the references
22
+ together with the instruction and *plans* a target visual embedding with a flow-matching head; the
23
+ Wan2.2-A14B MoE renderer (two 14 B DiTs, high-noise + low-noise) turns that plan into a video.
24
+
25
+ - Model: [`ByteDance/Bernini-Diffusers-v2`](https://huggingface.co/ByteDance/Bernini-Diffusers-v2)
26
+ - Code: [`bytedance/Bernini`](https://github.com/bytedance/Bernini)
27
+
28
+ ## What this Space runs
29
+
30
+ The `r2v` (reference-to-video) task, matching the authors' `scripts/bernini_v2/run_r2v.sh`
31
+ one-for-one: `guidance_mode=vae_txt_vit_wapg`, `omega_txt=4.5`, `omega_tgt=1.5`, `omega_img=3.0`,
32
+ `omega_vid=1.0`, `omega_scale=0.75`, `planning_step=50`, `vit_denoising_step=1`, `vit_txt_cfg=1.2`,
33
+ `vit_img_cfg=1.0`, `flow_shift=5.0`, `max_image_size=842`, 16 fps, and the same system / negative
34
+ prompt.
35
+
36
+ The only deviation is the default clip length and step count (33 frames / 16 steps instead of
37
+ 81 / 40), so a generation fits inside a single ZeroGPU slot — both are sliders under
38
+ **Advanced settings**. At the defaults a video takes about 4 minutes.
39
+
40
+ The released checkpoint is fp32 (~180 GB); it is loaded in bf16, which is the dtype the reference
41
+ pipeline computes in anyway.
42
+
43
+ ## Credits
44
+
45
+ The `bernini/` package and the `veomni/` subset shipped alongside `app.py` are vendored from
46
+ [`bytedance/Bernini`](https://github.com/bytedance/Bernini) and
47
+ [`ByteDance-Seed/VeOmni`](https://github.com/ByteDance-Seed/VeOmni) (v0.1.11), both Apache-2.0,
48
+ because both declare `requires-python` ranges that exclude this runtime.
49
+
50
+ The example reference images in `examples/` are the authors' own r2v test case assets from
51
+ `bytedance/Bernini` (Apache-2.0).
app.py CHANGED
@@ -1,36 +1,454 @@
1
- import os, shutil, subprocess, gradio as gr, spaces, torch
2
 
3
- def sh(c):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  try:
5
- return subprocess.run(c, shell=True, capture_output=True, text=True, timeout=60).stdout
6
- except Exception as e:
7
- return repr(e)
8
-
9
- INFO = []
10
- INFO.append("== df -h ==\n" + sh("df -h"))
11
- INFO.append("== free -g ==\n" + sh("free -g"))
12
- INFO.append("== nproc ==\n" + sh("nproc"))
13
- INFO.append("== HOME/cwd ==\n" + os.path.expanduser("~") + " " + os.getcwd())
14
- INFO.append("== env HF ==\n" + "\n".join(f"{k}={v}" for k, v in os.environ.items() if "HF" in k or "CACHE" in k or "TMP" in k))
15
- print("\n\n".join(INFO), flush=True)
16
-
17
- @spaces.GPU(duration=60)
18
- def probe(size_label):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  out = []
20
- out.append(sh("nvidia-smi"))
21
- free, total = torch.cuda.mem_get_info()
22
- out.append(f"torch VRAM free={free/2**30:.1f}GiB total={total/2**30:.1f}GiB")
23
- out.append(torch.cuda.get_device_name(0))
24
- out.append("torch " + torch.__version__)
25
- out.append(sh("df -h"))
26
- out.append(sh("free -g"))
27
- return "\n\n".join(out)
28
-
29
- with gr.Blocks(theme=gr.themes.Citrus()) as demo:
30
- gr.Markdown("probe")
31
- b = gr.Button("probe")
32
- t = gr.Textbox(lines=30)
33
- gr.Markdown("\n\n".join(INFO))
34
- b.click(probe, gr.Textbox(value="x", visible=False), t)
35
-
36
- demo.queue().launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bernini-Diffusers-v2 reference-to-video (subject-to-video) demo.
2
 
3
+ Bernini couples a Qwen2.5-VL planner (which reads the reference images and the
4
+ instruction, then *plans* a target visual embedding with a flow-matching head)
5
+ to a Wan2.2-A14B MoE renderer (two 14B DiTs, high-noise + low-noise).
6
+
7
+ This Space mirrors the authors' own ``scripts/bernini_v2/run_r2v.sh`` /
8
+ ``gradio_demo.py`` single-GPU path 1:1 (same guidance mode, omegas, planning
9
+ steps, system prompt and negative prompt); only the frame count / step count
10
+ defaults are lowered so a generation fits inside a ZeroGPU slot.
11
+ """
12
+
13
+ import os
14
+
15
+ os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
16
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
17
+ os.environ.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1")
18
+
19
+ import spaces # noqa: E402 (must precede torch / CUDA touching imports)
20
+
21
+ import gc # noqa: E402
22
+ import logging # noqa: E402
23
+ import random # noqa: E402
24
+ import tempfile # noqa: E402
25
+ import time # noqa: E402
26
+
27
+ import gradio as gr # noqa: E402
28
+ import torch # noqa: E402
29
+ from huggingface_hub import hf_hub_download, snapshot_download # noqa: E402
30
+
31
+ logging.basicConfig(level=logging.INFO, format="[%(asctime)s] %(name)s: %(message)s")
32
+ logging.getLogger("bernini.pipeline").setLevel(logging.INFO)
33
+
34
+ MODEL_ID = "ByteDance/Bernini-Diffusers-v2"
35
+
36
+
37
+ def _stat(tag):
38
+ import shutil
39
+
40
+ du = shutil.disk_usage("/tmp")
41
+ rss = 0
42
  try:
43
+ with open("/proc/self/status") as f:
44
+ for line in f:
45
+ if line.startswith("VmRSS"):
46
+ rss = int(line.split()[1]) / 1e6
47
+ except Exception:
48
+ pass
49
+ print(f"[stat] {tag}: rss={rss:.1f}GB disk_used={du.used / 1e9:.1f}GB "
50
+ f"free={du.free / 1e9:.1f}GB", flush=True)
51
+
52
+
53
+ # ---------------------------------------------------------------- weights ---
54
+ # The released checkpoint is fp32: `bernini/` alone is 180 GB, which blows past
55
+ # the Space's 150 GB disk quota. So only the small components are materialised
56
+ # up-front; the 38 big shards are streamed one at a time, cast to bf16 straight
57
+ # into a meta-initialised model, and deleted immediately. bf16 is the dtype the
58
+ # reference pipeline computes in anyway (`BerniniPipeline.weight_dtype`), so
59
+ # nothing is lost. Peak disk for the shard stream is one shard (~5 GB).
60
+ #
61
+ # `mllm/*.safetensors` is skipped too: config.json sets `scratch_mllm: true`, so
62
+ # the MLLM is built from config and filled from the `bernini/` shards.
63
+ MODEL_DIR = snapshot_download(
64
+ MODEL_ID,
65
+ allow_patterns=[
66
+ "config.json",
67
+ "transformer_config.json",
68
+ "transformer_2_config.json",
69
+ "scheduler/*",
70
+ "vae/*",
71
+ "t5_text_encoder/*",
72
+ "t5_tokenizer/*",
73
+ "mllm/*.json",
74
+ "mllm/*.txt",
75
+ "mllm/*.model",
76
+ ],
77
+ max_workers=8,
78
+ )
79
+ _stat("after small snapshot")
80
+
81
+ # ------------------------------------------------------------------ model ---
82
+ import json # noqa: E402
83
+
84
+ from accelerate import init_empty_weights # noqa: E402
85
+ from safetensors import safe_open # noqa: E402
86
+
87
+ from bernini.models import BerniniConfig, BerniniModel # noqa: E402
88
+ from bernini.pipeline import BerniniPipeline, _localize_bernini_config # noqa: E402
89
+ from diffusers.models import AutoencoderKLWan # noqa: E402
90
+ from transformers import AutoProcessor, AutoTokenizer # noqa: E402
91
+
92
+ config = BerniniConfig.from_pretrained(
93
+ MODEL_DIR,
94
+ use_unipc=True,
95
+ use_src_id_rotary_emb=True,
96
+ interpolate_src_id=True,
97
+ max_trained_src_id=5,
98
+ )
99
+ _localize_bernini_config(config, MODEL_DIR)
100
+ config.mllm_attn_implementation = "sdpa"
101
+
102
+ with init_empty_weights():
103
+ model = BerniniModel(config)
104
+ model.eval()
105
+ model.requires_grad_(False)
106
+ _stat("after meta init")
107
+
108
+ _index_path = hf_hub_download(MODEL_ID, f"{config.bernini_ckpt_subfolder}/model.safetensors.index.json")
109
+ _weight_map = json.load(open(_index_path))["weight_map"]
110
+ _shards = sorted(set(_weight_map.values()))
111
+ _pending = set(_weight_map)
112
+
113
+ for _i, _shard in enumerate(_shards, 1):
114
+ _p = hf_hub_download(MODEL_ID, f"{config.bernini_ckpt_subfolder}/{_shard}")
115
+ _sd = {}
116
+ with safe_open(_p, framework="pt", device="cpu") as _f:
117
+ for _k in _f.keys():
118
+ _t = _f.get_tensor(_k)
119
+ _sd[_k] = _t.to(torch.bfloat16) if _t.is_floating_point() else _t
120
+ del _t
121
+ model.load_state_dict(_sd, strict=False, assign=True)
122
+ _pending -= set(_sd)
123
+ del _sd
124
+ for _f2 in {os.path.realpath(_p), _p}:
125
+ try:
126
+ os.remove(_f2)
127
+ except OSError:
128
+ pass
129
+ gc.collect()
130
+ print(f"[load] shard {_i}/{len(_shards)} {_shard}", flush=True)
131
+
132
+ _stat("after shard stream")
133
+ _meta = [n for n, p in model.named_parameters() if p.device.type == "meta"]
134
+ if _meta:
135
+ print(f"[load] WARNING {len(_meta)} params still on meta, e.g. {_meta[:8]}", flush=True)
136
+ if _pending:
137
+ print(f"[load] WARNING {len(_pending)} checkpoint keys unconsumed, e.g. {sorted(_pending)[:8]}", flush=True)
138
+
139
+ # transformer_2 is loaded inside diff_dec_low and attached back before sampling
140
+ setattr(model.diff_dec, "transformer_2", model.diff_dec_low.transformer_2)
141
+
142
+ t5_tokenizer = AutoTokenizer.from_pretrained(
143
+ config.t5_tokenizer_path, subfolder=config.t5_tokenizer_subfolder, trust_remote_code=True
144
+ )
145
+ vit_processor = AutoProcessor.from_pretrained(
146
+ config.processor_config_path,
147
+ subfolder=config.processor_subfolder,
148
+ padding_side="right",
149
+ trust_remote_code=True,
150
+ )
151
+ vae = AutoencoderKLWan.from_pretrained(
152
+ config.vae_model_path, subfolder=config.vae_subfolder, torch_dtype=torch.float32
153
+ )
154
+ vae.eval()
155
+ vae.requires_grad_(False)
156
+
157
+ PIPE = BerniniPipeline(config, model, vae, t5_tokenizer, vit_processor, "cuda")
158
+
159
+ # The two 14B renderer DiTs (~56 GB bf16) live on the GPU for the whole life of
160
+ # the Space. The planner stack (MLLM / connector / vit head / T5 / VAE) is much
161
+ # smaller and the reference pipeline moves it on and off the device around its
162
+ # own phases, so it is left where that code expects to find it.
163
+ model.diff_dec.transformer.to("cuda")
164
+ model.diff_dec.transformer_2.to("cuda")
165
+ gc.collect()
166
+ _stat("after DiTs -> cuda")
167
+
168
+ # ------------------------------------------------------------------- task ---
169
+ # Verbatim from scripts/bernini_v2/run_r2v.sh
170
+ SYSTEM_PROMPT = "You are a helpful assistant specialized in subject-to-video generation."
171
+ NEG_PROMPT = (
172
+ "vivid tones, overexposed, static, blurry details, subtitles, style, artwork, painting, "
173
+ "image, motionless, overall grayish, worst quality, low quality, JPEG compression artifacts, "
174
+ "ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn face, deformed, disfigured, "
175
+ "malformed limbs, fused fingers, still frame, cluttered background, three legs, "
176
+ "too many people in the background, walking backwards"
177
+ )
178
+ R2V = dict(
179
+ guidance_mode="vae_txt_vit_wapg",
180
+ max_image_size=842,
181
+ flow_shift=5.0,
182
+ fps=16,
183
+ omega_txt=4.5,
184
+ omega_tgt=1.5,
185
+ omega_img=3.0,
186
+ omega_vid=1.0,
187
+ omega_scale=0.75,
188
+ planning_step=50,
189
+ vit_denoising_step=1,
190
+ vit_txt_cfg=1.2,
191
+ vit_img_cfg=1.0,
192
+ eta=0.5,
193
+ momentum=0.0,
194
+ norm_threshold=(50.0, 50.0, 50.0),
195
+ )
196
+
197
+ RESOLUTIONS = {
198
+ "Landscape · 848×480": (480, 848),
199
+ "Portrait · 480×848": (848, 480),
200
+ "Square · 640×640": (640, 640),
201
+ }
202
+ MAX_SEED = 2**31 - 1
203
+
204
+
205
+ def _coerce_gallery_paths(gallery_input):
206
+ """gr.Gallery hands back a list of (path, caption) tuples."""
207
+ if not gallery_input:
208
+ return None
209
  out = []
210
+ for item in gallery_input:
211
+ if isinstance(item, (list, tuple)) and item:
212
+ item = item[0]
213
+ if isinstance(item, str):
214
+ out.append(item)
215
+ elif isinstance(item, dict) and item.get("path"):
216
+ out.append(item["path"])
217
+ elif hasattr(item, "name"):
218
+ out.append(item.name)
219
+ return out or None
220
+
221
+
222
+ def _estimate(*args, **kwargs):
223
+ """Runtime scales with (denoising steps x latent tokens)."""
224
+ try:
225
+ n_images = max(1, len(args[0] or []))
226
+ num_frames = int(args[2])
227
+ steps = int(args[3])
228
+ resolution = args[4]
229
+ except Exception:
230
+ return 420
231
+ height, width = RESOLUTIONS.get(resolution, (480, 848))
232
+ latent_frames = (int(num_frames) - 1) // 4 + 1
233
+ tokens = latent_frames * (height // 16) * (width // 16)
234
+ # Fitted on this Space (33f/848x480/16 steps unless noted):
235
+ # 2 refs, 17f, 8 steps -> 95.1 s
236
+ # 2 refs -> 231.6 s warm / 254.3 s on a cold slot
237
+ # 5 refs -> 322.8 s
238
+ # Planning cost scales with the reference count, sampling with steps x latent tokens.
239
+ secs = 15.0 + 22.8 * n_images + 9.7e-4 * steps * tokens
240
+ return int(min(800, max(90, secs * 1.15)))
241
+
242
+
243
+ @spaces.GPU(duration=_estimate, size="xlarge")
244
+ def generate(
245
+ reference_images,
246
+ prompt,
247
+ num_frames=33,
248
+ num_inference_steps=16,
249
+ resolution="Landscape · 848×480",
250
+ seed=42,
251
+ randomize_seed=False,
252
+ negative_prompt=NEG_PROMPT,
253
+ omega_txt=4.5,
254
+ omega_img=3.0,
255
+ omega_tgt=1.5,
256
+ omega_scale=0.75,
257
+ progress=gr.Progress(track_tqdm=True),
258
+ ):
259
+ images = _coerce_gallery_paths(reference_images)
260
+ if not images:
261
+ raise gr.Error("Please add at least one reference image.")
262
+ if len(images) > 8:
263
+ raise gr.Error("Please use at most 8 reference images.")
264
+ if not prompt or not prompt.strip():
265
+ raise gr.Error("Please write a prompt describing the video you want.")
266
+
267
+ if randomize_seed:
268
+ seed = random.randint(0, MAX_SEED)
269
+ height, width = RESOLUTIONS[resolution]
270
+
271
+ out_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
272
+ kwargs = dict(R2V)
273
+ kwargs.update(
274
+ omega_txt=float(omega_txt),
275
+ omega_img=float(omega_img),
276
+ omega_tgt=float(omega_tgt),
277
+ omega_scale=float(omega_scale),
278
+ )
279
+
280
+ t0 = time.perf_counter()
281
+ PIPE(
282
+ "r2v",
283
+ prompt.strip(),
284
+ images=images,
285
+ neg_prompt=negative_prompt or "",
286
+ system_prompt=SYSTEM_PROMPT,
287
+ num_frames=int(num_frames),
288
+ height=int(height),
289
+ width=int(width),
290
+ num_inference_steps=int(num_inference_steps),
291
+ seed=int(seed),
292
+ output_path=out_path,
293
+ **kwargs,
294
+ )
295
+ elapsed = time.perf_counter() - t0
296
+ torch.cuda.empty_cache()
297
+ print(f"[bernini] generated in {elapsed:.1f}s "
298
+ f"({num_frames}f {width}x{height} {num_inference_steps} steps)", flush=True)
299
+ return out_path, int(seed)
300
+
301
+
302
+ # --------------------------------------------------------------------- UI ---
303
+ EX1_PROMPT = (
304
+ "The marble statue from image0, wearing the black T-shirt from image2, the tropical floral "
305
+ "shorts from image3, and the pink cat-ear headphones from image1, sits on the wooden bench in "
306
+ "the beach sunset setting from image4, facing the camera and gently bobbing and swaying to the "
307
+ "music in a medium shot. Generate a video where the marble statue from image0 is the main "
308
+ "subject, with the same muscular stone body, curly sculpted hair, and classical carved "
309
+ "appearance, now humorously dressed in the black short-sleeve T-shirt from image2 with the "
310
+ 'white word "bernini" across the chest, the bright blue tropical floral shorts from image3 '
311
+ "with large red, orange, and yellow flowers and green leaves, and the pink over-ear cat-ear "
312
+ "headphones from image1. He is seated on the wooden bench from image4, centered in the frame "
313
+ "and facing directly toward the camera in a medium shot. Keep the environment unchanged from "
314
+ "image4: a seaside promenade with the wooden bench in the foreground, sandy beach and calm "
315
+ "ocean behind it, palm trees rising on the left, and a vivid sunset sky glowing with warm "
316
+ "orange, pink, and purple tones. He begins moving subtly and rhythmically as if listening to "
317
+ "music through the headphones, gently nodding his head, swaying his upper body slightly, and "
318
+ "rocking side to side in a natural music-driven motion, always remaining seated on the bench "
319
+ "and facing the camera."
320
+ )
321
+ EX2_PROMPT = (
322
+ "Place the male marble sculpture from image0 on the bench in image1, wearing the black T-shirt "
323
+ 'from image2 with the word "bernini" across the chest, holding the brown ceramic cup from '
324
+ "image3 and slowly drinking from it with no steam visible, always facing the camera in a fixed "
325
+ "medium shot. Keep the seaside sunset setting from image1 unchanged: the wooden bench centered "
326
+ "on a paved path, palm trees on the left, and the beach, ocean and glowing sun in the "
327
+ "background under a pink and orange sky. He starts seated upright holding the cup near his "
328
+ "torso with a subtle rhythmic sway of the shoulders, then slowly lifts the cup toward his "
329
+ "mouth in a controlled motion, gently tilts it and takes a sip, and finally lowers it while "
330
+ "continuing a soft bobbing motion of the head and torso."
331
+ )
332
+
333
+ EXAMPLES = [
334
+ [
335
+ [
336
+ "examples/source_img0.png",
337
+ "examples/source_img1.png",
338
+ "examples/source_img2.png",
339
+ "examples/source_img3.png",
340
+ "examples/source_img4.png",
341
+ ],
342
+ EX1_PROMPT,
343
+ ],
344
+ [
345
+ [
346
+ "examples/source_img0.png",
347
+ "examples/source_img4.png",
348
+ "examples/source_img2.png",
349
+ "examples/source_img7.png",
350
+ ],
351
+ EX2_PROMPT,
352
+ ],
353
+ ]
354
+
355
+ CSS = """
356
+ #col-container { margin: 0 auto; max-width: 1100px; }
357
+ """
358
+
359
+ with gr.Blocks(title="Bernini-Diffusers-v2") as demo:
360
+ with gr.Column(elem_id="col-container"):
361
+ gr.Markdown(
362
+ """
363
+ # Bernini-Diffusers-v2 — reference-to-video
364
+
365
+ Drop in a few **reference images** (a subject, an outfit, a prop, a scene…), then describe the
366
+ video you want while pointing at them as `image0`, `image1`, … Bernini's Qwen2.5-VL planner reads
367
+ the references plus your instruction and plans a target visual embedding, which the Wan2.2-A14B
368
+ MoE renderer turns into a video.
369
+
370
+ [model](https://huggingface.co/ByteDance/Bernini-Diffusers-v2) ·
371
+ [code](https://github.com/bytedance/Bernini)
372
+ """
373
+ )
374
+
375
+ with gr.Row():
376
+ with gr.Column(scale=1):
377
+ reference_images = gr.Gallery(
378
+ label="Reference images (order matters → image0, image1, …)",
379
+ file_types=["image"],
380
+ type="filepath",
381
+ columns=4,
382
+ height=240,
383
+ object_fit="contain",
384
+ interactive=True,
385
+ show_label=True,
386
+ )
387
+ prompt = gr.Textbox(
388
+ label="Prompt",
389
+ lines=6,
390
+ placeholder="The statue from image0, wearing the shirt from image1, sits on a "
391
+ "bench at sunset and gently sways to the music in a medium shot…",
392
+ )
393
+ run_btn = gr.Button("Generate video", variant="primary")
394
+ with gr.Column(scale=1):
395
+ video_out = gr.Video(label="Result", autoplay=True, height=380)
396
+ used_seed = gr.Number(label="Seed used", interactive=False)
397
+
398
+ with gr.Accordion("Advanced settings", open=False):
399
+ with gr.Row():
400
+ num_frames = gr.Slider(
401
+ label="Frames (16 fps)", minimum=17, maximum=49, step=4, value=33
402
+ )
403
+ num_inference_steps = gr.Slider(
404
+ label="Denoising steps", minimum=8, maximum=24, step=1, value=16
405
+ )
406
+ resolution = gr.Radio(
407
+ label="Resolution",
408
+ choices=list(RESOLUTIONS.keys()),
409
+ value="Landscape · 848×480",
410
+ )
411
+ with gr.Row():
412
+ seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42)
413
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=False)
414
+ negative_prompt = gr.Textbox(label="Negative prompt", value=NEG_PROMPT, lines=3)
415
+ gr.Markdown("Guidance weights — the defaults are the authors' `run_r2v.sh` values.")
416
+ with gr.Row():
417
+ omega_txt = gr.Slider(label="omega_txt", minimum=1.0, maximum=8.0, step=0.1, value=4.5)
418
+ omega_img = gr.Slider(label="omega_img", minimum=0.0, maximum=8.0, step=0.1, value=3.0)
419
+ omega_tgt = gr.Slider(label="omega_tgt", minimum=0.0, maximum=6.0, step=0.1, value=1.5)
420
+ omega_scale = gr.Slider(label="omega_scale", minimum=0.0, maximum=1.0, step=0.05, value=0.75)
421
+
422
+ gr.Markdown(
423
+ "Longer clips and more steps look better but cost more GPU time. The defaults "
424
+ "(33 frames ≈ 2 s at 16 fps, 16 steps) take about 4 minutes; the authors' reference "
425
+ "setting is 81 frames / 40 steps, which does not fit in a single ZeroGPU slot."
426
+ )
427
+
428
+ gr.Examples(
429
+ examples=EXAMPLES,
430
+ inputs=[reference_images, prompt],
431
+ outputs=[video_out, used_seed],
432
+ fn=generate,
433
+ cache_examples=True,
434
+ cache_mode="lazy",
435
+ label="Official Bernini r2v examples",
436
+ )
437
+
438
+ inputs = [
439
+ reference_images,
440
+ prompt,
441
+ num_frames,
442
+ num_inference_steps,
443
+ resolution,
444
+ seed,
445
+ randomize_seed,
446
+ negative_prompt,
447
+ omega_txt,
448
+ omega_img,
449
+ omega_tgt,
450
+ omega_scale,
451
+ ]
452
+ run_btn.click(fn=generate, inputs=inputs, outputs=[video_out, used_seed], api_name="generate")
453
+
454
+ demo.queue(max_size=12).launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)
requirements.txt CHANGED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- core stack (versions from the Bernini repo's own requirements.txt) ---
2
+ transformers==4.57.3
3
+ diffusers==0.35.2
4
+ accelerate
5
+ safetensors
6
+ torchvision
7
+ einops
8
+ numpy
9
+ Pillow
10
+ tqdm
11
+ ftfy
12
+ scipy
13
+ sentencepiece
14
+ packaging
15
+ psutil
16
+ hf_transfer
17
+
18
+ # --- video / image I/O ---
19
+ decord
20
+ imageio
21
+ imageio-ffmpeg
22
+
23
+ # --- veomni deps that its inference-side modules actually touch ---
24
+ # (the veomni package itself is vendored in ./veomni, Apache-2.0, v0.1.11,
25
+ # because pip-installing it drags in datasets<=2.21.0 / torchdata / wandb)
26
+
27
+ # --- FlashAttention 2 ---
28
+ # bernini/models/modeling_qwen2_5_vl.py raises at import time unless flash_attn
29
+ # is importable, and the MLLM's vision tower asks for flash_attention_2.
30
+ # sm_120 (Blackwell) prebuilt wheel, cp312 / torch 2.11:
31
+ https://huggingface.co/datasets/multimodalart/zerogpu-blackwell-wheels/resolve/main/wheels/pt211-cu130-cp312/flash_attn-2.8.3-cp312-cp312-linux_x86_64.whl