A pipeline materializes every component before the first stage runs, so peak memory is the sum of all components even though no two are needed at the same moment. The CPU offload flags cannot help with this: they act after loading, and on a unified-memory device moving weights to the host frees nothing because it is the same pool. Add lazy_module_load, off by default. Heavy components become a LazyModule proxy that loads on first use, and the pipeline installs a release hook on the last stage that holds each one. Peak becomes the largest overlapping set instead of the sum. Measured on MiniMax-H3 r16, 121 GiB GB10, 1 GPU, 192x320, 4 steps. Peak CUDA allocated is 57.7 GiB, reached during conditioning where the text encoder and video VAE overlap. The four deferred components account for 96.3 GiB together, which is what stays resident without the flag: text encoder 48.0, DiT 37.8, video VAE 9.7, audio VAE 0.8. Generation completed and wrote a video. Details worth flagging: The release hook lives on PipelineStage.__call__, not in the pipeline stage loop, so pipelines that override forward still free. The proxy forwards __class__, so isinstance stays honest. A proxy answering False to isinstance(module, FSDPModule) would take the wrong branch silently. Self-returning methods hand back the proxy rather than the component. Stages write self.vae = self.vae.to(device) in a dozen places; returning the component there would replace the proxy with a reference the pipeline cannot release, and the run would look normal while freeing nothing. Releasing is a latency cost, never a correctness one: a released component reloads on next access. Training keeps everything resident and warns if the flag is set. If no stage holds a deferred component the pipeline warns rather than silently doing nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
327 lines
9.7 KiB
Python
327 lines
9.7 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/"
|
|
served_model_name: str | None = None
|
|
|
|
|
|
@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
|
|
# Not a CPU offload: loads each heavy component on first use and frees it
|
|
# after the last stage that needs it, so peak memory is the largest
|
|
# overlapping set rather than the sum. Grouped here because it is the same
|
|
# decision the offload knobs answer, which is how much of the model has to
|
|
# be resident at once.
|
|
lazy_module_load: bool = False
|
|
|
|
|
|
@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
|
|
lora_nickname: str = "default"
|
|
lora_strength: float = 1.0
|
|
override_pipeline_cls_name: str | None = None
|
|
override_transformer_cls_name: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class PipelineSelection:
|
|
workload_type: Literal["t2v", "i2v", "t2i", "i2i", "v2a", "t2a"] | 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
|
|
last_image: Any | None = None
|
|
references: list[Any] | None = None
|
|
latents: Any | None = None
|
|
audio_latents: 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
|
|
action_path: str | None = None
|
|
refine_from: str | None = None
|
|
stage1_video: Any | None = None
|
|
|
|
|
|
@dataclass
|
|
class SamplingConfig:
|
|
num_videos_per_prompt: int = 1
|
|
seed: int = 1024
|
|
max_sequence_length: int | None = None
|
|
num_frames: int = 125
|
|
height: int = 720
|
|
width: int = 1280
|
|
height_sr: int = 1072
|
|
width_sr: int = 1920
|
|
fps: int = 24
|
|
num_inference_steps: int = 50
|
|
num_inference_steps_sr: int = 50
|
|
guidance_scale: float = 1.0
|
|
batch_cfg: bool = False
|
|
guidance_scale_2: float | None = None
|
|
cfg_normalization: bool = False
|
|
cfg_truncation: float | None = 1.0
|
|
guidance_rescale: float = 0.0
|
|
true_cfg_scale: float | None = None
|
|
use_embedded_guidance: bool | 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
|
|
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. Although the in-memory object is
|
|
fully populated (schema defaults fill every unset field), the merge
|
|
walks ``_fastvideo_explicit_paths`` — populated during parse — 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.compat.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",
|
|
]
|