Files
hao-ai-lab-FastVideo/fastvideo/tests/api/test_parser.py
T
Davids048andClaude Opus 5.5 6c776a219b [refactor]: run the inference path on the resolved config
ResolvedGeneratorConfig becomes the runtime config of every mode. Resolution
builds the model's PipelineConfig once, freezes it, and attaches it as
resolved_config.pipeline_config; it also exposes mode, training_mode, and
inference_mode. VideoGenerator, fastvideo serve, the OpenAI server state,
the executors, the worker, and ComposedPipelineBase receive the resolved
config instead of a FastVideoArgs, and generator_config_to_fastvideo_args
is no longer on the inference path.

- Schema: ExecutionMode and WorkloadType move to fastvideo/api/schema.py;
  typed homes for mode, output_type, boundary_ratio, master_port,
  moba_config, and the LTX-2 refine settings; Enum parsing.
- Resolution: steps for the refine preset overrides, flat experimental
  keys, the V-MoBA config, the validation that check_fastvideo_args did,
  the num_gpus bump, the model's vae_tiling default, and a final
  fill_runtime_defaults step for typed fields no earlier step decides.
- fastvideo/api/training_schema.py: TrainingRunConfig and
  PreprocessRunConfig roots, their resolvers, and a --config plus dotted
  override loader.
- fastvideo/api/device_policy.py: the device offload policy as functions
  that return an overridden config; the worker keeps the result. The LTX-2
  and MiniMax-H3 checkpoint defaults rebind the pipeline's config.
- fastvideo/api/flat_name_fallback.py answers the flat FastVideoArgs names
  on the resolved config until the readers move to typed paths.

Golden changes: the decisions of the steps above are recorded, and
FastVideoArgs.ltx2_vae_tiling holds the model's vae_tiling (it held None;
no runtime code reads it). Against the 3f6893a0 goldens every
FastVideoArgs field, PipelineConfig, and SamplingParam matches, except
disable_autocast and boundary_ratio, which FastVideoArgs held at their
defaults because flattening routed them only to PipelineConfig.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012M91wnVFmPEvJ9h39r5BH7
2026-10-03 05:35:14 +00:00

412 lines
13 KiB
Python

# SPDX-License-Identifier: Apache-2.0
import json
import yaml
from fastvideo.api import (
config_to_dict,
ContinuationState,
GenerationRequest,
GeneratorConfig,
GpuPoolConfig,
load_run_config,
load_serve_config,
parse_config,
PlannedStage,
PromptEnhancerConfig,
PromptSafetyConfig,
RunConfig,
ServeConfig,
StreamingConfig,
WarmupConfig,
)
def test_parse_config_builds_nested_typed_config() -> None:
raw = {
"generator": {
"model_path": "/models/ltx2",
"pipeline": {
"workload_type": "t2v",
"preset": "ltx2_two_stage",
},
},
"request": {
"prompt": ["a fox", "a wolf"],
"sampling": {
"num_frames": 121,
"height": 1024,
"width": 1536,
"guidance_scale": 1.5,
},
"state": {
"kind": "ltx2_continuation",
"payload": {
"segment_index": 1
},
},
"plan": {
"stages": [{
"name": "base",
"kind": "sample",
}]
},
},
}
config = parse_config(RunConfig, raw)
assert config.generator.pipeline.preset == "ltx2_two_stage"
assert config.request.prompt == ["a fox", "a wolf"]
assert config.request.state == ContinuationState(
kind="ltx2_continuation",
payload={"segment_index": 1},
)
assert config.request.plan is not None
assert config.request.plan.stages == [PlannedStage(name="base", kind="sample")]
def test_parse_config_accepts_existing_typed_instance() -> None:
typed = RunConfig(
generator=GeneratorConfig(model_path="/models/base"),
request=GenerationRequest(prompt="hello"),
)
assert parse_config(RunConfig, typed) is typed
def test_load_run_config_supports_yaml_roundtrip(tmp_path) -> None:
raw = {
"generator": {
"model_path": "/models/wan"
},
"request": {
"prompt": "hello",
"sampling": {
"num_frames": 16
},
},
}
path = tmp_path / "run.yaml"
path.write_text(yaml.safe_dump(raw), encoding="utf-8")
loaded = load_run_config(path)
assert config_to_dict(loaded) == {
"generator": {
"model_path": "/models/wan",
"mode": "inference",
"revision": None,
"trust_remote_code": False,
"engine": {
"num_gpus": 1,
"execution_backend": "mp",
"parallelism": {
"tp_size": -1,
"sp_size": -1,
"hsdp_replicate_dim": 1,
"hsdp_shard_dim": -1,
"dist_timeout": None,
"master_port": None,
},
"offload": {
"dit": True,
"dit_layerwise": True,
"text_encoder": True,
"image_encoder": True,
"vae": True,
"pin_cpu_memory": True,
"lazy_module_load": None,
},
"compile": {
"enabled": False,
"backend": None,
"fullgraph": None,
"mode": None,
"dynamic": None,
"extras": {},
"text_encoder_enabled": None,
"vae_enabled": None,
"audio_vae_enabled": None,
"regional": None,
"dit_kwargs": {},
"text_encoder_kwargs": {},
"vae_kwargs": {},
"audio_vae_kwargs": {},
},
"attention": {
"backend": None,
"vsa_sparsity": None,
"vsa_tile_size": None,
"moba_config_path": None,
"moba_config": None,
},
"precision": {
"dit": None,
"vae": None,
"vae_decode": None,
"image_encoder": None,
"text_encoders": None,
},
"enable_stage_verification": True,
"use_fsdp_inference": False,
"disable_autocast": False,
"quantization": None,
},
"pipeline": {
"workload_type": None,
"preset": None,
"preset_version": None,
"components": {
"config_root": None,
"pipeline_config_path": None,
"text_encoder_weights": None,
"transformer_weights": None,
"transformer_2_weights": None,
"vae_weights": None,
"upsampler_weights": None,
"lora_path": None,
"lora_nickname": "default",
"lora_strength": 1.0,
"lora_target_modules": None,
"override_pipeline_cls_name": None,
"override_transformer_cls_name": None,
},
"vae_tiling": None,
"vae_sp": None,
"flow_shift": None,
"embedded_cfg_scale": None,
"dmd_denoising_steps": None,
"boundary_ratio": None,
"output_type": "pil",
"dit": {},
"vae": {},
"ltx2": {
"vae_spatial_tile_size_in_pixels": None,
"vae_spatial_tile_overlap_in_pixels": None,
"vae_temporal_tile_size_in_frames": None,
"vae_temporal_tile_overlap_in_frames": None,
"initial_latent_path": None,
"audio_latent_path": None,
"legacy_native_noise_order": None,
"use_distilled_sigmas": None,
"refine": {
"enabled": None,
"num_inference_steps": None,
"guidance_scale": None,
"add_noise": None,
"image_crf": None,
"video_position_offset_sec": None,
"transformer_path": None,
"lora_path": None,
"noise_path": None,
"audio_noise_path": None,
},
},
"minimax_h3": {
"sequential_load": None,
"video_decode_backend": None,
"taeh3_checkpoint": None,
"taeh3_chunk_size": None,
"vae_parallel_decode": None,
"vae_parallel_encode": None,
"vae_parallel_decode_strategy": None,
},
"longcat": {
"enable_bsa": None,
"bsa_sparsity": None,
"bsa_cdf_threshold": None,
"bsa_chunk_q": None,
"bsa_chunk_k": None,
},
"preset_overrides": {},
"experimental": {},
},
},
"request": {
"prompt": "hello",
"negative_prompt": None,
"inputs": {
"prompt_path": None,
"image_path": None,
"video_path": None,
"pil_image": None,
"last_image": None,
"references": None,
"latents": None,
"audio_latents": None,
"pose": None,
"mouse_cond": None,
"keyboard_cond": None,
"grid_sizes": None,
"c2ws_plucker_emb": None,
"action_path": None,
"refine_from": None,
"stage1_video": None,
},
"sampling": {
"num_videos_per_prompt": 1,
"seed": 1024,
"max_sequence_length": None,
"num_frames": 16,
"height": 720,
"width": 1280,
"height_sr": 1072,
"width_sr": 1920,
"fps": 24,
"num_inference_steps": 50,
"num_inference_steps_sr": 50,
"guidance_scale": 1.0,
"batch_cfg": False,
"guidance_scale_2": None,
"cfg_normalization": False,
"cfg_truncation": 1.0,
"guidance_rescale": 0.0,
"true_cfg_scale": None,
"use_embedded_guidance": None,
"embedded_cfg_scale": None,
"boundary_ratio": None,
"sigmas": None,
},
"runtime": {
"enable_teacache": False,
"return_trajectory_latents": False,
"return_trajectory_decoded": False,
},
"output": {
"output_path": "outputs/",
"output_video_name": None,
"save_video": True,
"return_frames": True,
"return_state": False,
},
"stage_overrides": {},
"state": None,
"plan": None,
"extensions": {},
},
}
def test_load_serve_config_supports_json_roundtrip(tmp_path) -> None:
raw = {
"generator": {
"model_path": "/models/server"
},
"server": {
"port": 9000
},
"default_request": {
"prompt": "serve default"
},
}
path = tmp_path / "serve.json"
path.write_text(json.dumps(raw), encoding="utf-8")
loaded = load_serve_config(path)
assert isinstance(loaded, ServeConfig)
assert loaded.server.port == 9000
assert loaded.default_request.prompt == "serve default"
def test_serve_config_streaming_defaults_to_none() -> None:
raw = {"generator": {"model_path": "/models/server"}}
loaded = parse_config(ServeConfig, raw)
assert loaded.streaming is None
def test_serve_config_parses_streaming_block() -> None:
raw = {
"generator": {
"model_path": "/models/server"
},
"streaming": {
"session_timeout_seconds": 120,
"generation_segment_cap": 4,
"stream_mode": "legacy_jpeg",
"warmup": {
"enabled": False,
"prompt": "warmup prompt",
"timeout_seconds": 600,
},
"pool": {
"num_workers": 2,
"enable_audio_reencode": False,
"conditioning_num_frames": 5,
"conditioning_end_offset": 1,
},
"prompt": {
"provider": "groq",
"model": "llama-3-70b",
"timeout_ms": 10000,
"system_prompt_dir": "/opt/prompts",
},
"safety": {
"enabled": True,
"classifier_path": "/opt/safety.pt",
},
},
}
loaded = parse_config(ServeConfig, raw)
assert isinstance(loaded.streaming, StreamingConfig)
assert loaded.streaming.session_timeout_seconds == 120
assert loaded.streaming.generation_segment_cap == 4
assert loaded.streaming.stream_mode == "legacy_jpeg"
assert loaded.streaming.warmup == WarmupConfig(enabled=False, prompt="warmup prompt", timeout_seconds=600)
assert loaded.streaming.pool == GpuPoolConfig(
num_workers=2,
enable_audio_reencode=False,
conditioning_num_frames=5,
conditioning_end_offset=1,
)
assert loaded.streaming.prompt == PromptEnhancerConfig(
provider="groq",
model="llama-3-70b",
timeout_ms=10000,
system_prompt_dir="/opt/prompts",
)
assert loaded.streaming.safety == PromptSafetyConfig(enabled=True, classifier_path="/opt/safety.pt")
def test_serve_config_streaming_round_trip_through_config_to_dict() -> None:
raw = {
"generator": {
"model_path": "/models/server"
},
"streaming": {
"session_timeout_seconds": 600
},
}
loaded = parse_config(ServeConfig, raw)
dumped = config_to_dict(loaded)
assert dumped["streaming"]["session_timeout_seconds"] == 600
assert dumped["streaming"]["warmup"]["enabled"] is True
assert dumped["streaming"]["prompt"]["enabled"] is False
assert dumped["streaming"]["prompt"]["provider"] == "cerebras"
assert dumped["streaming"]["safety"]["enabled"] is False
def test_load_serve_config_with_streaming_from_yaml(tmp_path) -> None:
raw = {
"generator": {
"model_path": "/models/server"
},
"streaming": {
"stream_mode": "av_fmp4",
"pool": {
"num_workers": 4
},
},
}
path = tmp_path / "serve.yaml"
path.write_text(yaml.safe_dump(raw), encoding="utf-8")
loaded = load_serve_config(path)
assert loaded.streaming is not None
assert loaded.streaming.stream_mode == "av_fmp4"
assert loaded.streaming.pool.num_workers == 4