Cydercoder commited on
Commit
afa8596
·
verified ·
1 Parent(s): 590e6fe

second commit

Browse files
Files changed (2) hide show
  1. README.md +7 -2
  2. app.py +45 -38
README.md CHANGED
@@ -6,6 +6,9 @@ colorTo: blue
6
  sdk: gradio
7
  app_file: app.py
8
  pinned: false
 
 
 
9
  models:
10
  - Lightricks/LTX-Video-0.9.5
11
  ---
@@ -74,8 +77,10 @@ browser, on any device, forever (as long as the Space stays public/free).
74
  (Data from the official [ZeroGPU docs](https://huggingface.co/docs/hub/en/spaces-zerogpu),
75
  Aug 2026. Free accounts can host up to 2 ZeroGPU Spaces.)
76
 
77
- - One short clip (~5s, 30 steps) consumes roughly **1–2 minutes** of that quota,
78
- so a free account realistically gets **~3–5 videos per day**.
 
 
79
  - Queue priority is based on remaining quota — use your quota early in the day.
80
  - There is **no unlimited free tier anywhere** for video generation. GPUs are
81
  expensive; that's physics, not a scam.
 
6
  sdk: gradio
7
  app_file: app.py
8
  pinned: false
9
+ startup_duration_timeout: 600
10
+ hardware:
11
+ accelerator: zero-gpu
12
  models:
13
  - Lightricks/LTX-Video-0.9.5
14
  ---
 
77
  (Data from the official [ZeroGPU docs](https://huggingface.co/docs/hub/en/spaces-zerogpu),
78
  Aug 2026. Free accounts can host up to 2 ZeroGPU Spaces.)
79
 
80
+ - ZeroGPU bills each request by its **reserved duration** (x1.5 on current
81
+ hardware), not the exact seconds used. The app reserves just enough for one
82
+ clip, so a free account realistically gets **~2–4 videos per day** (short,
83
+ low-resolution clips cost less and stretch it further).
84
  - Queue priority is based on remaining quota — use your quota early in the day.
85
  - There is **no unlimited free tier anywhere** for video generation. GPUs are
86
  expensive; that's physics, not a scam.
app.py CHANGED
@@ -10,6 +10,9 @@ import tempfile
10
 
11
  import gradio as gr
12
  import spaces
 
 
 
13
 
14
  # The 2B LTX-Video checkpoint (0.9.5): much lighter than the 13B main repo
15
  # (~15GB bf16 vs ~38GB), so it fits the 48GB ZeroGPU slice comfortably.
@@ -30,35 +33,37 @@ RESOLUTIONS = {
30
 
31
  FPS = 24
32
 
33
- # Pipelines are loaded lazily on the first generation (the model files are
34
- # prefetched at build time via the `models:` key in README.md, so this only
35
- # loads them into GPU memory once). Keeps the Space from OOM-ing at startup.
36
- _text_pipe = None
37
- _image_pipe = None
38
-
39
-
40
- def _load_pipes():
41
- """Load the text-to-video and image-to-video pipelines once, then reuse."""
42
- global _text_pipe, _image_pipe
43
-
44
- import torch
45
- from diffusers import LTXImageToVideoPipeline, LTXPipeline
46
-
47
- if _text_pipe is None:
48
- _text_pipe = LTXPipeline.from_pretrained(MODEL_ID, torch_dtype=torch.bfloat16)
49
- _text_pipe.to("cuda")
50
- _text_pipe.vae.enable_slicing()
51
- _text_pipe.vae.enable_tiling()
52
-
53
- if _image_pipe is None:
54
- _image_pipe = LTXImageToVideoPipeline.from_pretrained(
55
- MODEL_ID, torch_dtype=torch.bfloat16
56
- )
57
- _image_pipe.to("cuda")
58
- _image_pipe.vae.enable_slicing()
59
- _image_pipe.vae.enable_tiling()
60
-
61
- return _text_pipe, _image_pipe
 
 
62
 
63
 
64
  def _get_duration(
@@ -66,12 +71,17 @@ def _get_duration(
66
  ):
67
  """Return the GPU runtime budget for this call (seconds).
68
 
69
- ZeroGPU charges quota based on this reservation, so keep it tight.
70
- The first call also loads the model into GPU memory, so give it more room.
 
71
  """
72
- if _text_pipe is None:
73
- return 240 # first call: model load + generation
74
- return max(45, int(num_steps * 1.5) + 15)
 
 
 
 
75
 
76
 
77
  @spaces.GPU(duration=_get_duration)
@@ -87,9 +97,6 @@ def generate_video(
87
  guidance,
88
  ):
89
  """Generate a video from a text prompt (or an image + prompt)."""
90
- import torch
91
- from diffusers.utils import export_to_video
92
-
93
  if not prompt or not prompt.strip():
94
  raise gr.Error("Please write a prompt first.")
95
 
@@ -101,7 +108,7 @@ def generate_video(
101
  seed = random.randint(0, 2**31 - 1)
102
  generator = torch.Generator(device="cuda").manual_seed(seed)
103
 
104
- text_pipe, image_pipe = _load_pipes()
105
 
106
  # Timestep-aware VAE settings recommended for LTX-Video 0.9.1+.
107
  decode_kwargs = {"decode_timestep": 0.05, "decode_noise_scale": 0.025}
 
10
 
11
  import gradio as gr
12
  import spaces
13
+ import torch
14
+ from diffusers import LTXImageToVideoPipeline, LTXPipeline
15
+ from diffusers.utils import export_to_video
16
 
17
  # The 2B LTX-Video checkpoint (0.9.5): much lighter than the 13B main repo
18
  # (~15GB bf16 vs ~38GB), so it fits the 48GB ZeroGPU slice comfortably.
 
33
 
34
  FPS = 24
35
 
36
+ # ---------------------------------------------------------------------------
37
+ # Load the model ONCE at startup (module level).
38
+ #
39
+ # ZeroGPU rule: place models on cuda at module level so loading happens OUTSIDE
40
+ # the quota-charged generation call. A lazy first load inside @spaces.GPU would
41
+ # need a huge reservation (240s -> 360s billed after ZeroGPU's 1.5x factor),
42
+ # which exceeds the 300s free daily quota and gets rejected with
43
+ # "duration is larger than the maximum allowed". Preloading keeps every call
44
+ # small, so the free quota buys ~2-4 videos per day.
45
+ # The model files are prefetched at build time via the `models:` key in
46
+ # README.md, so this reads from local disk and takes under a minute.
47
+ # ---------------------------------------------------------------------------
48
+ print("Loading LTX-Video model (once, at startup)...")
49
+ _text_pipe = LTXPipeline.from_pretrained(MODEL_ID, torch_dtype=torch.bfloat16)
50
+ _text_pipe.to("cuda")
51
+ _text_pipe.vae.enable_slicing()
52
+ _text_pipe.vae.enable_tiling()
53
+
54
+ # The image-to-video pipeline reuses the same components, so it adds
55
+ # almost no extra memory.
56
+ _image_pipe = LTXImageToVideoPipeline.from_pretrained(
57
+ MODEL_ID,
58
+ transformer=_text_pipe.transformer,
59
+ vae=_text_pipe.vae,
60
+ text_encoder=_text_pipe.text_encoder,
61
+ tokenizer=_text_pipe.tokenizer,
62
+ scheduler=_text_pipe.scheduler,
63
+ torch_dtype=torch.bfloat16,
64
+ )
65
+ _image_pipe.to("cuda")
66
+ print("Model ready.")
67
 
68
 
69
  def _get_duration(
 
71
  ):
72
  """Return the GPU runtime budget for this call (seconds).
73
 
74
+ The model is already loaded, so this only needs to cover generation.
75
+ ZeroGPU bills the reservation (x1.5 on current hardware) against the daily
76
+ quota, so keep it tight: shorter durations = more videos per day.
77
  """
78
+ width, height = RESOLUTIONS[resolution]
79
+ pixel_scale = (width * height) / (512 * 704)
80
+ step_scale = num_steps / 30.0
81
+ estimate = num_frames * 0.25 * pixel_scale * step_scale
82
+ total = int((estimate + 15) * 1.2) # + VAE decode/export overhead, 20% margin
83
+ total = max(45, total)
84
+ return min(total, 120)
85
 
86
 
87
  @spaces.GPU(duration=_get_duration)
 
97
  guidance,
98
  ):
99
  """Generate a video from a text prompt (or an image + prompt)."""
 
 
 
100
  if not prompt or not prompt.strip():
101
  raise gr.Error("Please write a prompt first.")
102
 
 
108
  seed = random.randint(0, 2**31 - 1)
109
  generator = torch.Generator(device="cuda").manual_seed(seed)
110
 
111
+ text_pipe, image_pipe = _text_pipe, _image_pipe
112
 
113
  # Timestep-aware VAE settings recommended for LTX-Video 0.9.1+.
114
  decode_kwargs = {"decode_timestep": 0.05, "decode_noise_scale": 0.025}