fffiloni commited on
Commit
d8916e0
·
verified ·
1 Parent(s): 8b52cdc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +841 -7
app.py CHANGED
@@ -1,3 +1,825 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  with gr.Blocks(title="Krea Realtime Video 14B") as demo:
2
  gr.Markdown(
3
  "# Krea Realtime Video 14B\n"
@@ -33,6 +855,13 @@ with gr.Blocks(title="Krea Realtime Video 14B") as demo:
33
  load_lora_btn = gr.Button("Load style", variant="secondary")
34
  disable_lora_btn = gr.Button("Base model", variant="secondary")
35
 
 
 
 
 
 
 
 
36
  with gr.Row():
37
  num_blocks = gr.Slider(
38
  minimum=1,
@@ -55,8 +884,8 @@ with gr.Blocks(title="Krea Realtime Video 14B") as demo:
55
  generate_btn = gr.Button("Generate video", variant="primary")
56
 
57
  gr.Markdown(
58
- "Use **Origami** by selecting it, clicking **Load style**, then generating. "
59
- "The trigger `[origami]` is added automatically."
60
  )
61
 
62
  with gr.Column(scale=5):
@@ -142,9 +971,6 @@ with gr.Blocks(title="Krea Realtime Video 14B") as demo:
142
  num_inference_steps,
143
  seed,
144
  ],
145
- outputs=output_video,
146
- fn=generate,
147
- cache_examples=False,
148
  )
149
 
150
  warmup_btn.click(
@@ -157,14 +983,14 @@ with gr.Blocks(title="Krea Realtime Video 14B") as demo:
157
  load_lora_btn.click(
158
  load_selected_lora,
159
  inputs=[lora_style, lora_strength],
160
- outputs=lora_status,
161
  api_name="load_lora",
162
  )
163
 
164
  disable_lora_btn.click(
165
  disable_lora,
166
  inputs=None,
167
- outputs=lora_status,
168
  api_name="disable_lora",
169
  )
170
 
@@ -187,4 +1013,12 @@ with gr.Blocks(title="Krea Realtime Video 14B") as demo:
187
  inputs=None,
188
  outputs=model_status,
189
  api_name="health",
 
 
 
 
 
 
 
 
190
  )
 
1
+ # ---------------------------------------------------------------------------
2
+ # Krea Realtime Video 14B — Hugging Face Space Demo
3
+ # ZeroGPU compatibility version for Diffusers ModularPipeline.
4
+ # ---------------------------------------------------------------------------
5
+
6
+ import os
7
+
8
+ # ---------------------------------------------------------------------------
9
+ # HF Spaces / cache configuration — must happen before HF imports
10
+ # ---------------------------------------------------------------------------
11
+
12
+ _ASF_HF_CACHE_ROOT = os.environ.get("ASF_HF_CACHE_DIR") or "/tmp/asf-hf-cache"
13
+
14
+ os.environ.setdefault("HF_HOME", _ASF_HF_CACHE_ROOT)
15
+ os.environ.setdefault("HF_HUB_CACHE", os.path.join(_ASF_HF_CACHE_ROOT, "hub"))
16
+ os.environ.setdefault("HUGGINGFACE_HUB_CACHE", os.path.join(_ASF_HF_CACHE_ROOT, "hub"))
17
+ os.environ.setdefault("TRANSFORMERS_CACHE", os.path.join(_ASF_HF_CACHE_ROOT, "transformers"))
18
+ os.environ.setdefault("DIFFUSERS_CACHE", os.path.join(_ASF_HF_CACHE_ROOT, "diffusers"))
19
+ os.environ.setdefault("HF_MODULES_CACHE", "/tmp/hf_modules")
20
+ os.environ.setdefault("MPLCONFIGDIR", "/tmp/matplotlib")
21
+
22
+ # ZeroGPU compatibility mode:
23
+ # - torch.compile is disabled below.
24
+ # - hub kernels / torchao optimized path is intentionally not used.
25
+ os.environ.setdefault("DIFFUSERS_ENABLE_HUB_KERNELS", "0")
26
+ os.environ.setdefault("USE_HUB_KERNELS", "NO")
27
+
28
+ os.makedirs(_ASF_HF_CACHE_ROOT, exist_ok=True)
29
+ os.makedirs(os.path.join(_ASF_HF_CACHE_ROOT, "hub"), exist_ok=True)
30
+ os.makedirs(os.path.join(_ASF_HF_CACHE_ROOT, "transformers"), exist_ok=True)
31
+ os.makedirs(os.path.join(_ASF_HF_CACHE_ROOT, "diffusers"), exist_ok=True)
32
+ os.makedirs(os.environ["HF_MODULES_CACHE"], exist_ok=True)
33
+ os.makedirs(os.environ["MPLCONFIGDIR"], exist_ok=True)
34
+
35
+ HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
36
+
37
+ # ---------------------------------------------------------------------------
38
+ # Safe spaces import
39
+ # ---------------------------------------------------------------------------
40
+
41
+ try:
42
+ import spaces
43
+
44
+ HAS_SPACES = True
45
+ except Exception:
46
+ HAS_SPACES = False
47
+
48
+ class _DummySpaces:
49
+ def GPU(self, *args, **kwargs):
50
+ def decorator(fn):
51
+ return fn
52
+
53
+ return decorator
54
+
55
+ spaces = _DummySpaces()
56
+
57
+
58
+ def _spaces_gpu(*args, **kwargs):
59
+ """
60
+ Wrapper around spaces.GPU.
61
+
62
+ Some versions of the spaces package may not support size=...
63
+ In that case, we fall back to the same decorator without size.
64
+ """
65
+ try:
66
+ return spaces.GPU(*args, **kwargs)
67
+ except TypeError:
68
+ kwargs.pop("size", None)
69
+ return spaces.GPU(*args, **kwargs)
70
+
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # Imports + ZeroGPU torch.compile bypass
74
+ # ---------------------------------------------------------------------------
75
+
76
+ import sys
77
+ import re
78
+ import time
79
+ import threading
80
+ import traceback
81
+ import importlib.util
82
+ import importlib.metadata
83
+
84
+ import torch
85
+
86
+ _ORIG_TORCH_COMPILE = getattr(torch, "compile", None)
87
+ _ASF_COMPILE_BYPASS_ANNOUNCED = False
88
+
89
+
90
+ def _asf_zerogpu_compile_bypass(fn=None, *args, **kwargs):
91
+ """
92
+ ZeroGPU compatibility shim.
93
+
94
+ Supports both call styles:
95
+ torch.compile(fn, ...)
96
+ @torch.compile(...)
97
+ def fn(...): ...
98
+
99
+ Default behavior is quiet because FlexAttention may call this repeatedly
100
+ during generation.
101
+ """
102
+ global _ASF_COMPILE_BYPASS_ANNOUNCED
103
+
104
+ if fn is None:
105
+ def decorator(real_fn):
106
+ return _asf_zerogpu_compile_bypass(real_fn, *args, **kwargs)
107
+
108
+ return decorator
109
+
110
+ if os.environ.get("ASF_VERBOSE_COMPILE_BYPASS", "0") == "1":
111
+ name = getattr(fn, "__name__", repr(fn))
112
+ module = getattr(fn, "__module__", "")
113
+ print(
114
+ f"[ASF] ZeroGPU compatibility: bypassing torch.compile for {module}.{name}",
115
+ flush=True,
116
+ )
117
+ elif not _ASF_COMPILE_BYPASS_ANNOUNCED:
118
+ print(
119
+ "[ASF] ZeroGPU compatibility: torch.compile bypass is active.",
120
+ flush=True,
121
+ )
122
+ _ASF_COMPILE_BYPASS_ANNOUNCED = True
123
+
124
+ return fn
125
+
126
+
127
+ if _ORIG_TORCH_COMPILE is not None and os.environ.get("ASF_ENABLE_TORCH_COMPILE", "0") != "1":
128
+ torch.compile = _asf_zerogpu_compile_bypass
129
+
130
+ try:
131
+ import torch._dynamo
132
+
133
+ torch._dynamo.config.suppress_errors = True
134
+ except Exception:
135
+ pass
136
+
137
+
138
+ def _installed_version(package_name):
139
+ try:
140
+ return importlib.metadata.version(package_name)
141
+ except Exception:
142
+ return None
143
+
144
+
145
+ def _version_tuple(version):
146
+ """
147
+ Minimal semver-ish parser.
148
+ Handles strings like 0.12.0, 0.16.0, 0.16.0.dev...
149
+ """
150
+ if not version:
151
+ return None
152
+ parts = re.findall(r"\d+", version)
153
+ if not parts:
154
+ return None
155
+ return tuple(int(p) for p in parts[:3])
156
+
157
+
158
+ PEFT_AVAILABLE = importlib.util.find_spec("peft") is not None
159
+ PEFT_VERSION = _installed_version("peft")
160
+
161
+ TORCHAO_VERSION = _installed_version("torchao")
162
+ TORCHAO_INSTALLED = TORCHAO_VERSION is not None
163
+ TORCHAO_VERSION_TUPLE = _version_tuple(TORCHAO_VERSION)
164
+
165
+ # Recent PEFT rejects torchao < 0.16.0 when torchao is installed.
166
+ # In this Space, torchao is not needed because we are not using Krea's optimized
167
+ # torch.compile / FP8 path on ZeroGPU.
168
+ TORCHAO_COMPATIBLE_FOR_PEFT = (
169
+ not TORCHAO_INSTALLED
170
+ or (
171
+ TORCHAO_VERSION_TUPLE is not None
172
+ and TORCHAO_VERSION_TUPLE >= (0, 16, 0)
173
+ )
174
+ )
175
+
176
+ LORA_BACKEND_READY = PEFT_AVAILABLE and TORCHAO_COMPATIBLE_FOR_PEFT
177
+
178
+ if not PEFT_AVAILABLE:
179
+ LORA_BACKEND_ERROR = "PEFT is not installed. Add `peft` to requirements.txt and rebuild."
180
+ elif not TORCHAO_COMPATIBLE_FOR_PEFT:
181
+ LORA_BACKEND_ERROR = (
182
+ f"Incompatible torchao version detected: {TORCHAO_VERSION}. "
183
+ "Remove `torchao==0.12.0` from requirements.txt, or upgrade torchao to >=0.16.0. "
184
+ "For this ZeroGPU compatibility Space, removing torchao is recommended."
185
+ )
186
+ else:
187
+ LORA_BACKEND_ERROR = ""
188
+
189
+
190
+ import gradio as gr
191
+
192
+ # ---------------------------------------------------------------------------
193
+ # Diffusers imports
194
+ # ---------------------------------------------------------------------------
195
+
196
+ _DIFFUSERS_OK = False
197
+ _DIFFUSERS_IMPORT_ERROR = None
198
+
199
+ try:
200
+ from diffusers import ModularPipeline
201
+ from diffusers.modular_pipelines import PipelineState
202
+ from diffusers.utils import export_to_video
203
+
204
+ _DIFFUSERS_OK = True
205
+ except Exception as e:
206
+ _DIFFUSERS_IMPORT_ERROR = f"{type(e).__name__}: {e}"
207
+ traceback.print_exc()
208
+
209
+
210
+ # ---------------------------------------------------------------------------
211
+ # Model / LoRA configuration
212
+ # ---------------------------------------------------------------------------
213
+
214
+ MODEL_ID = "krea/krea-realtime-video"
215
+
216
+ KNOWN_LORAS = {
217
+ "Base model": None,
218
+ "Origami": {
219
+ "repo_id": "shauray/Origami_WanLora",
220
+ "prefix": "diffusion_model",
221
+ "weight_name": "origami_000000500.safetensors",
222
+ "adapter_name": "origami",
223
+ "trigger": "[origami]",
224
+ },
225
+ }
226
+
227
+ _pipeline = None
228
+ _pipeline_error = None
229
+ _pipeline_lock = threading.Lock()
230
+
231
+ _loaded_loras = set()
232
+ _active_lora = None
233
+ _active_lora_label = "Base model"
234
+ _active_lora_strength = 1.0
235
+ _lora_lock = threading.Lock()
236
+
237
+
238
+ def _log(msg):
239
+ print(f"[KreaRealtimeVideo] {msg}", flush=True)
240
+
241
+
242
+ def _runtime_report():
243
+ return {
244
+ "python": sys.version.replace("\n", " "),
245
+ "torch": getattr(torch, "__version__", "unknown"),
246
+ "cuda_available": bool(torch.cuda.is_available()),
247
+ "cuda_device_count": int(torch.cuda.device_count()) if torch.cuda.is_available() else 0,
248
+ "has_spaces": HAS_SPACES,
249
+ "torch_compile_bypassed": torch.compile is _asf_zerogpu_compile_bypass,
250
+ "peft_available": PEFT_AVAILABLE,
251
+ "peft_version": PEFT_VERSION,
252
+ "torchao_installed": TORCHAO_INSTALLED,
253
+ "torchao_version": TORCHAO_VERSION,
254
+ "lora_backend_ready": LORA_BACKEND_READY,
255
+ "lora_backend_error": LORA_BACKEND_ERROR,
256
+ "hf_home": os.environ.get("HF_HOME", ""),
257
+ "hf_modules_cache": os.environ.get("HF_MODULES_CACHE", ""),
258
+ }
259
+
260
+
261
+ def _lora_report():
262
+ return {
263
+ "active_lora": _active_lora_label,
264
+ "active_adapter": _active_lora,
265
+ "active_strength": _active_lora_strength,
266
+ "loaded_loras": sorted(list(_loaded_loras)),
267
+ "available_loras": list(KNOWN_LORAS.keys()),
268
+ "backend_ready": LORA_BACKEND_READY,
269
+ "backend_error": LORA_BACKEND_ERROR,
270
+ }
271
+
272
+
273
+ def _call_from_pretrained_compat(*args, **kwargs):
274
+ """
275
+ Compatibility wrapper because some diffusers/HF Hub combinations
276
+ may use token= while older ones expect use_auth_token= or no token.
277
+ """
278
+ try:
279
+ return ModularPipeline.from_pretrained(*args, **kwargs)
280
+ except TypeError as e:
281
+ if "token" in str(e):
282
+ kwargs.pop("token", None)
283
+ if HF_TOKEN:
284
+ kwargs["use_auth_token"] = HF_TOKEN
285
+ return ModularPipeline.from_pretrained(*args, **kwargs)
286
+ raise
287
+
288
+
289
+ def _load_components_compat(pipe, **kwargs):
290
+ """
291
+ Compatibility wrapper around pipe.load_components().
292
+ """
293
+ try:
294
+ return pipe.load_components(**kwargs)
295
+ except TypeError as e:
296
+ msg = str(e)
297
+ if "token" in msg:
298
+ kwargs.pop("token", None)
299
+ if HF_TOKEN:
300
+ kwargs["use_auth_token"] = HF_TOKEN
301
+ return pipe.load_components(**kwargs)
302
+ raise
303
+
304
+
305
+ def _load_pipeline():
306
+ """
307
+ Load the ModularPipeline once at app startup.
308
+
309
+ For ZeroGPU, this gives the best UX:
310
+ - model warms up when the app starts;
311
+ - generation remains protected by @spaces.GPU;
312
+ - LoRAs can be loaded manually before generation.
313
+ """
314
+ global _pipeline, _pipeline_error
315
+
316
+ with _pipeline_lock:
317
+ if _pipeline is not None:
318
+ return _pipeline
319
+
320
+ if not _DIFFUSERS_OK:
321
+ _pipeline_error = _DIFFUSERS_IMPORT_ERROR or "Diffusers import failed"
322
+ _log(f"Pipeline load skipped: {_pipeline_error}")
323
+ return None
324
+
325
+ try:
326
+ _log(f"Runtime report: {_runtime_report()}")
327
+ _log(f"Loading ModularPipeline from {MODEL_ID} ...")
328
+
329
+ pipe = _call_from_pretrained_compat(
330
+ MODEL_ID,
331
+ trust_remote_code=True,
332
+ token=HF_TOKEN,
333
+ )
334
+
335
+ _log("Skeleton loaded; attaching components ...")
336
+
337
+ try:
338
+ _load_components_compat(
339
+ pipe,
340
+ trust_remote_code=True,
341
+ device_map="cuda",
342
+ torch_dtype={
343
+ "default": torch.bfloat16,
344
+ "vae": torch.float16,
345
+ },
346
+ token=HF_TOKEN,
347
+ )
348
+ except RuntimeError as err:
349
+ msg = str(err)
350
+ cuda_load_failed = (
351
+ "Found no NVIDIA driver" in msg
352
+ or "No CUDA GPUs are available" in msg
353
+ or "libcudart" in msg
354
+ or "CUDA error" in msg
355
+ or "CUDA driver" in msg
356
+ )
357
+
358
+ if cuda_load_failed:
359
+ _log(
360
+ "device_map='cuda' failed during startup. "
361
+ "Retrying CPU-load + manual .to('cuda') ..."
362
+ )
363
+ _log(f"CUDA load error was: {msg}")
364
+
365
+ _load_components_compat(
366
+ pipe,
367
+ trust_remote_code=True,
368
+ torch_dtype={
369
+ "default": torch.bfloat16,
370
+ "vae": torch.float16,
371
+ },
372
+ token=HF_TOKEN,
373
+ )
374
+
375
+ pipe = pipe.to("cuda")
376
+ else:
377
+ raise
378
+
379
+ # Krea model-card optimization: fuse projections.
380
+ # This is safe; it is not torch.compile.
381
+ try:
382
+ if hasattr(pipe, "transformer") and hasattr(pipe.transformer, "blocks"):
383
+ fused = 0
384
+ for block in pipe.transformer.blocks:
385
+ self_attn = getattr(block, "self_attn", None)
386
+ if self_attn is not None and hasattr(self_attn, "fuse_projections"):
387
+ self_attn.fuse_projections()
388
+ fused += 1
389
+ _log(f"Fused attention projections on {fused} blocks.")
390
+ except Exception as e:
391
+ _log(f"fuse_projections warning: {type(e).__name__}: {e}")
392
+
393
+ _pipeline = pipe
394
+ _pipeline_error = None
395
+ _log("Pipeline ready.")
396
+ return _pipeline
397
+
398
+ except Exception as e:
399
+ _pipeline_error = f"{type(e).__name__}: {e}"
400
+ _log(f"Pipeline load FAILED: {_pipeline_error}")
401
+ traceback.print_exc()
402
+ return None
403
+
404
+
405
+ # ---------------------------------------------------------------------------
406
+ # LoRA helpers
407
+ # ---------------------------------------------------------------------------
408
+
409
+ def _load_lora_if_needed(pipe, lora_label):
410
+ """
411
+ Load a known LoRA adapter once.
412
+
413
+ This is intentionally not decorated with @spaces.GPU when called through
414
+ the UI load button, so it does not reserve ZeroGPU generation time.
415
+ """
416
+ global _loaded_loras
417
+
418
+ cfg = KNOWN_LORAS.get(lora_label)
419
+ if not cfg:
420
+ return None
421
+
422
+ if not LORA_BACKEND_READY:
423
+ raise RuntimeError(LORA_BACKEND_ERROR)
424
+
425
+ adapter_name = cfg["adapter_name"]
426
+
427
+ if adapter_name in _loaded_loras:
428
+ return adapter_name
429
+
430
+ transformer = getattr(pipe, "transformer", None)
431
+ if transformer is None or not hasattr(transformer, "load_lora_adapter"):
432
+ raise RuntimeError("This pipeline transformer does not expose load_lora_adapter().")
433
+
434
+ _log(f"Loading LoRA adapter: {lora_label} ({adapter_name})")
435
+
436
+ transformer.load_lora_adapter(
437
+ cfg["repo_id"],
438
+ prefix=cfg["prefix"],
439
+ weight_name=cfg["weight_name"],
440
+ adapter_name=adapter_name,
441
+ )
442
+
443
+ _loaded_loras.add(adapter_name)
444
+ _log(f"LoRA loaded: {adapter_name}")
445
+
446
+ return adapter_name
447
+
448
+
449
+ def _safe_disable_lora(transformer):
450
+ """
451
+ Disable PEFT LoRA if available.
452
+
453
+ Some diffusers methods raise if PEFT is not installed or incompatible, so
454
+ this is defensive.
455
+ """
456
+ if transformer is None:
457
+ return
458
+
459
+ if hasattr(transformer, "disable_lora"):
460
+ try:
461
+ transformer.disable_lora()
462
+ return
463
+ except Exception as e:
464
+ _log(f"disable_lora warning: {type(e).__name__}: {e}")
465
+
466
+ if hasattr(transformer, "set_adapters"):
467
+ try:
468
+ transformer.set_adapters([], adapter_weights=[])
469
+ return
470
+ except Exception as e:
471
+ _log(f"set_adapters([]) warning: {type(e).__name__}: {e}")
472
+
473
+
474
+ def _set_lora(pipe, lora_label, lora_strength, allow_load=True):
475
+ """
476
+ Activate the selected LoRA, or disable LoRA for base model.
477
+
478
+ If allow_load=False, this function will not download/load a missing adapter.
479
+ This keeps generate() fast and avoids hidden loading inside @spaces.GPU.
480
+ """
481
+ global _active_lora, _active_lora_label, _active_lora_strength
482
+
483
+ transformer = getattr(pipe, "transformer", None)
484
+ if transformer is None:
485
+ raise RuntimeError("Pipeline has no transformer.")
486
+
487
+ cfg = KNOWN_LORAS.get(lora_label)
488
+
489
+ if not cfg:
490
+ _safe_disable_lora(transformer)
491
+
492
+ _active_lora = None
493
+ _active_lora_label = "Base model"
494
+ _active_lora_strength = 1.0
495
+ return ""
496
+
497
+ adapter_name = cfg["adapter_name"]
498
+
499
+ if adapter_name not in _loaded_loras:
500
+ if not allow_load:
501
+ raise RuntimeError(
502
+ f"LoRA '{lora_label}' is selected but not loaded. "
503
+ "Click 'Load style' before generating."
504
+ )
505
+ adapter_name = _load_lora_if_needed(pipe, lora_label)
506
+
507
+ if hasattr(transformer, "enable_lora"):
508
+ transformer.enable_lora()
509
+
510
+ if hasattr(transformer, "set_adapters"):
511
+ transformer.set_adapters(
512
+ [adapter_name],
513
+ adapter_weights=[float(lora_strength)],
514
+ )
515
+ elif hasattr(transformer, "set_adapter"):
516
+ transformer.set_adapter(adapter_name)
517
+
518
+ _active_lora = adapter_name
519
+ _active_lora_label = lora_label
520
+ _active_lora_strength = float(lora_strength)
521
+
522
+ return cfg.get("trigger", "").strip()
523
+
524
+
525
+ def load_selected_lora(lora_style, lora_strength):
526
+ """
527
+ Manual LoRA loading button.
528
+
529
+ Not decorated with @spaces.GPU on purpose:
530
+ loading the adapter should happen before generation and not consume
531
+ the generation reservation window.
532
+ """
533
+ pipe = _load_pipeline()
534
+ if pipe is None:
535
+ return (
536
+ "Model is not loaded.",
537
+ {
538
+ "status": "error",
539
+ "error": _pipeline_error or "Pipeline failed to load",
540
+ "lora": _lora_report(),
541
+ },
542
+ )
543
+
544
+ if lora_style == "Base model":
545
+ with _lora_lock:
546
+ _set_lora(pipe, "Base model", 1.0, allow_load=False)
547
+
548
+ return (
549
+ "Base model active.",
550
+ {
551
+ "status": "ready",
552
+ "message": "Base model active. No LoRA selected.",
553
+ "lora": _lora_report(),
554
+ },
555
+ )
556
+
557
+ try:
558
+ with _lora_lock:
559
+ trigger = _set_lora(
560
+ pipe,
561
+ lora_style,
562
+ float(lora_strength),
563
+ allow_load=True,
564
+ )
565
+
566
+ return (
567
+ f"{lora_style} loaded. Trigger `{trigger}` will be added automatically.",
568
+ {
569
+ "status": "ready",
570
+ "message": f"LoRA loaded and activated: {lora_style}",
571
+ "trigger": trigger,
572
+ "lora": _lora_report(),
573
+ },
574
+ )
575
+
576
+ except Exception as e:
577
+ traceback.print_exc()
578
+ return (
579
+ f"LoRA load failed: {type(e).__name__}: {e}",
580
+ {
581
+ "status": "error",
582
+ "message": "LoRA load failed.",
583
+ "error": f"{type(e).__name__}: {e}",
584
+ "lora": _lora_report(),
585
+ },
586
+ )
587
+
588
+
589
+ def disable_lora():
590
+ """
591
+ Disable LoRA and return to base model.
592
+
593
+ This does not necessarily remove the adapter from memory; it only disables it.
594
+ Keeping the adapter cached makes switching back faster.
595
+ """
596
+ pipe = _load_pipeline()
597
+ if pipe is None:
598
+ return (
599
+ "Model is not loaded.",
600
+ {
601
+ "status": "error",
602
+ "error": _pipeline_error or "Pipeline failed to load",
603
+ "lora": _lora_report(),
604
+ },
605
+ )
606
+
607
+ try:
608
+ with _lora_lock:
609
+ _set_lora(pipe, "Base model", 1.0, allow_load=False)
610
+
611
+ return (
612
+ "Base model active.",
613
+ {
614
+ "status": "ready",
615
+ "message": "LoRA disabled. Base model active.",
616
+ "lora": _lora_report(),
617
+ },
618
+ )
619
+
620
+ except Exception as e:
621
+ traceback.print_exc()
622
+ return (
623
+ f"Could not switch to base model cleanly: {type(e).__name__}: {e}",
624
+ {
625
+ "status": "error",
626
+ "message": "Could not disable LoRA cleanly.",
627
+ "error": f"{type(e).__name__}: {e}",
628
+ "lora": _lora_report(),
629
+ },
630
+ )
631
+
632
+
633
+ # ---------------------------------------------------------------------------
634
+ # Eager app runtime warm-up
635
+ # ---------------------------------------------------------------------------
636
+
637
+ if os.environ.get("SKIP_MODEL_LOAD") != "1":
638
+ _load_pipeline()
639
+
640
+
641
+ # ---------------------------------------------------------------------------
642
+ # Health / warm-up endpoints
643
+ # ---------------------------------------------------------------------------
644
+
645
+ def health():
646
+ return {
647
+ "status": "ready" if _pipeline is not None else "not_loaded",
648
+ "model_ready": _pipeline is not None,
649
+ "pipeline_ready": _pipeline is not None,
650
+ "model_id": MODEL_ID,
651
+ "runtime_mode": "zerogpu_compatibility_compile_bypass",
652
+ "last_error": _pipeline_error or "",
653
+ "runtime": _runtime_report(),
654
+ "lora": _lora_report(),
655
+ }
656
+
657
+
658
+ def warmup_model():
659
+ """
660
+ Manual refresh button.
661
+
662
+ Usually the model is already loaded at app startup.
663
+ """
664
+ pipe = _load_pipeline()
665
+ if pipe is None:
666
+ return {
667
+ "status": "error",
668
+ "message": "Pipeline failed to load.",
669
+ "error": _pipeline_error or "Pipeline failed to load",
670
+ "runtime": _runtime_report(),
671
+ "lora": _lora_report(),
672
+ }
673
+
674
+ return {
675
+ "status": "ready",
676
+ "message": "Model loaded and cached in this Space process.",
677
+ "model_id": MODEL_ID,
678
+ "runtime": _runtime_report(),
679
+ "lora": _lora_report(),
680
+ }
681
+
682
+
683
+ # ---------------------------------------------------------------------------
684
+ # Generation endpoint — real inference guarded by @spaces.GPU
685
+ # ---------------------------------------------------------------------------
686
+
687
+ def _gpu_duration(
688
+ prompt,
689
+ lora_style,
690
+ lora_strength,
691
+ num_blocks,
692
+ num_inference_steps,
693
+ seed,
694
+ *args,
695
+ **kwargs,
696
+ ):
697
+ try:
698
+ blocks = int(num_blocks)
699
+ steps = int(num_inference_steps)
700
+ except Exception:
701
+ blocks = 9
702
+ steps = 6
703
+
704
+ # Model and LoRA are expected to be loaded before generation.
705
+ # Observed on this Space:
706
+ # 9 blocks × 4 steps < 75s
707
+ # 9 blocks × 8 steps < 80s
708
+ return min(120, max(30, int(35 + blocks * steps * 1.2)))
709
+
710
+
711
+ @_spaces_gpu(duration=_gpu_duration, size="xlarge")
712
+ def generate(
713
+ prompt,
714
+ lora_style,
715
+ lora_strength,
716
+ num_blocks,
717
+ num_inference_steps,
718
+ seed,
719
+ progress=gr.Progress(track_tqdm=True),
720
+ ):
721
+ pipe = _load_pipeline()
722
+
723
+ if pipe is None:
724
+ err = _pipeline_error or "Pipeline not loaded (unknown failure)"
725
+ raise RuntimeError(f"Generation unavailable: {err}")
726
+
727
+ if not isinstance(prompt, str) or not prompt.strip():
728
+ raise ValueError("Prompt must be a non-empty string.")
729
+
730
+ num_blocks = int(num_blocks)
731
+ num_inference_steps = int(num_inference_steps)
732
+ seed = int(seed)
733
+ lora_strength = float(lora_strength)
734
+
735
+ if num_blocks < 1 or num_blocks > 12:
736
+ raise ValueError("num_blocks must be between 1 and 12.")
737
+
738
+ if num_inference_steps < 1 or num_inference_steps > 8:
739
+ raise ValueError("num_inference_steps must be between 1 and 8.")
740
+
741
+ device = "cuda"
742
+
743
+ try:
744
+ pipe = pipe.to(device)
745
+ except Exception as e:
746
+ _log(f"Pipeline .to('cuda') warning: {type(e).__name__}: {e}")
747
+
748
+ with _lora_lock:
749
+ trigger = _set_lora(
750
+ pipe,
751
+ lora_style,
752
+ lora_strength,
753
+ allow_load=False,
754
+ )
755
+
756
+ final_prompt = prompt.strip()
757
+ if trigger and not final_prompt.startswith(trigger):
758
+ final_prompt = f"{trigger} {final_prompt}"
759
+
760
+ frames = []
761
+ state = PipelineState()
762
+
763
+ try:
764
+ generator = torch.Generator(device=device).manual_seed(seed)
765
+ except Exception as e:
766
+ _log(f"CUDA generator failed, falling back to CPU generator: {type(e).__name__}: {e}")
767
+ generator = torch.Generator(device="cpu").manual_seed(seed)
768
+
769
+ try:
770
+ progress(0, desc="Preparing generation")
771
+
772
+ for block_idx in progress.tqdm(
773
+ range(num_blocks),
774
+ desc="Generating video blocks",
775
+ ):
776
+ _log(f"Block {block_idx + 1}/{num_blocks}")
777
+
778
+ progress(
779
+ block_idx / max(1, num_blocks),
780
+ desc=f"Generating block {block_idx + 1}/{num_blocks}",
781
+ )
782
+
783
+ state = pipe(
784
+ state,
785
+ prompt=[final_prompt],
786
+ num_inference_steps=num_inference_steps,
787
+ num_blocks=num_blocks,
788
+ block_idx=block_idx,
789
+ generator=generator,
790
+ )
791
+
792
+ videos = state.values.get("videos")
793
+ if not videos:
794
+ raise RuntimeError("Pipeline state did not contain `videos` after inference.")
795
+
796
+ frames.extend(videos[0])
797
+
798
+ except Exception as e:
799
+ _log(f"Inference failed at block {locals().get('block_idx', 'unknown')}: {e}")
800
+ traceback.print_exc()
801
+ raise RuntimeError(
802
+ f"Inference error at block {locals().get('block_idx', 'unknown')}: {e}"
803
+ )
804
+
805
+ if not frames:
806
+ raise RuntimeError("No frames were generated.")
807
+
808
+ progress(0.95, desc="Exporting video")
809
+
810
+ output_path = f"/tmp/krea_output_{int(time.time())}.mp4"
811
+ export_to_video(frames, output_path, fps=24)
812
+
813
+ progress(1.0, desc="Done")
814
+ _log(f"Saved video to {output_path}")
815
+
816
+ return output_path
817
+
818
+
819
+ # ---------------------------------------------------------------------------
820
+ # Gradio app
821
+ # ---------------------------------------------------------------------------
822
+
823
  with gr.Blocks(title="Krea Realtime Video 14B") as demo:
824
  gr.Markdown(
825
  "# Krea Realtime Video 14B\n"
 
855
  load_lora_btn = gr.Button("Load style", variant="secondary")
856
  disable_lora_btn = gr.Button("Base model", variant="secondary")
857
 
858
+ style_status = gr.Textbox(
859
+ label="Style status",
860
+ value="Base model active.",
861
+ interactive=False,
862
+ lines=1,
863
+ )
864
+
865
  with gr.Row():
866
  num_blocks = gr.Slider(
867
  minimum=1,
 
884
  generate_btn = gr.Button("Generate video", variant="primary")
885
 
886
  gr.Markdown(
887
+ "For Origami: select **Origami**, click **Load style**, then generate. "
888
+ "The `[origami]` trigger is added automatically."
889
  )
890
 
891
  with gr.Column(scale=5):
 
971
  num_inference_steps,
972
  seed,
973
  ],
 
 
 
974
  )
975
 
976
  warmup_btn.click(
 
983
  load_lora_btn.click(
984
  load_selected_lora,
985
  inputs=[lora_style, lora_strength],
986
+ outputs=[style_status, lora_status],
987
  api_name="load_lora",
988
  )
989
 
990
  disable_lora_btn.click(
991
  disable_lora,
992
  inputs=None,
993
+ outputs=[style_status, lora_status],
994
  api_name="disable_lora",
995
  )
996
 
 
1013
  inputs=None,
1014
  outputs=model_status,
1015
  api_name="health",
1016
+ )
1017
+
1018
+
1019
+ if __name__ == "__main__":
1020
+ demo.queue().launch(
1021
+ server_name="0.0.0.0",
1022
+ server_port=7860,
1023
+ show_error=True,
1024
  )