LTX-2.5-diffusers-mini / ltx /zerogpu_duration.py
John6666's picture
Upload 39 files
e8b6587 verified
Raw
History Blame Contribute Delete
12.1 kB
"""ZeroGPU reservation estimator, CPU preflight, and user-facing preview."""
from __future__ import annotations
from math import ceil, isfinite
import gradio as gr
from space_config import FRAME_RATE, STANDARD_MAX_SECONDS
from .app_config import (
EFFECTIVE_AUTO_DURATION_MAX_SECONDS,
IS_FULL_SFT_PROFILE,
)
from .conditioning import mode_from_condition_images
from .ui_controls import frames_from_seconds, parse_resolution_value
# Explicit policy coefficients. Runtime evidence records estimator revision and environment identity.
DURATION_ESTIMATOR_REVISION = "p34r0-v2"
FULL_SFT_DURATION_25F_FLOOR_SECONDS = 90
FULL_SFT_DURATION_LONG_FLOOR_SECONDS = 110
FULL_SFT_DURATION_SHORT_FIXED_SECONDS = 24.0
FULL_SFT_DURATION_SHORT_SECONDS_PER_VIDEO_SECOND = 2.5
FULL_SFT_DURATION_LONG_FIXED_SECONDS = 16.0
FULL_SFT_DURATION_LONG_SECONDS_PER_VIDEO_SECOND = 10.2
FULL_SFT_DURATION_LONG_GUARD_RATIO = 1.10
FULL_SFT_DURATION_LONG_GUARD_SECONDS = 5.0
# P29 long center already includes the measured Conv-VAE tail. P33 measured the
# 721f Diffusion Decoder tail on the same P28R4 two-step contract. After
# normalizing P33 to P28R4's cold Stage-2 adapter transfer, the decoder path adds
# ~26.1s over Conv VAE at 30s. Keep the historical 6s fixed term and scale the
# incremental temporal cost at 2/3s per realized video second.
FULL_SFT_DURATION_LONG_DIFFUSION_DECODER_FIXED_SECONDS = 6.0
FULL_SFT_DURATION_LONG_DIFFUSION_DECODER_SECONDS_PER_VIDEO_SECOND = 2.0 / 3.0
DURATION_LORA_ALLOWANCE_SECONDS = 8.0
DURATION_CONDITION_ALLOWANCE_SECONDS = {
"T2V": 0.0, "I2V": 2.0, "FLF2V": 4.0, "KEYFRAME": 4.0, "KEYFRAME_FLF2V": 6.0,
}
DISTILLED_DURATION_SHORT_FIXED_SECONDS = 8.0
DISTILLED_DURATION_SHORT_SECONDS_PER_VIDEO_SECOND = 1.2
DISTILLED_DURATION_LONG_FIXED_SECONDS = 18.0
DISTILLED_DURATION_LONG_SECONDS_PER_VIDEO_SECOND = 1.8
class DurationEstimateError(ValueError):
"""A user/config input prevented a safe ZeroGPU reservation estimate."""
def estimate_zerogpu_duration_seconds(
start_image_path, middle_image_path, end_image_path, duration_seconds, resolution_key, selected_loras,
use_diffusion_decoder, use_auto_duration,
) -> int:
"""Strict estimator shared by UI, CPU preflight, and the ZeroGPU scheduler callable."""
try:
width, height = parse_resolution_value(resolution_key)
reservation_seconds = (
EFFECTIVE_AUTO_DURATION_MAX_SECONDS if bool(use_auto_duration) else float(duration_seconds)
)
except Exception as exc:
raise DurationEstimateError(f"invalid duration/resolution input: {exc}") from exc
if not isfinite(reservation_seconds) or reservation_seconds <= 0:
raise DurationEstimateError(f"duration must be a finite positive value, got {reservation_seconds!r}")
frames = frames_from_seconds(reservation_seconds)
realized_seconds = (frames - 1) / FRAME_RATE
area_ratio = max(1.0, (width * height) / float(512 * 512))
lora_count = len(selected_loras or [])
mode = mode_from_condition_images(start_image_path, middle_image_path, end_image_path)
if mode == "INVALID_CONDITION_WITHOUT_START":
raise DurationEstimateError("Middle/End frame requires a Start frame")
if middle_image_path and bool(use_auto_duration):
raise DurationEstimateError("Middle keyframe requires Manual Duration so its midpoint is known before Stage 1")
condition_overhead = DURATION_CONDITION_ALLOWANCE_SECONDS.get(mode)
if condition_overhead is None:
raise DurationEstimateError(f"unsupported conditioning mode for duration estimate: {mode}")
lora_overhead = DURATION_LORA_ALLOWANCE_SECONDS * lora_count
# The distilled profile keeps the P0R1 absolute decoder calibration. For
# Full/SFT long requests, the P29 center already contains the Conv-VAE tail,
# so only the measured Diffusion-vs-Conv increment belongs on top.
distilled_decoder_overhead = (6.0 + realized_seconds) if bool(use_diffusion_decoder) else 0.0
full_sft_decoder_increment = (
FULL_SFT_DURATION_LONG_DIFFUSION_DECODER_FIXED_SECONDS
+ (FULL_SFT_DURATION_LONG_DIFFUSION_DECODER_SECONDS_PER_VIDEO_SECOND * realized_seconds)
if bool(use_diffusion_decoder) else 0.0
)
if IS_FULL_SFT_PROFILE:
short_estimate = (
FULL_SFT_DURATION_SHORT_FIXED_SECONDS
+ (FULL_SFT_DURATION_SHORT_SECONDS_PER_VIDEO_SECOND * realized_seconds * area_ratio)
+ condition_overhead + lora_overhead + full_sft_decoder_increment
)
short_requested = int(ceil(short_estimate))
if frames <= 25:
return int(max(FULL_SFT_DURATION_25F_FLOOR_SECONDS, short_requested))
long_center = (
FULL_SFT_DURATION_LONG_FIXED_SECONDS
+ (FULL_SFT_DURATION_LONG_SECONDS_PER_VIDEO_SECOND * realized_seconds * area_ratio)
+ condition_overhead + lora_overhead + full_sft_decoder_increment
)
long_requested = int(ceil(
(FULL_SFT_DURATION_LONG_GUARD_RATIO * long_center) + FULL_SFT_DURATION_LONG_GUARD_SECONDS
))
return int(max(FULL_SFT_DURATION_LONG_FLOOR_SECONDS, short_requested, long_requested))
if realized_seconds > STANDARD_MAX_SECONDS:
estimate = (
DISTILLED_DURATION_LONG_FIXED_SECONDS
+ (DISTILLED_DURATION_LONG_SECONDS_PER_VIDEO_SECOND * realized_seconds * area_ratio)
+ condition_overhead + lora_overhead + distilled_decoder_overhead
)
else:
estimate = (
DISTILLED_DURATION_SHORT_FIXED_SECONDS
+ (DISTILLED_DURATION_SHORT_SECONDS_PER_VIDEO_SECOND * realized_seconds * area_ratio)
+ condition_overhead + lora_overhead + distilled_decoder_overhead
)
return int(max(10, ceil(estimate)))
def generation_duration(prompt, start_image_path, middle_image_path, end_image_path, duration_seconds, experimental_long, resolution_key, seed, randomize_seed,
selected_loras, lora_strength, custom_loras, prepared_loras, use_diffusion_decoder, use_auto_duration, session_id, history, *args) -> int: # noqa: ARG001
"""ZeroGPU scheduler callable. Strict by design; CPU preflight runs first in product UI."""
del prompt, experimental_long, seed, randomize_seed, lora_strength, custom_loras, prepared_loras, session_id, history, args
return estimate_zerogpu_duration_seconds(
start_image_path, middle_image_path, end_image_path, duration_seconds, resolution_key, selected_loras,
use_diffusion_decoder, use_auto_duration,
)
def generation_duration_preflight(
start_image_path, middle_image_path, end_image_path, duration_seconds, resolution_key, selected_loras,
use_diffusion_decoder, use_auto_duration,
):
try:
requested = estimate_zerogpu_duration_seconds(
start_image_path, middle_image_path, end_image_path, duration_seconds, resolution_key, selected_loras,
use_diffusion_decoder, use_auto_duration,
)
except DurationEstimateError as exc:
raise gr.Error(
"ZeroGPU duration could not be estimated safely. GPU quota was not requested. "
f"Check Duration/Resolution/input mode and try again. Details: {exc}"
) from exc
return f"Preflight ready · ZeroGPU reservation **{requested}s**. GPU request starts next."
def zerogpu_duration_panel(start_image_path, middle_image_path, end_image_path, duration_seconds, experimental_long, resolution_key, selected_loras, use_diffusion_decoder, use_auto_duration): # noqa: ARG001
try:
width, height = parse_resolution_value(resolution_key)
except Exception:
return "⚠️ Resolution must be entered as WIDTH × HEIGHT with both dimensions multiples of 64."
reservation_seconds = EFFECTIVE_AUTO_DURATION_MAX_SECONDS if bool(use_auto_duration) else float(duration_seconds)
frames = frames_from_seconds(reservation_seconds)
realized_seconds = (frames - 1) / FRAME_RATE
try:
requested = estimate_zerogpu_duration_seconds(
start_image_path, middle_image_path, end_image_path, duration_seconds, resolution_key, selected_loras,
use_diffusion_decoder, use_auto_duration,
)
except DurationEstimateError as exc:
return (
"⚠️ **ZeroGPU duration estimate unavailable.** "
f"GPU generation will stop in CPU preflight instead of requesting a fallback duration. Details: `{exc}`"
)
mode = mode_from_condition_images(start_image_path, middle_image_path, end_image_path)
lora_count = len(selected_loras or [])
mode_note = (
"⚠️ Middle/End frame requires a Start frame; generation will be rejected until a Start frame is supplied."
if mode == "INVALID_CONDITION_WITHOUT_START" else f"Input mode: **{mode}**."
)
if requested <= 60:
quota_note = "tight/ordinary reservation range for this app"
elif requested <= 120:
quota_note = "heavy reservation — quota impact is significant"
elif requested <= 300:
quota_note = "very heavy reservation — may consume a large share of a visitor's daily quota"
else:
quota_note = "aggressive reservation — visitor tier caps or remaining quota may reject it before execution"
full_sft_probe_note = ""
if IS_FULL_SFT_PROFILE and frames > 25:
full_sft_probe_note = (
" \n**Full/SFT >25f reservation model:** P27/P28 Stage-1 scaling + P28R4 cold Conv-VAE tail define the base model; P33's 721f Diffusion Decoder witness now calibrates decoder-vs-Conv incremental cost. "
"The 110s value is only the minimum floor; long requests scale above it. Runtime evidence is hardware-specific; unverified combinations remain available rather than being product-blocked."
)
long_note = ""
if realized_seconds > STANDARD_MAX_SECONDS:
long_note = (
" \n⚠️ **Experimental video length.** The 30s / 721f endpoint is live-passed for distilled T2V and I2V at 512×512; "
"long durations remain opt-in and quota/VRAM cost rises with length."
)
flf_note = ""
if mode == "FLF2V":
flf_note = (
" \n⚠️ **FLF2V remains experimental.** 512×512/25f current-NF4 has a returned ZeroGPU 48GB PASS; "
"longer durations and other FLF2V resolutions remain unvalidated."
)
keyframe_note = ""
if middle_image_path:
keyframe_note = (
" \n⚠️ **Middle keyframe is experimental.** It is fixed at the nearest latent-grid midpoint of Manual Duration; "
"Auto Duration is intentionally rejected before GPU reservation while a Middle image is present."
)
vram_note = ""
if width == 512 and height == 512 and mode == "T2V":
predicted_gib = 26.5189 + (0.0254508 * frames)
vram_note = (
f" \nRough 512×512 T2V peak-allocation extrapolation: **{predicted_gib:.1f} GiB** "
"(measured linear trend through 241f; not a guarantee)."
)
duration_contract = (
f"Auto Duration · reservation basis {EFFECTIVE_AUTO_DURATION_MAX_SECONDS:.1f}s cap"
if bool(use_auto_duration) else f"Manual · {frames}f · {realized_seconds:.2f}s video"
)
auto_note = (
" \nAuto Duration is predicted only after GPU allocation; ZeroGPU therefore reserves against the configured max cap, "
"not the eventual model-predicted length. The actual output is snapped to the upstream 8k+1 grid and may be shorter than the cap."
if bool(use_auto_duration) else ""
)
return (
f"**Estimated ZeroGPU request: {requested} seconds** — {quota_note}. \n"
f"{mode_note} Contract: **{width}×{height} · {duration_contract} · {lora_count} LoRA(s) · decoder {'Diffusion' if use_diffusion_decoder else 'Conv VAE'}**. \n"
"This is the requested GPU reservation/quota budget, not the video duration or a guaranteed wall-clock runtime."
f"{auto_note}{full_sft_probe_note}{long_note}{flf_note}{keyframe_note}{vram_note}"
)