Files
hao-ai-lab-FastVideo/tests/local_tests/test_cookbook_serving.py

184 lines
8.0 KiB
Python

"""Check serving snippets against their executable sources, without model imports."""
import copy
import json
from pathlib import Path
from html.parser import HTMLParser
import pytest
import yaml
from docs.generate_examples import COOKBOOK_DATA, Example, cookbook_serving_profile, validate_cookbook
ROOT = Path(__file__).resolve().parents[2]
def serving_recipe():
recipes = json.loads(COOKBOOK_DATA.read_text())["recipes"]
return next(recipe for recipe in recipes if recipe["id"] == "fasth3-preview-cuda")
def test_cookbook_serving_does_not_inherit_local_benchmark():
recipe = serving_recipe()
assert recipe["hardware"]["evidence"] == "validated"
profile = cookbook_serving_profile(recipe)
assert profile["hardware"] == {"platform": "cuda", "gpu_count": 4, "evidence": "source-configured"}
assert profile["model"] == "fasth3"
assert profile["sampling"]["num_frames"] == 124
assert "--server.host 127.0.0.1" in profile["command"]
assert profile["playground_url"] == "http://127.0.0.1:8000/playground/"
for client in profile["clients"].values():
assert client["code"] == (ROOT / client["source"]).read_text()
validate_cookbook()
@pytest.mark.parametrize(
"config_path",
[
"examples/inference/basic/basic_fasth3_spark.yaml",
"examples/inference/basic/basic_fasth3_spark_pair.yaml",
"examples/serving/openai_fasth3_spark.yaml",
],
)
def test_spark_configs_use_lazy_load_not_sequential(config_path):
cfg = yaml.safe_load((ROOT / config_path).read_text())
offload = cfg["generator"]["engine"]["offload"]
assert offload["lazy_module_load"] is True
experimental = cfg["generator"]["pipeline"]["experimental"]
assert "h3_sequential_load" not in experimental
def test_spark_preview_is_a_runtime_with_one_or_two_devices():
recipes = json.loads(COOKBOOK_DATA.read_text())["recipes"]
one = next(item for item in recipes if item["id"] == "fasth3-preview-spark")
pair = next(item for item in recipes if item["id"] == "fasth3-spark-pair")
assert one["group"] == pair["group"] == "fasth3-preview"
assert one["hardware"]["device"] == pair["hardware"]["device"] == "spark"
assert one["hardware"]["gpu_count"] == 1
assert pair["hardware"]["gpu_count"] == 2
assert "serving" in one
assert "serving" not in pair
profile = cookbook_serving_profile(one)
assert profile["hardware"] == {
"platform": "cuda",
"device": "spark",
"gpu_count": 1,
"evidence": "source-configured",
}
assert profile["command"].startswith("FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0")
assert "openai_fasth3_spark.yaml" in profile["command"]
assert "--server.host 127.0.0.1" in profile["command"]
def test_mlx_serving_profile_has_native_launcher_and_no_invented_memory():
recipes = json.loads(COOKBOOK_DATA.read_text())["recipes"]
recipe = next(item for item in recipes if item["id"] == "fasth3-preview-mlx")
profile = cookbook_serving_profile(recipe)
assert profile["hardware"] == {"platform": "mlx", "evidence": "source-configured"}
assert profile["command"] == (
"python -m fastvideo.entrypoints.openai.mlx_server --config examples/serving/mlx_fasth3.yaml"
)
assert profile["prepare"] in recipe["command"]
assert profile["sampling"]["num_inference_steps"] == 5
for client in profile["clients"].values():
assert client["code"] == (ROOT / client["source"]).read_text()
def test_fasth3_8step_cuda_and_mlx_share_the_openai_client():
recipes = json.loads(COOKBOOK_DATA.read_text())["recipes"]
cuda = next(item for item in recipes if item["id"] == "fasth3-8step-v2-cuda")
mlx = next(item for item in recipes if item["id"] == "fasth3-8step-v2-mlx")
assert cuda["group"] == mlx["group"] == "fasth3-8step-v2"
cuda_profile = cookbook_serving_profile(cuda)
mlx_profile = cookbook_serving_profile(mlx)
assert cuda_profile["model"] == mlx_profile["model"] == "fasth3"
assert cuda_profile["sampling"]["num_inference_steps"] == 9
assert mlx_profile["sampling"]["num_inference_steps"] == 9
assert "openai_fasth3_8step.yaml" in cuda_profile["command"]
assert mlx_profile["command"] == (
"python -m fastvideo.entrypoints.openai.mlx_server --config examples/serving/mlx_fasth3_8step.yaml"
)
assert mlx_profile["prepare"] in mlx["command"]
assert "--include-vsa" in mlx_profile["prepare"]
preview = cookbook_serving_profile(next(item for item in recipes if item["id"] == "fasth3-preview-cuda"))
assert cuda_profile["clients"]["python"]["code"] == preview["clients"]["python"]["code"]
def test_mlx_preview_and_8step_yaml_map_sigma_points_to_forwards():
from fastvideo.entrypoints.openai.mlx_server import (EIGHT_STEP_MODEL, PREVIEW_MODEL, load_config, mlx_num_steps,
validate_mlx_video_request)
from fastvideo.entrypoints.openai.protocol import VideoGenerationRequest
from fastvideo.registry import get_preset_selection
assert mlx_num_steps(5, model_path=PREVIEW_MODEL) == 4
assert mlx_num_steps(None, model_path=PREVIEW_MODEL) == 4
assert mlx_num_steps(9, model_path=EIGHT_STEP_MODEL) == 8
with pytest.raises(ValueError, match="5 sigma points"):
mlx_num_steps(9, model_path=PREVIEW_MODEL)
with pytest.raises(ValueError, match="9 sigma points"):
mlx_num_steps(5, model_path=EIGHT_STEP_MODEL)
validate_mlx_video_request(VideoGenerationRequest(prompt="a fox", num_inference_steps=5), model_path=PREVIEW_MODEL)
validate_mlx_video_request(VideoGenerationRequest(prompt="a fox", num_inference_steps=9),
model_path=EIGHT_STEP_MODEL)
with pytest.raises(ValueError, match="9 sigma points"):
validate_mlx_video_request(VideoGenerationRequest(prompt="a fox", num_inference_steps=5),
model_path=EIGHT_STEP_MODEL)
preview = load_config(str(ROOT / "examples/serving/mlx_fasth3.yaml"))
eight = load_config(str(ROOT / "examples/serving/mlx_fasth3_8step.yaml"))
assert preview.generator.vsa is False
assert eight.generator.vsa is True
assert eight.generator.vsa_sparsity == 0.8
assert eight.default_request["sampling"]["num_inference_steps"] == 9
assert get_preset_selection(EIGHT_STEP_MODEL) == ("minimax_h3_t2va", "minimax_h3")
@pytest.mark.parametrize("field,value", [("model", "wrong-checkpoint"), ("hardware", {"platform": "mlx"})])
def test_cookbook_rejects_mismatched_serving_recipe(field, value):
recipe = copy.deepcopy(serving_recipe())
recipe[field] = value
with pytest.raises(ValueError):
cookbook_serving_profile(recipe)
def test_cookbook_rejects_config_outside_serving_examples():
recipe = serving_recipe()
recipe["serving"]["source"] = "pyproject.toml"
with pytest.raises(ValueError, match="examples/serving"):
cookbook_serving_profile(recipe)
def test_example_docs_exclude_installed_client_dependencies(tmp_path):
(tmp_path / "README.md").write_text("# Example")
(tmp_path / "client.mjs").write_text("// example")
for dirname in ["node_modules", ".venv", "__pycache__"]:
dependency = tmp_path / dirname
dependency.mkdir()
(dependency / "README.md").write_text("Not example documentation")
assert Example(tmp_path).other_files == [tmp_path / "client.mjs"]
def test_h3_command_blocks_have_unique_copy_targets():
class CodeBlocks(HTMLParser):
def __init__(self):
super().__init__()
self.ids = []
def handle_starttag(self, tag, attrs):
if tag == "pre":
block_id = dict(attrs).get("id")
if block_id:
self.ids.append(block_id)
parser = CodeBlocks()
parser.feed((ROOT / "docs/cookbook/minimax-h3.md").read_text())
assert parser.ids == [
"cookbook-local-command",
"cookbook-server-install",
"cookbook-server-prepare",
"cookbook-server-command",
"cookbook-health-command",
"cookbook-client-install",
"cookbook-client-code",
]
assert len(set(parser.ids)) == len(parser.ids)