fffiloni commited on
Commit
d6cca66
·
verified ·
1 Parent(s): 94f6d94

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +319 -107
app.py CHANGED
@@ -1,154 +1,316 @@
1
  # ---------------------------------------------------------------------------
2
  # Krea Realtime Video 14B — Hugging Face Space Demo
3
- # Attempts the real diffusers ModularPipeline path per the model card.
4
  # ---------------------------------------------------------------------------
 
5
  import os
6
 
7
- # Redirect caches before any library import (HF Spaces operational rule)
8
- os.environ.setdefault("HF_HOME", "/tmp/asf-hf-cache")
 
 
 
 
 
 
 
 
 
9
  os.environ.setdefault("HF_MODULES_CACHE", "/tmp/hf_modules")
10
  os.environ.setdefault("MPLCONFIGDIR", "/tmp/matplotlib")
11
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
13
 
14
- # Safe spaces import: valid on both ZeroGPU and fixed-GPU fallback hardware
 
 
 
15
  try:
16
  import spaces
 
17
  HAS_SPACES = True
18
  except Exception:
19
  HAS_SPACES = False
20
 
21
  class _DummySpaces:
22
- def GPU(self, duration=60):
23
  def decorator(fn):
24
  return fn
 
25
  return decorator
 
26
  spaces = _DummySpaces()
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  import traceback
 
29
  import torch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  import gradio as gr
31
 
32
- # ASF v198.26.20 writable Hugging Face cache guard.
33
- _ASF_HF_CACHE_ROOT = os.environ.get("ASF_HF_CACHE_DIR") or "/tmp/asf-hf-cache"
34
- os.makedirs(_ASF_HF_CACHE_ROOT, exist_ok=True)
35
- os.makedirs(os.path.join(_ASF_HF_CACHE_ROOT, "hub"), exist_ok=True)
36
- os.makedirs(os.path.join(_ASF_HF_CACHE_ROOT, "transformers"), exist_ok=True)
37
- os.makedirs(os.path.join(_ASF_HF_CACHE_ROOT, "diffusers"), exist_ok=True)
38
- os.environ.setdefault("HF_HOME", _ASF_HF_CACHE_ROOT)
39
- os.environ.setdefault("HF_HUB_CACHE", os.path.join(_ASF_HF_CACHE_ROOT, "hub"))
40
- os.environ.setdefault("HUGGINGFACE_HUB_CACHE", os.path.join(_ASF_HF_CACHE_ROOT, "hub"))
41
- os.environ.setdefault("TRANSFORMERS_CACHE", os.path.join(_ASF_HF_CACHE_ROOT, "transformers"))
42
- os.environ.setdefault("DIFFUSERS_CACHE", os.path.join(_ASF_HF_CACHE_ROOT, "diffusers"))
43
 
44
  _DIFFUSERS_OK = False
45
  _DIFFUSERS_IMPORT_ERROR = None
46
 
47
- # Wrap diffusers imports so a build/API mismatch doesn't prevent the app from booting
48
  try:
49
  from diffusers import ModularPipeline
50
  from diffusers.modular_pipelines import PipelineState
51
  from diffusers.utils import export_to_video
 
52
  _DIFFUSERS_OK = True
53
  except Exception as e:
54
  _DIFFUSERS_IMPORT_ERROR = f"{type(e).__name__}: {e}"
55
  traceback.print_exc()
56
 
 
 
 
 
 
57
  MODEL_ID = "krea/krea-realtime-video"
58
 
 
 
 
 
 
59
  def _log(msg):
60
  print(f"[KreaRealtimeVideo] {msg}", flush=True)
61
 
62
- _pipeline = None
63
- _pipeline_error = None
 
 
 
 
 
 
 
 
 
64
 
65
 
66
  def _load_pipeline():
 
 
 
 
 
 
 
67
  global _pipeline, _pipeline_error
68
 
69
- if not _DIFFUSERS_OK:
70
- _pipeline_error = _DIFFUSERS_IMPORT_ERROR or "Diffusers import failed"
71
- _log(f"Pipeline load skipped: {_pipeline_error}")
72
- return
73
 
74
- try:
75
- _log(f"Loading ModularPipeline from {MODEL_ID} ...")
76
- pipe = ModularPipeline.from_pretrained(MODEL_ID, trust_remote_code=True, token=HF_TOKEN)
77
- _log("Skeleton loaded; attaching components ...")
78
 
79
- # Primary path: device_map="cuda" as the model card instructs.
80
  try:
81
- pipe.load_components(
 
 
 
 
82
  trust_remote_code=True,
83
- device_map="cuda",
84
- torch_dtype={"default": torch.bfloat16, "vae": torch.float16},
85
- token=HF_TOKEN)
86
- except RuntimeError as err:
87
- msg = str(err)
88
- if "Found no NVIDIA driver" in msg or "No CUDA GPUs are available" in msg or "libcudart" in msg:
89
- _log(f"device_map='cuda' failed at module level ({msg}). Retrying with CPU-load + manual .to('cuda') ...")
90
  pipe.load_components(
91
  trust_remote_code=True,
92
- torch_dtype={"default": torch.bfloat16, "vae": torch.float16},
93
- token=HF_TOKEN)
94
- # ZeroGPU emulates CUDA at module level; .to('cuda') is safe here.
95
- # On fixed GPU it moves to the real device.
96
- pipe = pipe.to("cuda")
97
- else:
98
- raise
99
-
100
- # Optimization: fuse Q/K/V projections in attention blocks.
101
- try:
102
- if hasattr(pipe, "transformer") and hasattr(pipe.transformer, "blocks"):
103
- for block in pipe.transformer.blocks:
104
- self_attn = getattr(block, "self_attn", None)
105
- if self_attn is not None and hasattr(self_attn, "fuse_projections"):
106
- self_attn.fuse_projections()
107
- _log("Fused attention projections.")
108
- except Exception as e:
109
- _log(f"fuse_projections warning: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- _pipeline = pipe
112
- _pipeline_error = None
113
- _log("Pipeline ready.")
114
- except Exception as e:
115
- _pipeline_error = f"{type(e).__name__}: {e}"
116
- _log(f"Pipeline load FAILED: {_pipeline_error}")
117
- traceback.print_exc()
118
 
119
 
120
- # Attempt module-level load unless explicitly suppressed (useful for local syntax checks)
121
- if os.environ.get("SKIP_MODEL_LOAD") != "1":
 
122
  _load_pipeline()
123
 
124
 
125
  # ---------------------------------------------------------------------------
126
- # Health endpoint — cheap, no weights, no GPU work
127
  # ---------------------------------------------------------------------------
 
128
  def health():
129
  return {
130
- "status": "ok" if _pipeline is not None else "error",
131
  "model_ready": _pipeline is not None,
132
  "pipeline_ready": _pipeline is not None,
133
- "model_family": "diffusers_full_pipeline",
134
- "loader_strategy": "DiffusionPipeline_or_family_specific_from_pretrained",
 
 
135
  "last_error": _pipeline_error or "",
136
  "expected_output_type": "video",
 
137
  }
138
 
139
 
140
  # ---------------------------------------------------------------------------
141
  # Generation endpoint — real inference guarded by @spaces.GPU
142
  # ---------------------------------------------------------------------------
143
- def _gpu_duration(*args, **kwargs):
144
- # inputs: prompt, num_blocks, num_inference_steps, seed
145
- # Reserve enough time for load hydration + forward passes.
146
- return 300
147
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
- @spaces.GPU(duration=_gpu_duration)
 
150
  def generate(prompt, num_blocks, num_inference_steps, seed):
151
- if _pipeline is None:
 
 
152
  err = _pipeline_error or "Pipeline not loaded (unknown failure)"
153
  raise RuntimeError(f"Generation unavailable: {err}")
154
 
@@ -159,40 +321,68 @@ def generate(prompt, num_blocks, num_inference_steps, seed):
159
  num_inference_steps = int(num_inference_steps)
160
  seed = int(seed)
161
 
162
- if num_blocks < 1 or num_blocks > 9:
163
- raise ValueError("num_blocks must be between 1 and 9.")
164
- if num_inference_steps < 1 or num_inference_steps > 50:
165
- raise ValueError("num_inference_steps must be between 1 and 50.")
 
 
 
 
166
 
167
  device = "cuda"
168
- # If the pipeline was loaded on CPU (fallback path) ensure it is on the active CUDA device.
169
- if hasattr(_pipeline, "device"):
170
- current = str(_pipeline.device)
171
- if "cuda" not in current:
172
- _log("Moving pipeline to cuda ...")
173
- _pipeline.to(device)
 
 
 
 
174
 
175
  frames = []
176
  state = PipelineState()
177
- generator = torch.Generator(device=device).manual_seed(seed)
 
 
 
 
 
178
 
179
  try:
180
  for block_idx in range(num_blocks):
181
  _log(f"Block {block_idx + 1}/{num_blocks}")
182
- state = _pipeline(
 
183
  state,
184
  prompt=[prompt],
185
  num_inference_steps=num_inference_steps,
186
  num_blocks=num_blocks,
187
  block_idx=block_idx,
188
- generator=generator)
189
- frames.extend(state.values["videos"][0])
 
 
 
 
 
 
 
190
  except Exception as e:
191
- _log(f"Inference failed at block {block_idx}: {e}")
192
- raise RuntimeError(f"Inference error at block {block_idx}: {e}")
 
 
 
 
 
 
193
 
194
  output_path = "/tmp/krea_output.mp4"
195
  export_to_video(frames, output_path, fps=24)
 
196
  _log(f"Saved video to {output_path}")
197
  return output_path
198
 
@@ -200,14 +390,15 @@ def generate(prompt, num_blocks, num_inference_steps, seed):
200
  # ---------------------------------------------------------------------------
201
  # Gradio app
202
  # ---------------------------------------------------------------------------
 
203
  with gr.Blocks(title="Krea Realtime Video 14B") as demo:
204
  gr.Markdown(
205
  "# Krea Realtime Video 14B\n\n"
206
- "This Space attempts **real local inference** for the Krea Realtime 14B text-to-video model "
207
- "using the official Diffusers ModularPipeline path.\n\n"
208
- "⚠️ **Hardware warning**: this is a 14B transformer (~28 GB) paired with a UMT5-XXL text encoder "
209
- "(~12–14 GB). Standard HF Space GPUs (48 GB) are unlikely to fit the full model in bf16. "
210
- "If the pipeline fails to load, `/health` and the UI will show the concrete error."
211
  )
212
 
213
  with gr.Row():
@@ -215,14 +406,27 @@ with gr.Blocks(title="Krea Realtime Video 14B") as demo:
215
  prompt = gr.Textbox(
216
  label="Prompt",
217
  placeholder="e.g., a cat sitting on a boat",
218
- lines=2)
 
 
219
  num_blocks = gr.Slider(
220
- minimum=1, maximum=9, value=3, step=1,
221
- label="Number of Blocks (frames per block ≈ 3)")
 
 
 
 
 
222
  num_inference_steps = gr.Slider(
223
- minimum=1, maximum=20, value=4, step=1,
224
- label="Inference Steps per Block")
 
 
 
 
 
225
  seed = gr.Number(value=42, precision=0, label="Seed")
 
226
  generate_btn = gr.Button("Generate Video", variant="primary")
227
 
228
  with gr.Column():
@@ -230,27 +434,35 @@ with gr.Blocks(title="Krea Realtime Video 14B") as demo:
230
 
231
  gr.Examples(
232
  examples=[
233
- ["a cat sitting on a boat", 3, 4, 42],
234
- ["a futuristic city at sunset", 3, 4, 123],
235
- ["a panda playing guitar in a forest", 2, 4, 7],
236
  ],
237
  inputs=[prompt, num_blocks, num_inference_steps, seed],
238
  outputs=output_video,
239
  fn=generate,
240
- cache_examples=False)
 
241
 
242
  generate_btn.click(
243
  generate,
244
  inputs=[prompt, num_blocks, num_inference_steps, seed],
245
  outputs=output_video,
246
- api_name="generate")
 
247
 
248
- # Structured health endpoint
249
  demo.load(
250
  lambda: health(),
251
  None,
252
  gr.JSON(),
253
- api_name="health")
 
 
254
 
255
  if __name__ == "__main__":
256
- demo.queue().launch(server_name="0.0.0.0", server_port=7860, show_error=True)
 
 
 
 
 
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
+ # Compatibility mode: avoid optional hub-kernels path unless explicitly enabled.
23
+ # The first failure you saw came from transformers -> hub_kernels -> kernels.
24
+ os.environ.setdefault("DIFFUSERS_ENABLE_HUB_KERNELS", "0")
25
+ os.environ.setdefault("USE_HUB_KERNELS", "NO")
26
+
27
+ os.makedirs(_ASF_HF_CACHE_ROOT, exist_ok=True)
28
+ os.makedirs(os.path.join(_ASF_HF_CACHE_ROOT, "hub"), exist_ok=True)
29
+ os.makedirs(os.path.join(_ASF_HF_CACHE_ROOT, "transformers"), exist_ok=True)
30
+ os.makedirs(os.path.join(_ASF_HF_CACHE_ROOT, "diffusers"), exist_ok=True)
31
+ os.makedirs(os.environ["HF_MODULES_CACHE"], exist_ok=True)
32
+ os.makedirs(os.environ["MPLCONFIGDIR"], exist_ok=True)
33
+
34
  HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
35
 
36
+ # ---------------------------------------------------------------------------
37
+ # Safe spaces import
38
+ # ---------------------------------------------------------------------------
39
+
40
  try:
41
  import spaces
42
+
43
  HAS_SPACES = True
44
  except Exception:
45
  HAS_SPACES = False
46
 
47
  class _DummySpaces:
48
+ def GPU(self, *args, **kwargs):
49
  def decorator(fn):
50
  return fn
51
+
52
  return decorator
53
+
54
  spaces = _DummySpaces()
55
 
56
+
57
+ def _spaces_gpu(*args, **kwargs):
58
+ """
59
+ Wrapper around spaces.GPU.
60
+
61
+ Some spaces versions may not support size=...
62
+ In that case, fall back to the same decorator without size.
63
+ """
64
+ try:
65
+ return spaces.GPU(*args, **kwargs)
66
+ except TypeError:
67
+ kwargs.pop("size", None)
68
+ return spaces.GPU(*args, **kwargs)
69
+
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # Imports + ZeroGPU torch.compile bypass
73
+ # ---------------------------------------------------------------------------
74
+
75
+ import sys
76
+ import threading
77
  import traceback
78
+
79
  import torch
80
+
81
+ # ZeroGPU does not support torch.compile.
82
+ # Krea remote code compiles FlexAttention at import/load time.
83
+ # Therefore we no-op torch.compile globally for this Space.
84
+ _ORIG_TORCH_COMPILE = getattr(torch, "compile", None)
85
+
86
+
87
+ def _asf_zerogpu_compile_bypass(fn=None, *args, **kwargs):
88
+ """
89
+ ZeroGPU compatibility shim.
90
+
91
+ Supports both call styles:
92
+ torch.compile(fn, ...)
93
+ @torch.compile(...)
94
+ def fn(...): ...
95
+ """
96
+ if fn is None:
97
+ def decorator(real_fn):
98
+ return _asf_zerogpu_compile_bypass(real_fn, *args, **kwargs)
99
+
100
+ return decorator
101
+
102
+ name = getattr(fn, "__name__", repr(fn))
103
+ module = getattr(fn, "__module__", "")
104
+
105
+ print(
106
+ f"[ASF] ZeroGPU compatibility: bypassing torch.compile for {module}.{name}",
107
+ flush=True,
108
+ )
109
+ return fn
110
+
111
+
112
+ if _ORIG_TORCH_COMPILE is not None and os.environ.get("ASF_ENABLE_TORCH_COMPILE", "0") != "1":
113
+ torch.compile = _asf_zerogpu_compile_bypass
114
+
115
+ try:
116
+ import torch._dynamo
117
+
118
+ torch._dynamo.config.suppress_errors = True
119
+ except Exception:
120
+ pass
121
+
122
  import gradio as gr
123
 
124
+ # ---------------------------------------------------------------------------
125
+ # Diffusers imports
126
+ # ---------------------------------------------------------------------------
 
 
 
 
 
 
 
 
127
 
128
  _DIFFUSERS_OK = False
129
  _DIFFUSERS_IMPORT_ERROR = None
130
 
 
131
  try:
132
  from diffusers import ModularPipeline
133
  from diffusers.modular_pipelines import PipelineState
134
  from diffusers.utils import export_to_video
135
+
136
  _DIFFUSERS_OK = True
137
  except Exception as e:
138
  _DIFFUSERS_IMPORT_ERROR = f"{type(e).__name__}: {e}"
139
  traceback.print_exc()
140
 
141
+
142
+ # ---------------------------------------------------------------------------
143
+ # Model configuration
144
+ # ---------------------------------------------------------------------------
145
+
146
  MODEL_ID = "krea/krea-realtime-video"
147
 
148
+ _pipeline = None
149
+ _pipeline_error = None
150
+ _pipeline_lock = threading.Lock()
151
+
152
+
153
  def _log(msg):
154
  print(f"[KreaRealtimeVideo] {msg}", flush=True)
155
 
156
+
157
+ def _runtime_report():
158
+ return {
159
+ "python": sys.version.replace("\n", " "),
160
+ "torch": getattr(torch, "__version__", "unknown"),
161
+ "cuda_available": bool(torch.cuda.is_available()),
162
+ "has_spaces": HAS_SPACES,
163
+ "torch_compile_bypassed": torch.compile is _asf_zerogpu_compile_bypass,
164
+ "hf_home": os.environ.get("HF_HOME", ""),
165
+ "hf_modules_cache": os.environ.get("HF_MODULES_CACHE", ""),
166
+ }
167
 
168
 
169
  def _load_pipeline():
170
+ """
171
+ Lazy-load the ModularPipeline.
172
+
173
+ Important for ZeroGPU:
174
+ - The GPU is only actually allocated inside @spaces.GPU-decorated functions.
175
+ - Therefore loading inside generate() is safer than loading at module import.
176
+ """
177
  global _pipeline, _pipeline_error
178
 
179
+ with _pipeline_lock:
180
+ if _pipeline is not None:
181
+ return _pipeline
 
182
 
183
+ if not _DIFFUSERS_OK:
184
+ _pipeline_error = _DIFFUSERS_IMPORT_ERROR or "Diffusers import failed"
185
+ _log(f"Pipeline load skipped: {_pipeline_error}")
186
+ return None
187
 
 
188
  try:
189
+ _log(f"Runtime report: {_runtime_report()}")
190
+ _log(f"Loading ModularPipeline from {MODEL_ID} ...")
191
+
192
+ pipe = ModularPipeline.from_pretrained(
193
+ MODEL_ID,
194
  trust_remote_code=True,
195
+ token=HF_TOKEN,
196
+ )
197
+
198
+ _log("Skeleton loaded; attaching components ...")
199
+
200
+ # Primary path: device_map='cuda', as the Krea model card expects.
201
+ try:
202
  pipe.load_components(
203
  trust_remote_code=True,
204
+ device_map="cuda",
205
+ torch_dtype={
206
+ "default": torch.bfloat16,
207
+ "vae": torch.float16,
208
+ },
209
+ token=HF_TOKEN,
210
+ )
211
+ except RuntimeError as err:
212
+ msg = str(err)
213
+ cuda_load_failed = (
214
+ "Found no NVIDIA driver" in msg
215
+ or "No CUDA GPUs are available" in msg
216
+ or "libcudart" in msg
217
+ or "CUDA error" in msg
218
+ )
219
+
220
+ if cuda_load_failed:
221
+ _log(
222
+ "device_map='cuda' failed at module level. "
223
+ "Retrying with CPU-load + manual .to('cuda') ..."
224
+ )
225
+ _log(f"CUDA load error was: {msg}")
226
+
227
+ pipe.load_components(
228
+ trust_remote_code=True,
229
+ torch_dtype={
230
+ "default": torch.bfloat16,
231
+ "vae": torch.float16,
232
+ },
233
+ token=HF_TOKEN,
234
+ )
235
+
236
+ # On ZeroGPU, CUDA should be active inside @spaces.GPU.
237
+ pipe = pipe.to("cuda")
238
+ else:
239
+ raise
240
+
241
+ # Krea model-card optimization: fuse Q/K/V projections.
242
+ # This is not torch.compile and should be safe.
243
+ try:
244
+ if hasattr(pipe, "transformer") and hasattr(pipe.transformer, "blocks"):
245
+ fused = 0
246
+ for block in pipe.transformer.blocks:
247
+ self_attn = getattr(block, "self_attn", None)
248
+ if self_attn is not None and hasattr(self_attn, "fuse_projections"):
249
+ self_attn.fuse_projections()
250
+ fused += 1
251
+ _log(f"Fused attention projections on {fused} blocks.")
252
+ except Exception as e:
253
+ _log(f"fuse_projections warning: {type(e).__name__}: {e}")
254
+
255
+ _pipeline = pipe
256
+ _pipeline_error = None
257
+ _log("Pipeline ready.")
258
+ return _pipeline
259
 
260
+ except Exception as e:
261
+ _pipeline_error = f"{type(e).__name__}: {e}"
262
+ _log(f"Pipeline load FAILED: {_pipeline_error}")
263
+ traceback.print_exc()
264
+ return None
 
 
265
 
266
 
267
+ # Optional eager load for non-ZeroGPU debugging only.
268
+ # Keep default lazy for ZeroGPU.
269
+ if os.environ.get("ASF_LOAD_AT_STARTUP") == "1" and os.environ.get("SKIP_MODEL_LOAD") != "1":
270
  _load_pipeline()
271
 
272
 
273
  # ---------------------------------------------------------------------------
274
+ # Health endpoint
275
  # ---------------------------------------------------------------------------
276
+
277
  def health():
278
  return {
279
+ "status": "ok" if _pipeline is not None else "not_loaded",
280
  "model_ready": _pipeline is not None,
281
  "pipeline_ready": _pipeline is not None,
282
+ "model_id": MODEL_ID,
283
+ "model_family": "diffusers_modular_pipeline",
284
+ "loader_strategy": "ModularPipeline_from_pretrained_trust_remote_code",
285
+ "runtime_mode": "zerogpu_compatibility_compile_bypass",
286
  "last_error": _pipeline_error or "",
287
  "expected_output_type": "video",
288
+ "runtime": _runtime_report(),
289
  }
290
 
291
 
292
  # ---------------------------------------------------------------------------
293
  # Generation endpoint — real inference guarded by @spaces.GPU
294
  # ---------------------------------------------------------------------------
 
 
 
 
295
 
296
+ def _gpu_duration(prompt, num_blocks, num_inference_steps, seed):
297
+ # Reserve enough time for lazy load + forward passes.
298
+ # Keep conservative for ZeroGPU quota/queue behavior.
299
+ try:
300
+ blocks = int(num_blocks)
301
+ steps = int(num_inference_steps)
302
+ except Exception:
303
+ blocks = 1
304
+ steps = 4
305
+
306
+ return min(900, max(300, 180 + blocks * steps * 20))
307
 
308
+
309
+ @_spaces_gpu(duration=_gpu_duration, size="xlarge")
310
  def generate(prompt, num_blocks, num_inference_steps, seed):
311
+ pipe = _load_pipeline()
312
+
313
+ if pipe is None:
314
  err = _pipeline_error or "Pipeline not loaded (unknown failure)"
315
  raise RuntimeError(f"Generation unavailable: {err}")
316
 
 
321
  num_inference_steps = int(num_inference_steps)
322
  seed = int(seed)
323
 
324
+ # Conservative bounds for ZeroGPU compatibility mode.
325
+ if num_blocks < 1 or num_blocks > 3:
326
+ raise ValueError("num_blocks must be between 1 and 3 in ZeroGPU compatibility mode.")
327
+
328
+ if num_inference_steps < 1 or num_inference_steps > 8:
329
+ raise ValueError(
330
+ "num_inference_steps must be between 1 and 8 in ZeroGPU compatibility mode."
331
+ )
332
 
333
  device = "cuda"
334
+
335
+ # Ensure the pipeline is on CUDA inside the ZeroGPU-decorated function.
336
+ try:
337
+ if hasattr(pipe, "device"):
338
+ current = str(pipe.device)
339
+ if "cuda" not in current:
340
+ _log("Moving pipeline to cuda ...")
341
+ pipe.to(device)
342
+ except Exception as e:
343
+ _log(f"Pipeline device check/move warning: {type(e).__name__}: {e}")
344
 
345
  frames = []
346
  state = PipelineState()
347
+
348
+ try:
349
+ generator = torch.Generator(device=device).manual_seed(seed)
350
+ except Exception as e:
351
+ _log(f"CUDA generator failed, falling back to CPU generator: {type(e).__name__}: {e}")
352
+ generator = torch.Generator(device="cpu").manual_seed(seed)
353
 
354
  try:
355
  for block_idx in range(num_blocks):
356
  _log(f"Block {block_idx + 1}/{num_blocks}")
357
+
358
+ state = pipe(
359
  state,
360
  prompt=[prompt],
361
  num_inference_steps=num_inference_steps,
362
  num_blocks=num_blocks,
363
  block_idx=block_idx,
364
+ generator=generator,
365
+ )
366
+
367
+ videos = state.values.get("videos")
368
+ if not videos:
369
+ raise RuntimeError("Pipeline state did not contain `videos` after inference.")
370
+
371
+ frames.extend(videos[0])
372
+
373
  except Exception as e:
374
+ _log(f"Inference failed at block {locals().get('block_idx', 'unknown')}: {e}")
375
+ traceback.print_exc()
376
+ raise RuntimeError(
377
+ f"Inference error at block {locals().get('block_idx', 'unknown')}: {e}"
378
+ )
379
+
380
+ if not frames:
381
+ raise RuntimeError("No frames were generated.")
382
 
383
  output_path = "/tmp/krea_output.mp4"
384
  export_to_video(frames, output_path, fps=24)
385
+
386
  _log(f"Saved video to {output_path}")
387
  return output_path
388
 
 
390
  # ---------------------------------------------------------------------------
391
  # Gradio app
392
  # ---------------------------------------------------------------------------
393
+
394
  with gr.Blocks(title="Krea Realtime Video 14B") as demo:
395
  gr.Markdown(
396
  "# Krea Realtime Video 14B\n\n"
397
+ "This Space attempts **real local inference** for the Krea Realtime 14B "
398
+ "text-to-video model using the Diffusers `ModularPipeline` path.\n\n"
399
+ "⚠️ **ZeroGPU compatibility mode**: `torch.compile` is disabled because "
400
+ "ZeroGPU does not support it. This may be slower than the optimized Krea runtime.\n\n"
401
+ "Start with 1 block and 4 steps to validate the runtime before increasing settings."
402
  )
403
 
404
  with gr.Row():
 
406
  prompt = gr.Textbox(
407
  label="Prompt",
408
  placeholder="e.g., a cat sitting on a boat",
409
+ lines=2,
410
+ )
411
+
412
  num_blocks = gr.Slider(
413
+ minimum=1,
414
+ maximum=3,
415
+ value=1,
416
+ step=1,
417
+ label="Number of Blocks (ZeroGPU-safe range)",
418
+ )
419
+
420
  num_inference_steps = gr.Slider(
421
+ minimum=1,
422
+ maximum=8,
423
+ value=4,
424
+ step=1,
425
+ label="Inference Steps per Block",
426
+ )
427
+
428
  seed = gr.Number(value=42, precision=0, label="Seed")
429
+
430
  generate_btn = gr.Button("Generate Video", variant="primary")
431
 
432
  with gr.Column():
 
434
 
435
  gr.Examples(
436
  examples=[
437
+ ["a cat sitting on a boat", 1, 4, 42],
438
+ ["a futuristic city at sunset", 1, 4, 123],
439
+ ["a panda playing guitar in a forest", 1, 4, 7],
440
  ],
441
  inputs=[prompt, num_blocks, num_inference_steps, seed],
442
  outputs=output_video,
443
  fn=generate,
444
+ cache_examples=False,
445
+ )
446
 
447
  generate_btn.click(
448
  generate,
449
  inputs=[prompt, num_blocks, num_inference_steps, seed],
450
  outputs=output_video,
451
+ api_name="generate",
452
+ )
453
 
454
+ # Structured health endpoint.
455
  demo.load(
456
  lambda: health(),
457
  None,
458
  gr.JSON(),
459
+ api_name="health",
460
+ )
461
+
462
 
463
  if __name__ == "__main__":
464
+ demo.queue().launch(
465
+ server_name="0.0.0.0",
466
+ server_port=7860,
467
+ show_error=True,
468
+ )