multimodalart HF Staff commited on
Commit
3a15024
·
verified ·
1 Parent(s): f7944df

Fit the ZeroGPU booking to measurement (89s vs 125s for the default request) and guard the ceiling

Browse files
Files changed (1) hide show
  1. app.py +39 -17
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, from the packed video rows it is about to denoise: linear in the rows for the
216
- # matmuls, quadratic for the attention, against the AoTI block package this Space runs.
 
 
 
217
  _DUR_B, _DUR_C = 1.1745e-4, 3.8396e-9
218
- # The two resident decoders, the post chain and the mux, which scale with the output rather than with the step
219
- # count. `_DEFAULT_CANVAS_PIXELS` is 960x544x124, the default request.
220
- _DECODE_BASE, _DECODE_PER_DEFAULT_CANVAS, _DEFAULT_CANVAS_PIXELS = 15, 15, 960 * 544 * 124
221
- # FILM, per *emitted* intermediate frame at the default canvas. Re-measured against the live Space; a 2x pass over
222
- # 124 frames at 960x544 is 123 of them.
223
- _FILM_PER_FRAME = 0.16
224
- # `pack` mode: only the ~10 GB of VAEs move on a cold worker.
225
- _PLACEMENT_ALLOWANCE, _PAD = 12, 10
 
 
 
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
- film = 0.0
254
- if multiplier > 1 and FILM is not None:
255
- film = (num_frames - 1) * (multiplier - 1) * _FILM_PER_FRAME * pixel_ratio
256
- return max(60, int(denoise + decode + film) + _PLACEMENT_ALLOWANCE + _PAD)
 
 
 
 
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
- return path, denoised, post_seconds, int(frames.shape[0]), fps, multiplier
 
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
- path, denoise_seconds, post_seconds, out_frames, fps, multiplier = _generate(
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}"