[bugfix]: Keep model discovery answering for unresolved paths

Resolve the served family with allow_remote=False so /v1/model_info answers null instead of downloading a manifest and 500ing on an unresolved path. Drop the unreachable canvas clamp so both H3 families advertise one geometry, and pin that invariant in the tests.
This commit is contained in:
Aryan Kumar
2026-11-13 20:16:56 +00:00
parent 7c4231fdf9
commit 91200ea61e
4 changed files with 48 additions and 19 deletions
+2
View File
@@ -16,6 +16,8 @@ requests. HTTP handling and job polling remain asynchronous.
(including FastH3 and Omni) and Pika H3 Flex. Version 1 reports the 24 fps
frame grid, integer duration choices from 5 to 15 seconds, and recommended
480p/768p width/height pairs by aspect ratio. Other families return `null`.
The family is resolved from the registry and local checkpoints only, so
discovery never downloads a Hub manifest.
These values describe model geometry, independently of the serve file's
default request. `recommended_frame_sizes` is not an exhaustive canvas list,
and `hardware_capacity_verified: false` means the response does not establish
@@ -2,6 +2,10 @@
"""Output geometry advertised by a loaded pipeline, independent of device capacity."""
from typing import Any
from fastvideo.logger import init_logger
logger = init_logger(__name__)
def video_output_capabilities(args: Any) -> dict[str, Any] | None:
"""Return known output constraints, or None for an unregistered family.
@@ -11,7 +15,12 @@ def video_output_capabilities(args: Any) -> dict[str, Any] | None:
"""
from fastvideo.registry import get_preset_selection
_, family = get_preset_selection(args.model_path)
try:
# Resolve locally: model discovery must never gate /v1/model_info on a Hub lookup.
_, family = get_preset_selection(args.model_path, allow_remote=False)
except Exception as exc: # discovery is best-effort; report unknown instead of failing the request
logger.warning("Could not resolve output capabilities for %r: %s", args.model_path, exc)
return None
if family not in {"minimax_h3", "pika_h3_flex"}:
return None
@@ -37,13 +46,6 @@ def video_output_capabilities(args: Any) -> dict[str, Any] | None:
height, width = canvas_for_aspect(aspect, resolution)
else:
height, width = resolve_canvas_size(aw, ah, short_edge=short_edge, max_pixels=max_pixels)
# Nearest-lattice rounding can exceed the area cap. These are recommendations for the
# explicit width/height path, whose adapter enforces the cap before admission.
while width * height > max_pixels:
if width >= height:
width -= MINIMAX_H3_CANVAS_MULTIPLE
else:
height -= MINIMAX_H3_CANVAS_MULTIPLE
sizes[aspect][resolution] = [width, height]
return {
"schema_version": 1,
+6 -3
View File
@@ -1467,14 +1467,17 @@ def get_default_preset(model_path: str) -> str | None:
return config_info.default_preset
def get_preset_selection(model_path: str) -> tuple[str | None, str | None]:
def get_preset_selection(model_path: str, *, allow_remote: bool = True) -> tuple[str | None, str | None]:
"""Return ``(default_preset, model_family)`` for a model path.
Single-lookup variant of :func:`get_default_preset` +
:func:`get_model_family`; callers that need both should prefer this
to avoid walking the registry twice.
to avoid walking the registry twice. With ``allow_remote=False`` the
family comes only from the registry and local directories, so an
unresolved path returns ``(None, None)`` instead of downloading a Hub
manifest.
"""
config_info = _get_config_info(model_path, raise_on_missing=False)
config_info = _get_config_info(model_path, raise_on_missing=False, allow_remote=allow_remote)
if config_info is None:
return None, None
return config_info.default_preset, config_info.model_family
@@ -7,12 +7,17 @@ import pytest
from fastvideo.entrypoints.openai.generation_capabilities import video_output_capabilities
from fastvideo.pipelines.basic.minimax_h3.packing import MINIMAX_H3_MAX_PIXELS
from fastvideo.pipelines.basic.minimax_h3.stages.minimax_h3_input_preparation import resolve_target_num_frames
from fastvideo.pipelines.basic.pika_h3_flex.buckets import canvas_for_aspect
from fastvideo.pipelines.basic.pika_h3_flex.buckets import (PIKA_FPS, PIKA_MAX_DURATION, PIKA_MIN_DURATION,
canvas_for_aspect, resolve_frames)
def _family(monkeypatch, family: str | None) -> None:
monkeypatch.setattr("fastvideo.registry.get_preset_selection", lambda *_args, **_kwargs: (None, family))
@pytest.mark.parametrize("family", ["minimax_h3", "pika_h3_flex"])
def test_h3_discovery_includes_longer_clips_and_768p(monkeypatch, family):
monkeypatch.setattr("fastvideo.registry.get_preset_selection", lambda _: (None, family))
_family(monkeypatch, family)
result = video_output_capabilities(SimpleNamespace(model_path="local-model"))
assert result["duration_seconds"] == {"min": 5, "max": 15}
assert result["recommended_frame_sizes"]["16:9"] == {"480p": [832, 480], "768p": [1344, 768]}
@@ -23,24 +28,41 @@ def test_h3_discovery_includes_longer_clips_and_768p(monkeypatch, family):
for aspect, tiers in result["recommended_frame_sizes"].items():
for tier, (width, height) in tiers.items():
assert width % 32 == height % 32 == 0
if family == "minimax_h3":
assert width * height <= MINIMAX_H3_MAX_PIXELS
else:
assert [height, width] == list(canvas_for_aspect(aspect, tier))
# Both families share the released tier geometry, so neither may advertise a
# bucket the other would reject for the same aspect and resolution.
assert (height, width) == canvas_for_aspect(aspect, tier)
assert width * height <= MINIMAX_H3_MAX_PIXELS
assert result["hardware_capacity_verified"] is False
def test_duration_grid_reuses_pika_frame_rules(monkeypatch):
_family(monkeypatch, "pika_h3_flex")
result = video_output_capabilities(SimpleNamespace(model_path="local-model"))
assert result["fps"] == PIKA_FPS
assert result["duration_seconds"] == {"min": int(PIKA_MIN_DURATION), "max": int(PIKA_MAX_DURATION)}
for seconds, frames in result["num_frames_by_duration_seconds"].items():
assert frames == resolve_frames(int(seconds) * PIKA_FPS)
def test_unknown_family_does_not_advertise_h3_options(monkeypatch):
monkeypatch.setattr("fastvideo.registry.get_preset_selection", lambda _: (None, "wan"))
_family(monkeypatch, "wan")
assert video_output_capabilities(SimpleNamespace(model_path="local-model")) is None
def test_unresolved_model_path_stays_offline(monkeypatch):
def _no_download(*_args, **_kwargs):
raise AssertionError("model discovery must not download a Hub manifest")
monkeypatch.setattr("fastvideo.registry.maybe_download_model_index", _no_download)
assert video_output_capabilities(SimpleNamespace(model_path="unregistered-org/unregistered-model")) is None
def test_model_info_exposes_output_constraints_without_generation(monkeypatch):
from fastapi import FastAPI
from fastapi.testclient import TestClient
from fastvideo.entrypoints.openai import common_api
monkeypatch.setattr("fastvideo.registry.get_preset_selection", lambda _: (None, "minimax_h3"))
_family(monkeypatch, "minimax_h3")
monkeypatch.setattr(common_api, "get_server_args", lambda: SimpleNamespace(model_path="local-model", lora_path=None))
monkeypatch.setattr(common_api, "get_serving_engine", lambda: SimpleNamespace(lora_runtime=None))
monkeypatch.setattr(common_api, "get_served_model_name", lambda: "custom-omni-alias")