Directly-constructed GenerationRequests marked every schema default as explicit (normalize_generation_request bound the full serialization when _fastvideo_explicit_paths was absent), so request_to_sampling_param overwrote model preset values with schema defaults: FastWan DMD ran 3->50 steps, TurboDiffusion 4->50, stable-audio 100 steps/CFG7 -> 50/CFG1, and 1.3B Wan examples rendered 720x1280x125f with CFG off. Fix at the root: SamplingConfig fields now default to None = "inherit the model preset". None leaves are never bound as explicit paths — for directly-constructed requests (normalize_generation_request) and for parsed YAML/JSON (parse_config treats explicit null as unset, so widening the types cannot let nulls stomp presets either). Dicts emptied by the pruning are dropped too, so None-valued extensions cannot resurface via the live-attribute read in explicit_request_updates. The _SCHEMA_DEFAULT_UPDATES tolerance hack is deleted (an explicitly-set field unsupported by a model's SamplingParam now always fails loudly), and return_state is skipped in the update loop since it is already translated to return_continuation_state. Consumers of raw (pre-resolution) sampling fields are guarded: the streaming server validates operator-pinned default_request.sampling before the multi-minute model boot (run_server) and in build_app; its segment negative_prompt merge preserves an explicit "" clear; the mock server supplies concrete fallbacks; generate_async resolves the preset so its advertised total_steps matches the actual run; the ray-serve gradio demo passes "" to keep its no-negative-prompt default. Stale schema-default claims in video_api/ServeConfig/openai.md docs updated. Tests: test_preset_inheritance.py locks the contract (bare request inherits preset; partial override; explicit value equal to an old schema default wins; negative_prompt None-inherits/""-clears; parsed nulls inherit; None-valued extensions are dropped; unsupported explicit field raises) plus a build_app pin-validation test.
319 lines
9.5 KiB
Python
319 lines
9.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Literal
|
|
|
|
|
|
@dataclass
|
|
class ServerConfig:
|
|
host: str = "0.0.0.0"
|
|
port: int = 8000
|
|
output_dir: str = "outputs/"
|
|
|
|
|
|
@dataclass
|
|
class ParallelismConfig:
|
|
tp_size: int = -1
|
|
sp_size: int = -1
|
|
hsdp_replicate_dim: int = 1
|
|
hsdp_shard_dim: int = -1
|
|
dist_timeout: int | None = None
|
|
|
|
|
|
@dataclass
|
|
class OffloadConfig:
|
|
dit: bool = True
|
|
dit_layerwise: bool = True
|
|
text_encoder: bool = True
|
|
image_encoder: bool = True
|
|
vae: bool = True
|
|
pin_cpu_memory: bool = True
|
|
|
|
|
|
@dataclass
|
|
class CompileConfig:
|
|
"""Typed ``torch.compile`` configuration.
|
|
|
|
``backend``/``fullgraph``/``mode``/``dynamic`` are the four most
|
|
common ``torch.compile`` knobs. ``extras`` holds any remaining
|
|
``torch.compile`` kwargs (e.g. ``options``, ``disable``).
|
|
|
|
The ``enabled`` switch covers the DiT transformer path (including
|
|
``transformer_2`` and the LTX-2 stage-2 ``transformer_refine``).
|
|
Per-component flags below are independent overlays — set to ``True``
|
|
to compile that component, ``None`` to leave it eager. Each
|
|
``*_kwargs`` dict overrides the master ``backend``/``fullgraph``/
|
|
``mode``/``dynamic``/``extras`` for that component when non-empty;
|
|
leaving it empty inherits the master kwargs.
|
|
"""
|
|
|
|
enabled: bool = False
|
|
backend: str | None = None
|
|
fullgraph: bool | None = None
|
|
mode: str | None = None
|
|
dynamic: bool | None = None
|
|
extras: dict[str, Any] = field(default_factory=dict)
|
|
|
|
text_encoder_enabled: bool | None = None
|
|
vae_enabled: bool | None = None
|
|
audio_vae_enabled: bool | None = None
|
|
|
|
dit_kwargs: dict[str, Any] = field(default_factory=dict)
|
|
text_encoder_kwargs: dict[str, Any] = field(default_factory=dict)
|
|
vae_kwargs: dict[str, Any] = field(default_factory=dict)
|
|
audio_vae_kwargs: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class QuantizationConfig:
|
|
text_encoder_quant: str | None = None
|
|
transformer_quant: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class EngineConfig:
|
|
num_gpus: int = 1
|
|
execution_backend: Literal["mp", "ray"] = "mp"
|
|
parallelism: ParallelismConfig = field(default_factory=ParallelismConfig)
|
|
offload: OffloadConfig = field(default_factory=OffloadConfig)
|
|
compile: CompileConfig = field(default_factory=CompileConfig)
|
|
enable_stage_verification: bool = True
|
|
use_fsdp_inference: bool = False
|
|
disable_autocast: bool = False
|
|
quantization: QuantizationConfig | None = None
|
|
|
|
|
|
@dataclass
|
|
class ComponentConfig:
|
|
config_root: str | None = None
|
|
pipeline_config_path: str | None = None
|
|
text_encoder_weights: str | None = None
|
|
transformer_weights: str | None = None
|
|
transformer_2_weights: str | None = None
|
|
vae_weights: str | None = None
|
|
upsampler_weights: str | None = None
|
|
lora_path: str | None = None
|
|
override_pipeline_cls_name: str | None = None
|
|
override_transformer_cls_name: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class PipelineSelection:
|
|
workload_type: Literal["t2v", "i2v", "t2i", "i2i"] | None = None
|
|
preset: str | None = None
|
|
preset_version: int | None = None
|
|
components: ComponentConfig = field(default_factory=ComponentConfig)
|
|
vae_tiling: bool | None = None
|
|
"""Tile-based VAE decode. ``None`` keeps the model's default."""
|
|
preset_overrides: dict[str, Any] = field(default_factory=dict)
|
|
experimental: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class GeneratorConfig:
|
|
model_path: str
|
|
revision: str | None = None
|
|
trust_remote_code: bool = False
|
|
engine: EngineConfig = field(default_factory=EngineConfig)
|
|
pipeline: PipelineSelection = field(default_factory=PipelineSelection)
|
|
|
|
|
|
@dataclass
|
|
class InputConfig:
|
|
prompt_path: str | None = None
|
|
image_path: str | list[str] | None = None
|
|
video_path: str | list[str] | None = None
|
|
pil_image: Any | None = None
|
|
pose: str | None = None
|
|
mouse_cond: Any | None = None
|
|
keyboard_cond: Any | None = None
|
|
grid_sizes: Any | None = None
|
|
c2ws_plucker_emb: Any | None = None
|
|
refine_from: str | None = None
|
|
stage1_video: Any | None = None
|
|
|
|
|
|
@dataclass
|
|
class SamplingConfig:
|
|
"""Sampling knobs for a generation request.
|
|
|
|
``None`` means "not specified": the value is inherited from the
|
|
model's preset (``SamplingParam.from_pretrained``) at generate time.
|
|
Set a field to override the preset. There is deliberately no way to
|
|
express "explicitly the schema default" — pass the concrete value
|
|
you want instead.
|
|
"""
|
|
num_videos_per_prompt: int | None = None
|
|
seed: int | None = None
|
|
num_frames: int | None = None
|
|
height: int | None = None
|
|
width: int | None = None
|
|
height_sr: int | None = None
|
|
width_sr: int | None = None
|
|
fps: int | None = None
|
|
num_inference_steps: int | None = None
|
|
num_inference_steps_sr: int | None = None
|
|
guidance_scale: float | None = None
|
|
guidance_scale_2: float | None = None
|
|
guidance_rescale: float | None = None
|
|
true_cfg_scale: float | None = None
|
|
boundary_ratio: float | None = None
|
|
sigmas: list[float] | None = None
|
|
|
|
|
|
@dataclass
|
|
class RequestRuntimeConfig:
|
|
enable_teacache: bool = False
|
|
return_trajectory_latents: bool = False
|
|
return_trajectory_decoded: bool = False
|
|
|
|
|
|
@dataclass
|
|
class OutputConfig:
|
|
output_path: str = "outputs/"
|
|
output_video_name: str | None = None
|
|
save_video: bool = True
|
|
return_frames: bool = True
|
|
return_state: bool = False
|
|
|
|
|
|
@dataclass
|
|
class ContinuationState:
|
|
kind: str
|
|
payload: dict[str, Any]
|
|
|
|
|
|
@dataclass
|
|
class PlannedStage:
|
|
name: str
|
|
kind: str
|
|
source: str | None = None
|
|
overrides: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class GenerationPlan:
|
|
stages: list[PlannedStage]
|
|
final_stage: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class GenerationRequest:
|
|
prompt: str | list[str] | None = None
|
|
negative_prompt: str | None = None
|
|
"""``None`` inherits the model preset's negative prompt; pass ``""``
|
|
to explicitly clear it."""
|
|
inputs: InputConfig = field(default_factory=InputConfig)
|
|
sampling: SamplingConfig = field(default_factory=SamplingConfig)
|
|
runtime: RequestRuntimeConfig = field(default_factory=RequestRuntimeConfig)
|
|
output: OutputConfig = field(default_factory=OutputConfig)
|
|
stage_overrides: dict[str, Any] = field(default_factory=dict)
|
|
state: ContinuationState | None = None
|
|
plan: GenerationPlan | None = None
|
|
extensions: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class RunConfig:
|
|
generator: GeneratorConfig
|
|
request: GenerationRequest
|
|
|
|
|
|
@dataclass
|
|
class WarmupConfig:
|
|
enabled: bool = True
|
|
prompt: str = ("A cinematic drone shot over coastal cliffs at sunrise, "
|
|
"golden light, gentle ocean waves, ultra detailed")
|
|
timeout_seconds: int = 2400
|
|
|
|
|
|
@dataclass
|
|
class GpuPoolConfig:
|
|
num_workers: int | None = None
|
|
enable_audio_reencode: bool = True
|
|
conditioning_num_frames: int = 9
|
|
conditioning_end_offset: int = 0
|
|
|
|
|
|
@dataclass
|
|
class PromptEnhancerConfig:
|
|
enabled: bool = False
|
|
provider: Literal["cerebras", "groq"] = "cerebras"
|
|
model: str = "gpt-oss-120b"
|
|
timeout_ms: int = 20000
|
|
system_prompt_dir: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class PromptSafetyConfig:
|
|
enabled: bool = False
|
|
classifier_path: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class StreamingConfig:
|
|
session_timeout_seconds: int = 300
|
|
generation_segment_cap: int = 6
|
|
stream_mode: Literal["av_fmp4", "legacy_jpeg"] = "av_fmp4"
|
|
warmup: WarmupConfig = field(default_factory=WarmupConfig)
|
|
pool: GpuPoolConfig = field(default_factory=GpuPoolConfig)
|
|
prompt: PromptEnhancerConfig = field(default_factory=PromptEnhancerConfig)
|
|
safety: PromptSafetyConfig = field(default_factory=PromptSafetyConfig)
|
|
|
|
|
|
@dataclass
|
|
class ServeConfig:
|
|
"""Typed serve config loaded from ``fastvideo serve --config``.
|
|
|
|
``default_request`` is a full :class:`GenerationRequest` — the same type
|
|
clients POST to ``/v1/videos``. At request time the server merges it into
|
|
the incoming body as the operator-pinned baseline.
|
|
|
|
Important nuance: only fields the operator **explicitly wrote** in the
|
|
serve YAML/JSON count as defaults. Unset sampling fields stay ``None``
|
|
("inherit the model preset"), other sections keep their schema
|
|
defaults in memory — but the merge walks ``_fastvideo_explicit_paths``
|
|
(populated during parse; explicit ``null`` is treated as unset), so
|
|
unset fields are *not* forced onto requests. Per-request precedence:
|
|
|
|
body (client-explicit) > default_request (operator-explicit)
|
|
> hardcoded fallback (e.g. ``fps=24``)
|
|
|
|
See :func:`fastvideo.api.translation.explicit_request_updates` for the
|
|
projection and ``entrypoints/openai/video_api.py::_build_generation_kwargs``
|
|
for the merge.
|
|
"""
|
|
generator: GeneratorConfig
|
|
server: ServerConfig = field(default_factory=ServerConfig)
|
|
default_request: GenerationRequest = field(default_factory=GenerationRequest)
|
|
streaming: StreamingConfig | None = None
|
|
|
|
|
|
__all__ = [
|
|
"CompileConfig",
|
|
"ComponentConfig",
|
|
"ContinuationState",
|
|
"EngineConfig",
|
|
"GenerationPlan",
|
|
"GenerationRequest",
|
|
"GeneratorConfig",
|
|
"GpuPoolConfig",
|
|
"InputConfig",
|
|
"OffloadConfig",
|
|
"OutputConfig",
|
|
"ParallelismConfig",
|
|
"PipelineSelection",
|
|
"PlannedStage",
|
|
"PromptEnhancerConfig",
|
|
"PromptSafetyConfig",
|
|
"QuantizationConfig",
|
|
"RequestRuntimeConfig",
|
|
"RunConfig",
|
|
"SamplingConfig",
|
|
"ServeConfig",
|
|
"ServerConfig",
|
|
"StreamingConfig",
|
|
"WarmupConfig",
|
|
]
|