[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:
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user