Spaces:
Running on Zero
Running on Zero
Fit the ZeroGPU booking to measurement (89s vs 125s for the default request) and guard the ceiling
Browse files
app.py
CHANGED
|
@@ -212,17 +212,23 @@ def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewri
|
|
| 212 |
return handle.get_tensor("prompt_embeds"), handle.get_tensor("text_token_tags"), metadata, plan
|
| 213 |
|
| 214 |
|
| 215 |
-
# Seconds of GPU one request needs
|
| 216 |
-
#
|
|
|
|
|
|
|
|
|
|
| 217 |
_DUR_B, _DUR_C = 1.1745e-4, 3.8396e-9
|
| 218 |
-
# The two resident decoders,
|
| 219 |
-
#
|
| 220 |
-
_DECODE_BASE, _DECODE_PER_DEFAULT_CANVAS, _DEFAULT_CANVAS_PIXELS =
|
| 221 |
-
#
|
| 222 |
-
#
|
| 223 |
-
_FILM_PER_FRAME = 0.
|
| 224 |
-
# `pack` mode: only the ~10 GB of VAEs move on a cold worker.
|
| 225 |
-
_PLACEMENT_ALLOWANCE,
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
|
| 228 |
def get_duration(
|
|
@@ -250,10 +256,14 @@ def get_duration(
|
|
| 250 |
denoise = steps * (_DUR_B * rows + _DUR_C * rows**2)
|
| 251 |
pixel_ratio = (height * width) / (960 * 544)
|
| 252 |
decode = _DECODE_BASE + _DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / _DEFAULT_CANVAS_PIXELS
|
| 253 |
-
|
| 254 |
-
if multiplier > 1 and FILM is
|
| 255 |
-
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
|
| 259 |
@spaces.GPU(duration=get_duration, size=GPU_SIZE)
|
|
@@ -282,6 +292,7 @@ def _generate(
|
|
| 282 |
|
| 283 |
global FILM
|
| 284 |
|
|
|
|
| 285 |
if PLACEMENT == "lazy":
|
| 286 |
PIPE.to("cuda")
|
| 287 |
elif PLACEMENT == "pack":
|
|
@@ -337,7 +348,8 @@ def _generate(
|
|
| 337 |
path = os.path.join(directory, f"pk-h3-{int(time.time() * 1000)}.mp4")
|
| 338 |
encode_video(frames, fps=fps, output_path=path, audio=audio, audio_sample_rate=sampling_rate)
|
| 339 |
|
| 340 |
-
|
|
|
|
| 341 |
|
| 342 |
|
| 343 |
def generate(
|
|
@@ -392,7 +404,7 @@ def generate(
|
|
| 392 |
return ImageOps.exif_transpose(Image.open(path)).convert("RGB") if path else None
|
| 393 |
|
| 394 |
progress(0.1, desc=f"Denoising {int(steps)} steps at {width}x{height}, {num_frames} frames ...")
|
| 395 |
-
|
| 396 |
prompt_embeds,
|
| 397 |
text_token_tags,
|
| 398 |
keyframe(first_frame),
|
|
@@ -406,6 +418,15 @@ def generate(
|
|
| 406 |
multiplier,
|
| 407 |
int(seed),
|
| 408 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
|
| 410 |
post = [f"RCAS {float(sharpen):.2f}" if float(sharpen) > 0 else "no sharpening"]
|
| 411 |
post.append(f"FILM {multiplier}x -> {fps} fps" if multiplier > 1 else f"{fps} fps")
|
|
@@ -414,7 +435,8 @@ def generate(
|
|
| 414 |
f"{int(steps)} steps of `{schedule_key}` · {' · '.join(post)} · seed {int(seed)}\n\n"
|
| 415 |
f"conditioner {condition_seconds:.0f}s ({plan['num_text_tokens']} tokens"
|
| 416 |
f"{', upsampled' if refined else ''}) · denoise + decode {denoise_seconds:.0f}s "
|
| 417 |
-
f"({denoise_seconds / max(1, int(steps)):.1f} s/step) · post {post_seconds:.0f}s"
|
|
|
|
| 418 |
)
|
| 419 |
if refined:
|
| 420 |
report += f"\n\n**Upsampled prompt**\n\n{refined}"
|
|
|
|
| 212 |
return handle.get_tensor("prompt_embeds"), handle.get_tensor("text_token_tags"), metadata, plan
|
| 213 |
|
| 214 |
|
| 215 |
+
# Seconds of GPU one request needs. Fitted to *this* Space, measured: booking a ceiling nobody reaches spends every
|
| 216 |
+
# visitor's ZeroGPU quota on nothing and costs the demo queue priority, so each term below is a measurement.
|
| 217 |
+
#
|
| 218 |
+
# The denoise loop, from the packed video rows it is about to run: linear in the rows for the matmuls, quadratic for
|
| 219 |
+
# the attention, against the AoTI block package this Space loads. 3.6 s/step at the default canvas.
|
| 220 |
_DUR_B, _DUR_C = 1.1745e-4, 3.8396e-9
|
| 221 |
+
# The two resident decoders, which scale with the output rather than with the step count. `_DEFAULT_CANVAS_PIXELS` is
|
| 222 |
+
# 960x544x124, the default request, where the pair measures ~7 s.
|
| 223 |
+
_DECODE_BASE, _DECODE_PER_DEFAULT_CANVAS, _DEFAULT_CANVAS_PIXELS = 2, 5.5, 960 * 544 * 124
|
| 224 |
+
# The workflow's post chain. RCAS is a handful of elementwise passes over the clip; FILM is per *emitted* intermediate
|
| 225 |
+
# frame (a 2x pass over 124 frames is 123 of them); the h264 mux is per frame actually written.
|
| 226 |
+
_POST_BASE, _FILM_PER_FRAME, _MUX_PER_FRAME = 2.0, 0.025, 0.02
|
| 227 |
+
# `pack` mode: only the ~10 GB of fp32 VAEs move, and only on a cold worker.
|
| 228 |
+
_PLACEMENT_ALLOWANCE, _MARGIN = 8, 1.15
|
| 229 |
+
# The ZeroGPU per-call ceiling. A booking above it is refused with `ZeroGPU illegal duration` once the request is
|
| 230 |
+
# already in flight, so `generate` checks it up front and says which knob to turn instead.
|
| 231 |
+
_MAX_BOOKING = int(os.environ.get("H3_MAX_BOOKING", "1500"))
|
| 232 |
|
| 233 |
|
| 234 |
def get_duration(
|
|
|
|
| 256 |
denoise = steps * (_DUR_B * rows + _DUR_C * rows**2)
|
| 257 |
pixel_ratio = (height * width) / (960 * 544)
|
| 258 |
decode = _DECODE_BASE + _DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / _DEFAULT_CANVAS_PIXELS
|
| 259 |
+
|
| 260 |
+
if multiplier > 1 and FILM is None:
|
| 261 |
+
multiplier = 1
|
| 262 |
+
out_frames = (num_frames - 1) * multiplier + 1 if multiplier > 1 else num_frames
|
| 263 |
+
film = (num_frames - 1) * (multiplier - 1) * _FILM_PER_FRAME * pixel_ratio
|
| 264 |
+
post = _POST_BASE + film + out_frames * _MUX_PER_FRAME * pixel_ratio
|
| 265 |
+
|
| 266 |
+
return max(60, int((denoise + decode + post) * _MARGIN) + _PLACEMENT_ALLOWANCE)
|
| 267 |
|
| 268 |
|
| 269 |
@spaces.GPU(duration=get_duration, size=GPU_SIZE)
|
|
|
|
| 292 |
|
| 293 |
global FILM
|
| 294 |
|
| 295 |
+
booked = time.time()
|
| 296 |
if PLACEMENT == "lazy":
|
| 297 |
PIPE.to("cuda")
|
| 298 |
elif PLACEMENT == "pack":
|
|
|
|
| 348 |
path = os.path.join(directory, f"pk-h3-{int(time.time() * 1000)}.mp4")
|
| 349 |
encode_video(frames, fps=fps, output_path=path, audio=audio, audio_sample_rate=sampling_rate)
|
| 350 |
|
| 351 |
+
# `booked` to here is what `get_duration` had to predict, so it is what the report prints it against.
|
| 352 |
+
return path, denoised, post_seconds, time.time() - booked, int(frames.shape[0]), fps, multiplier
|
| 353 |
|
| 354 |
|
| 355 |
def generate(
|
|
|
|
| 404 |
return ImageOps.exif_transpose(Image.open(path)).convert("RGB") if path else None
|
| 405 |
|
| 406 |
progress(0.1, desc=f"Denoising {int(steps)} steps at {width}x{height}, {num_frames} frames ...")
|
| 407 |
+
call = (
|
| 408 |
prompt_embeds,
|
| 409 |
text_token_tags,
|
| 410 |
keyframe(first_frame),
|
|
|
|
| 418 |
multiplier,
|
| 419 |
int(seed),
|
| 420 |
)
|
| 421 |
+
# The same call `spaces` will book the worker with, so the report can show the fit against the measurement.
|
| 422 |
+
booked_seconds = get_duration(*call)
|
| 423 |
+
if booked_seconds > _MAX_BOOKING:
|
| 424 |
+
raise gr.Error(
|
| 425 |
+
f"That would book {booked_seconds}s of GPU, over the {_MAX_BOOKING}s ZeroGPU ceiling. Shorten the "
|
| 426 |
+
f"**duration**, drop the **steps**, or pick a smaller **target dimension** — the denoise loop is "
|
| 427 |
+
f"quadratic in the canvas."
|
| 428 |
+
)
|
| 429 |
+
path, denoise_seconds, post_seconds, gpu_seconds, out_frames, fps, multiplier = _generate(*call)
|
| 430 |
|
| 431 |
post = [f"RCAS {float(sharpen):.2f}" if float(sharpen) > 0 else "no sharpening"]
|
| 432 |
post.append(f"FILM {multiplier}x -> {fps} fps" if multiplier > 1 else f"{fps} fps")
|
|
|
|
| 435 |
f"{int(steps)} steps of `{schedule_key}` · {' · '.join(post)} · seed {int(seed)}\n\n"
|
| 436 |
f"conditioner {condition_seconds:.0f}s ({plan['num_text_tokens']} tokens"
|
| 437 |
f"{', upsampled' if refined else ''}) · denoise + decode {denoise_seconds:.0f}s "
|
| 438 |
+
f"({denoise_seconds / max(1, int(steps)):.1f} s/step) · post {post_seconds:.0f}s · "
|
| 439 |
+
f"GPU {gpu_seconds:.0f}s of {booked_seconds}s booked"
|
| 440 |
)
|
| 441 |
if refined:
|
| 442 |
report += f"\n\n**Upsampled prompt**\n\n{refined}"
|