Spaces:
Running on Zero
Running on Zero
File size: 22,000 Bytes
1593be5 423e21d 9a5ea10 423e21d 1593be5 423e21d 1593be5 423e21d 1593be5 31c6b21 1593be5 31c6b21 1593be5 e295293 1593be5 e295293 1593be5 e295293 1593be5 423e21d 1593be5 e295293 423e21d e295293 423e21d e295293 423e21d 1593be5 423e21d 1593be5 e30756a 1593be5 e295293 423e21d 1593be5 423e21d 1593be5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 | """Precomputed-results cache: chain policy, request canonicalization, lookup.
GPU time on the HF Space is rationed (ZeroGPU gives free-tier visitors a few
minutes a day, and load_pipeline's ~37.5 GB of checkpoints is loaded from
inside the @spaces.GPU allocation). So the default configuration of each tab
is generated ahead of time on the cluster and served from disk: the page shows
a real model output the moment it opens, and clicking Generate without
changing anything costs nothing.
Two halves, and they have to agree exactly:
- scripts/precompute_demo_outputs.py calls resolve_a2a / resolve_fp to build
a *canonical spec* dict, runs the model, and writes both to the manifest.
- app.py calls the same resolvers on the live UI arguments and looks up the
spec by plain dict equality.
Everything that can change a generated result is in the spec; nothing that
can't is (see _effective_overrides). That is what makes a fresh page load
compare equal to a run recorded weeks earlier -- and, just as importantly,
what makes a nudged slider miss instead of silently serving the wrong video.
This module deliberately does NOT import gradio or app: the precompute script
has to reach the chain-policy helpers without building ~1000 UI components.
"""
import hashlib
import json
import os
import traceback
from collections import namedtuple
from inference import (
CONFIGS, HYPERPARAM_PRESETS, FUTURE_CHAIN,
get_precomputed_file, load_precomputed_manifest,
)
# Bump when the manifest layout changes incompatibly; a mismatched manifest is
# ignored outright rather than half-understood.
SCHEMA_VERSION = 1
# --- modality registry (shared with the UI) -----------------------------------
# Fixed order for the hyperparameter accordion only (unrelated to the
# dynamic, chain-order-following output slots).
ALL_MODALITIES = ["rgb", "depth", "normal", "opticalflow", "dinov2", "vjepa", "siglip", "det", "caption", "transcription"]
TEXT_MODALITIES = {"caption", "transcription"}
# Which tuned HYPERPARAM_PRESETS set fits a given input modality -- dense
# visual/feature-map inputs carry a lot of information already, sparse
# text-like inputs need the more exploratory sparse-to-dense tuning.
DENSE_INPUT_MODALITIES = {"rgb", "depth", "normal", "opticalflow", "dinov2", "vjepa", "siglip"}
# Two recommended coarse-to-fine orders -- one for dense (pixel/feature-map)
# inputs, one for sparse (text-like) inputs -- each modality drops its own
# key from whichever list it belongs to. Previously each input modality had
# its own hand-tuned order; simplified to these two shared orders. Edit
# freely; the only rule is that a list must not contain its own key
# (enforced below, not hand-maintained per entry anymore).
DENSE_CHAIN = ["siglip", "dinov2", "vjepa", "caption", "transcription", "det", "depth", "normal", "opticalflow", "rgb"]
SPARSE_CHAIN = ["caption", "transcription", "siglip", "dinov2", "vjepa", "det", "depth", "normal", "opticalflow", "rgb"]
SPARSE_INPUT_MODALITIES = ("caption", "transcription", "det")
# Both source lists contain all 10 modalities (each is a target list for the
# other group's inputs too), so the dict keys must be restricted to each
# group's actual INPUT modalities -- iterating the full source lists here
# would let the second comprehension silently overwrite the first for every
# shared key.
COARSE_TO_FINE_BY_INPUT = {
**{mod: [m for m in DENSE_CHAIN if m != mod] for mod in DENSE_INPUT_MODALITIES},
**{mod: [m for m in SPARSE_CHAIN if m != mod] for mod in SPARSE_INPUT_MODALITIES},
}
DEFAULT_INPUT_MODALITY = "rgb"
DEFAULT_ABS_MODALITY = "depth"
DEFAULT_CHAIN_STATE = list(COARSE_TO_FINE_BY_INPUT[DEFAULT_INPUT_MODALITY])
# Future Prediction tab defaults. Shared by the UI (fp_targets_state), the
# page-load prefill and the precompute recipe: the prefill re-derives its
# lookup from the tab's actual default state, so if these three ever
# disagreed the cache would simply never be found.
DEFAULT_FP_OBS_MODALITY = DEFAULT_INPUT_MODALITY
DEFAULT_FP_SEED_TOKENS = 512 # "First 5 frames"
# Predict every modality we can, so the showcase shows the model's full
# any-modality reach rather than a two-card sample. The observed modality is
# excluded (its given frames are completed in place, not predicted).
DEFAULT_FP_TARGETS = [m for m in FUTURE_CHAIN if m != DEFAULT_FP_OBS_MODALITY]
# A hand-picked caption prompt shown as the first quick-pick chip in the
# Caption input mode -- unlike the rest of CAPTION_SAMPLES (real GT captions
# of curated gallery clips), this one has no backing example_stem, so it must
# be listed here explicitly for scripts/precompute_demo_outputs.py to also
# precompute it. Single source of truth: app.py imports this rather than
# hardcoding its own copy, so the two can't drift apart.
EXTRA_CAPTION_SAMPLE = (
"A young woman with light skin and reddish-blonde hair in pigtails is facing the camera. "
"She wears a green and blue tie-dye top and is in a room with posters on the wall. "
"She appears to be in the middle of a burp, exclaiming 'Oh'."
)
def hyperparam_preset_key_for_modality(input_modality):
return "rgb_to_others" if input_modality in DENSE_INPUT_MODALITIES else "text_to_rgb"
# Flat temp/cfg overrides for each tab's "Direct" preset -- deliberately a
# single fixed pair per modality family rather than a tuned HYPERPARAM_PRESETS
# set, since skipping the coarse-to-fine chain calls for simpler generation
# hyperparameters too.
DIRECT_HYPERPARAMS_A2A = {
**{m: {"temp": 2.0, "cfg": 4.0} for m in DENSE_INPUT_MODALITIES},
**{m: {"temp": 1.0, "cfg": 1.0} for m in TEXT_MODALITIES | {"det"}},
}
DIRECT_HYPERPARAMS_FP = {m: {"temp": 1.0, "cfg": 1.0} for m in ALL_MODALITIES}
# --- chain policy --------------------------------------------------------------
def clean_chain(input_modality, chain):
"""Drops the input modality from chain if it's stuck there from before
a modality switch (rather than wiping the whole chain), and dedupes.
"""
seen = set()
cleaned = []
for k in chain:
if k != input_modality and k not in seen:
cleaned.append(k)
seen.add(k)
return cleaned
def preset_chain(preset, input_modality):
"""The chain a preset stands for, given the current input modality.
Returns None for "custom" (leave the chain exactly as the user built it).
"""
if preset == "coarse":
fallback = ["caption", "dinov2", "depth", "rgb"]
return clean_chain(input_modality, list(COARSE_TO_FINE_BY_INPUT.get(input_modality, fallback)))
if preset == "direct":
return ["depth"] if input_modality == "rgb" else ["rgb"]
return None
def chain_for_input_change(preset, new_input_modality, current_chain):
"""What the chain becomes when the input modality changes: coarse
rebuilds its per-input ladder, direct keeps its single chosen target
while it stays valid (falling back to the direct default if the new
input swallowed it), custom just drops the new input from the chain.
"""
if preset == "coarse":
return preset_chain("coarse", new_input_modality)
cleaned = clean_chain(new_input_modality, current_chain)
if preset == "direct":
return cleaned[:1] if cleaned else preset_chain("direct", new_input_modality)
return cleaned
def fp_extra(cond_mode, cond_modality):
"""Effective conditioning modality: None when unconditional."""
return None if cond_mode == "none" else cond_modality
def fp_chain(seed_modality, extra, targets):
"""The chain generate_future_prediction runs: always the fixed, tuned
FUTURE_CHAIN order filtered down to what's needed -- the user picks WHICH
modalities to predict, never the order. The seed modality (its given
frames are completed in place) and the conditioning modality (fully
given; the schedule builder pops it) are structurally required in-chain.
"""
keep = set(targets) | {seed_modality} | ({extra} if extra else set())
return [m for m in FUTURE_CHAIN if m in keep]
def fp_preset_targets(preset, obs_modality):
"""(targets, complete_seed) for a Future Prediction preset button --
the tab 2 analogue of preset_chain. 'chained' is today's only behavior:
predict every other modality, chained through the fixed FUTURE_CHAIN
order. 'direct' mirrors the A2A tab's single-hop "Direct" preset: land
on rgb without chaining through any intermediate abstract modality, or,
when rgb is itself what's being observed, just continue rgb's own
remaining frames (there's nothing more "direct" than that).
"""
if preset == "direct":
return ([], True) if obs_modality == "rgb" else (["rgb"], False)
return ([m for m in FUTURE_CHAIN if m != obs_modality], True)
# --- canonicalization ----------------------------------------------------------
# Mirrors the flat spec _build_hyperparam_controls emits, in the same order.
HYPERPARAM_SPEC = [(key, param) for key in ALL_MODALITIES for param in ("temp", "cfg")]
_CONFIG_IDX = {"temp": 5, "cfg": 6}
def default_slider_values(preset_key):
"""Exactly the values a freshly-loaded hyperparameter accordion holds, in
HYPERPARAM_SPEC order. preset_key=None sources straight from CONFIGS (what
the Future Prediction tab does). _build_hyperparam_controls builds its
sliders from this, so UI defaults and the canonicalizer cannot drift.
"""
hp_defaults = HYPERPARAM_PRESETS[preset_key] if preset_key else {}
return [
hp_defaults.get(key, {}).get(param, CONFIGS[key][_CONFIG_IDX[param]])
for key, param in HYPERPARAM_SPEC
]
def _num(value):
"""Fixed-width float text. A step=0.01 range input can hand us
0.7000000000000001; comparing those as floats would miss forever."""
return "%.4f" % float(value)
def _effective_overrides(chain, overrides):
"""Only the hyperparameters that can actually change the output.
_build_schedule reads overrides[k] for k in chain and nothing else, and
pop_conditioning_domain then drops the input modality's entry entirely --
so the sliders for off-chain modalities, and the input modality's own
sliders, are inert. Including them would make the cache miss whenever a
visitor idly dragged an irrelevant slider.
"""
overrides = overrides or {}
effective = {}
for key in chain:
given = overrides.get(key) or {}
entry = {
"temp": _num(given.get("temp", CONFIGS[key][5])),
"cfg": _num(given.get("cfg", CONFIGS[key][6])),
}
# decoding_steps is ignored for the autoregressive modalities
# (caption/transcription/det), so it must not enter their key.
if CONFIGS[key][2] is not None:
entry["decoding_steps"] = int(given.get("decoding_steps", CONFIGS[key][2]))
effective[key] = entry
return effective
def canonical_a2a_spec(input_modality, source, chain, seed, top_p, top_k, overrides):
"""source: {"kind": "example", "stem": ...} or {"kind": "caption", "text": ...}."""
return {
"v": SCHEMA_VERSION,
"task": "a2a",
"input_modality": input_modality,
"source": source,
"chain": list(chain), # ordered: the chain order changes the result
"seed": int(seed),
"top_p": _num(top_p),
"top_k": int(round(float(top_k))),
"overrides": _effective_overrides(chain, overrides),
}
def canonical_fp_spec(stem, obs_mod, seed_tokens, extra, steer_text, chain, seed, top_p, top_k, overrides,
complete_seed=True):
# When not completing the seed modality, its hyperparameter sliders are
# inert (same reasoning as _effective_overrides' docstring for off-chain
# modalities) -- excluded here so idly dragging them can't miss the cache.
overrides_chain = chain if complete_seed else [k for k in chain if k != obs_mod]
spec = {
"v": SCHEMA_VERSION,
"task": "fp",
"stem": stem,
"obs": obs_mod,
"seed_tokens": int(seed_tokens),
"extra": extra,
"chain": list(chain), # already canonical: fp_chain filters FUTURE_CHAIN
"complete_seed": bool(complete_seed),
"seed": int(seed),
"top_p": _num(top_p),
"top_k": int(round(float(top_k))),
"overrides": _effective_overrides(overrides_chain, overrides),
}
if extra:
# steer_text only reaches the model when a conditioning modality is on.
spec["steer_text"] = steer_text or ""
return spec
def code_fingerprint():
"""Fingerprint of everything a spec is computed from. A mismatch means the
cache will simply miss -- worth a startup warning, never a hard failure."""
blob = json.dumps(
{"configs": CONFIGS, "presets": HYPERPARAM_PRESETS,
"future_chain": FUTURE_CHAIN, "coarse": COARSE_TO_FINE_BY_INPUT},
sort_keys=True, separators=(",", ":"), default=list,
)
return hashlib.sha256(blob.encode("utf-8")).hexdigest()[:16]
# --- UI arguments -> generation call (one implementation, two callers) ---------
A2ARequest = namedtuple("A2ARequest", "input_modality chain gen_kwargs overrides seed top_p top_k spec")
FPRequest = namedtuple("FPRequest", "stem obs_mod seed_tokens extra chain steer_text overrides seed top_p top_k spec complete_seed raw_video_path")
def resolve_a2a(src_mode, example_stem, upload_path, caption_text, abs_mod,
chain, seed, top_p, top_k, hyperparam_values, hyperparam_spec=HYPERPARAM_SPEC):
"""Validates and normalizes the any-to-any tab's arguments.
Raises ValueError for anything the user has to fix; app.py re-raises those
as gr.Error *before* requesting a GPU, so a mis-click costs no quota.
spec is None for uploads -- they have no stable identity, so they are never
cacheable and always run live.
"""
# Pin down what's really being generated from the source mode rather than
# trusting any UI state that could lag.
if src_mode == "caption":
input_modality = "caption"
elif src_mode == "abstract":
input_modality = abs_mod
else:
input_modality = "rgb"
chain = clean_chain(input_modality, chain)
if not chain:
raise ValueError("Please add at least one modality to the chain first.")
gen_kwargs = {}
source = None
if src_mode == "video" and upload_path:
gen_kwargs["raw_video_path"] = upload_path
elif src_mode == "video":
if not example_stem:
raise ValueError("Please pick an example clip or upload a video first.")
gen_kwargs["example_stem"] = example_stem
source = {"kind": "example", "stem": example_stem}
elif src_mode == "caption":
text = (caption_text or "").strip()
if not text:
raise ValueError("Please type a caption first.")
gen_kwargs["raw_caption_text"] = text
source = {"kind": "caption", "text": text}
else:
if not example_stem:
raise ValueError("Please pick an example clip first.")
gen_kwargs["example_stem"] = example_stem
source = {"kind": "example", "stem": example_stem}
# Decoding steps aren't exposed as sliders -- seed overrides from the tuned
# preset matching the input modality, then layer the user-adjustable
# temp/cfg slider values on top.
preset_key = hyperparam_preset_key_for_modality(input_modality)
overrides = {key: dict(vals) for key, vals in HYPERPARAM_PRESETS[preset_key].items()}
for (key, param), value in zip(hyperparam_spec, hyperparam_values):
overrides.setdefault(key, {})[param] = value
seed, top_p, top_k = int(seed), float(top_p), float(top_k)
spec = None
if source is not None:
spec = canonical_a2a_spec(input_modality, source, chain, seed, top_p, top_k, overrides)
return A2ARequest(input_modality, chain, gen_kwargs, overrides, seed, top_p, top_k, spec)
def resolve_fp(example_stem, obs_mod, seed_tokens, cond_mode, cond_mod, steer_text, targets,
seed, top_p, top_k, hyperparam_values, hyperparam_spec=HYPERPARAM_SPEC,
complete_seed_modality=True, raw_video_path=None):
"""Validates and normalizes the future-prediction tab's arguments.
complete_seed_modality: mirrors run_generation.py's
--should_complete_partial_conditioned_modalities -- see
inference.py's _build_future_schedule for what this actually changes.
raw_video_path: an uploaded clip instead of a curated example -- same
convention as resolve_a2a's raw_video_path (only valid for RGB
observation, has no stable identity so it's never cacheable: spec is
None below, same as an any-to-any upload).
"""
if raw_video_path:
if obs_mod != "rgb":
raise ValueError("An uploaded video can only be used for RGB observation.")
elif not example_stem:
raise ValueError("Please pick an example clip or upload a video first.")
extra = fp_extra(cond_mode, cond_mod)
if raw_video_path and extra:
# No ground-truth caption/transcription exists for an uploaded clip
# (unlike a curated example) -- the steering text has to be typed in.
if not (steer_text or "").strip():
raise ValueError(
f"An uploaded video has no ground-truth {extra} -- please type one to condition on."
)
targets = [t for t in targets if t != obs_mod and t != extra]
# Completing the seed modality's own remaining frames now counts as
# "predicting something" too (see the Future Prediction tab's chip for
# it), so this is only a hard requirement when that's also off.
if not targets and not complete_seed_modality:
raise ValueError("Pick at least one modality to predict the future in.")
chain = fp_chain(obs_mod, extra, targets)
# No HYPERPARAM_PRESETS base here -- Future Prediction uses plain CONFIGS
# hyperparameters directly (sliders already default to those values; only
# overridden if changed).
overrides = {}
for (key, param), value in zip(hyperparam_spec, hyperparam_values):
overrides.setdefault(key, {})[param] = value
# Stripped so a stray trailing newline in the textarea can't miss the cache.
steer_text = (steer_text or "").strip() or None
seed, seed_tokens = int(seed), int(seed_tokens)
top_p, top_k = float(top_p), float(top_k)
complete_seed_modality = bool(complete_seed_modality)
spec = None if raw_video_path else canonical_fp_spec(
example_stem, obs_mod, seed_tokens, extra, steer_text,
chain, seed, top_p, top_k, overrides,
complete_seed=complete_seed_modality,
)
return FPRequest(example_stem, obs_mod, seed_tokens, extra, chain, steer_text,
overrides, seed, top_p, top_k, spec, complete_seed_modality, raw_video_path)
# --- the cache itself ----------------------------------------------------------
class CacheStore:
"""Read-only view over the precomputed manifest. Every accessor is total:
a missing, truncated or half-uploaded cache degrades to "no cache", never
to an exception -- the demo has to boot offline (same contract as
_list_examples_safe in app.py)."""
def __init__(self, data=None):
self.data = data or {}
self.entries = [e for e in self.data.get("entries", []) if isinstance(e, dict)]
@property
def enabled(self):
return bool(self.entries)
def lookup(self, spec):
"""The entry generated with exactly this configuration, or None."""
if not spec:
return None
return next((e for e in self.entries if e.get("spec") == spec), None)
def prefill_entry(self, task):
return next((e for e in self.entries
if e.get("task") == task and e.get("prefill")), None)
def prefill_stem(self, task):
"""Which example clip the page-load prefill for this tab is about.
Read back out of the entry's own spec so it cannot drift from it."""
entry = self.prefill_entry(task)
spec = (entry or {}).get("spec") or {}
if task == "fp":
return spec.get("stem")
return (spec.get("source") or {}).get("stem")
def resolve_results(self, entry):
"""{result_key: local mp4 path | text}, shaped exactly like
generate_any_to_any's return so the normal render path consumes it
unchanged. Videos that failed to download are dropped rather than
passed on as None -- a bad path in a build-time gr.Video(value=...)
raises at import and the Space never comes up."""
results = {}
for key, item in ((entry or {}).get("results") or {}).items():
if not isinstance(item, dict):
continue
if item.get("type") == "text":
results[key] = item.get("value")
continue
path = get_precomputed_file(item.get("file") or "")
if path and os.path.exists(path):
results[key] = path
return results
def load_store():
"""Fetches and validates the manifest. Never raises."""
try:
data = load_precomputed_manifest()
except Exception:
traceback.print_exc()
return CacheStore()
if not data:
return CacheStore()
if data.get("v") != SCHEMA_VERSION:
print(f"[precompute] manifest schema v{data.get('v')} != v{SCHEMA_VERSION}; cache disabled")
return CacheStore()
if data.get("code_fingerprint") != code_fingerprint():
print("[precompute] WARNING: CONFIGS / HYPERPARAM_PRESETS / chain presets have changed "
"since these results were generated — lookups will miss and fall back to live GPU. "
"Re-run scripts/precompute_demo_outputs.py.")
store = CacheStore(data)
print(f"[precompute] loaded {len(store.entries)} precomputed entr"
f"{'y' if len(store.entries) == 1 else 'ies'}")
return store
|