Files
Davids048andClaude Fable 5.1 1ea7278c00 [refactor]: group model-family settings under pipeline.model
pipeline.model is a tagged union: a mapping with exactly one key, which
names the model family (ltx2, minimax_h3, longcat, or generic) and selects
that family's options class. The former sibling sections pipeline.ltx2,
pipeline.minimax_h3, and pipeline.longcat, and the DiT/VAE architecture
override dicts pipeline.dit and pipeline.vae, live inside the family block.
The parser implements tagged unions as a general mechanism (a dataclass
with a TAGS table); the family is readable as pipeline.model.family.

Resolution validates the tag against the family that the registry chose
for model_path (validate_model_family raises and names the registry class
and the right block) and fills the family's empty block when the input has
none (fill_model_family), so readers never see None. Family-only fills and
derivations (the MiniMax-H3 parallel-VAE environment variables, the LTX-2
tile-size derivation and refine preset copy, the LongCat BSA mirrors) run
only for their family.

Against the 3f6893a0 goldens every PipelineConfig is identical and no
other field differs beyond the allowed categories; family-only settings
are no longer decided on models of another family. LingBot-Video reads
its refiner switch from pipeline.preset_overrides.refine.enabled and loads
the refiner by default when the checkpoint has one, as at 3f6893a0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbSuTKA9oX4YsECdcBPAsX
2026-10-05 06:04:17 +00:00

442 lines
17 KiB
Python

# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import dataclasses
import enum
import json
import types
from pathlib import Path
from collections.abc import Mapping
from typing import Any, Literal, TypeVar, Union, get_args, get_origin, get_type_hints
import yaml
from fastvideo.api.errors import ConfigValidationError
from fastvideo.api.overrides import apply_overrides, normalize_overrides
from fastvideo.api.request_metadata import (
bind_generation_request_raw,
bind_run_config_raw,
bind_serve_config_raw,
)
from fastvideo.api.schema import GenerationRequest, RunConfig, ServeConfig
T = TypeVar("T")
_UNION_ORIGINS = {types.UnionType, Union}
@dataclasses.dataclass(frozen=True)
class _DataclassSpec:
cls: type[Any]
type_hints: dict[str, Any]
fields_by_name: dict[str, dataclasses.Field[Any]]
def parse_config(config_type: type[T], raw: Mapping[str, Any] | T) -> T:
"""Parse a nested mapping into a typed inference config object."""
if isinstance(raw, config_type):
return raw
if not isinstance(raw, Mapping):
raise ConfigValidationError("", f"expected mapping for {config_type.__name__}")
parsed = _SchemaParser().parse_dataclass(config_type, raw, "")
if config_type is GenerationRequest:
return bind_generation_request_raw(parsed, raw)
if config_type is RunConfig:
return bind_run_config_raw(parsed, raw)
if config_type is ServeConfig:
return bind_serve_config_raw(parsed, raw)
return parsed
def config_to_dict(config: Any) -> Any:
"""Serialize a typed config object into plain Python containers.
A tagged-union member is written in its input form, ``{tag: fields}``, without the ``family`` tag field, so the
result parses back with :func:`parse_config`.
"""
if dataclasses.is_dataclass(config) and not isinstance(config, type):
tag = union_tag(config)
fields = {
field.name: config_to_dict(getattr(config, field.name))
for field in dataclasses.fields(config) if tag is None or field.name != TAG_FIELD
}
return fields if tag is None else {tag: fields}
if isinstance(config, list):
return [config_to_dict(item) for item in config]
if isinstance(config, dict):
return {key: config_to_dict(value) for key, value in config.items()}
return config
def load_config(
config_type: type[T],
path: str | Path,
overrides: list[str] | Mapping[str, Any] | None = None,
) -> T:
"""Load a typed config object from YAML or JSON."""
raw = load_raw_config(path)
normalized_overrides = normalize_overrides(overrides)
if normalized_overrides:
raw = apply_overrides(raw, normalized_overrides)
return parse_config(config_type, raw)
def load_run_config(
path: str | Path,
overrides: list[str] | Mapping[str, Any] | None = None,
) -> RunConfig:
return load_config(RunConfig, path, overrides)
def load_serve_config(
path: str | Path,
overrides: list[str] | Mapping[str, Any] | None = None,
) -> ServeConfig:
return load_config(ServeConfig, path, overrides)
def load_raw_config(path: str | Path) -> dict[str, Any]:
config_path = Path(path)
if not config_path.exists():
raise FileNotFoundError(f"Config file not found: {config_path}")
with config_path.open(encoding="utf-8") as handle:
raw = _load_raw_mapping(handle, config_path)
if raw is None:
return {}
if not isinstance(raw, Mapping):
raise ConfigValidationError("", f"{config_path} must contain a top-level mapping")
return dict(raw)
def _load_raw_mapping(handle: Any, config_path: Path) -> Any:
suffix = config_path.suffix.lower()
if suffix in {".yaml", ".yml"}:
return yaml.safe_load(handle)
if suffix == ".json":
return json.load(handle)
raise ValueError(f"Unsupported config file format: {config_path}")
# A tagged union is a dataclass that defines ``TAGS`` in its own namespace: a mapping from tag to the dataclass that
# the tag selects. Its input is a mapping with exactly one key, the tag, whose value is parsed as the selected class.
# Every selected class carries the tag in the ``TAG_FIELD`` field, whose default is the tag; the input never writes it.
TAG_FIELD = "family"
def tagged_union_tags(config_type: Any) -> Mapping[str, type[Any]] | None:
"""The ``TAGS`` table of a tagged-union dataclass, or ``None`` for any other annotation.
Only the class that defines ``TAGS`` is the union; the classes that it selects inherit the attribute and parse as
plain dataclasses.
"""
if not isinstance(config_type, type) or not dataclasses.is_dataclass(config_type):
return None
return vars(config_type).get("TAGS")
def union_tag(config: Any) -> str | None:
"""The tag under which a tagged-union member instance is written, or ``None`` for any other value."""
for base in type(config).__mro__:
tags = tagged_union_tags(base)
if tags is not None:
for tag, member_type in tags.items():
if member_type is type(config):
return tag
raise ConfigValidationError("", f"{type(config).__name__} is not in the TAGS of {base.__name__}")
return None
def strip_union_tags(config_type: type[Any], paths: set[str]) -> set[str]:
"""Rewrite dotted input paths through a tagged-union field into the field paths of the parsed config.
An input path ``pipeline.model.ltx2.refine.enabled`` names the member block by its tag; the parsed config holds
the block at ``pipeline.model``, so the path becomes ``pipeline.model.refine.enabled``. Paths that do not go
through a tagged-union field of ``config_type`` are unchanged.
"""
union_paths = _tagged_union_field_paths(config_type, "")
if not union_paths:
return paths
stripped: set[str] = set()
for path in paths:
for union_path, tags in union_paths.items():
if path.startswith(union_path + "."):
tag, _, rest = path[len(union_path) + 1:].partition(".")
if tag in tags:
path = _join_path(union_path, rest) if rest else union_path
break
stripped.add(path)
return stripped
def _tagged_union_field_paths(config_type: type[Any], prefix: str) -> dict[str, Mapping[str, type[Any]]]:
"""Dotted path -> ``TAGS`` for every tagged-union field reachable through the nested dataclass fields."""
found: dict[str, Mapping[str, type[Any]]] = {}
spec = _get_dataclass_spec(config_type)
for name in spec.fields_by_name:
field_path = _join_path(prefix, name)
for candidate in _annotation_classes(spec.type_hints[name]):
tags = tagged_union_tags(candidate)
if tags is not None:
found[field_path] = tags
elif dataclasses.is_dataclass(candidate):
found.update(_tagged_union_field_paths(candidate, field_path))
return found
def _annotation_classes(annotation: Any) -> list[type[Any]]:
"""The classes that an annotation names directly or as members of an optional or union annotation."""
if get_origin(annotation) in _UNION_ORIGINS:
return [candidate for candidate in get_args(annotation) if isinstance(candidate, type)]
return [annotation] if isinstance(annotation, type) else []
class _SchemaParser:
def parse_dataclass(
self,
config_type: type[T],
raw: Mapping[str, Any],
path: str,
) -> T:
if not isinstance(raw, Mapping):
raise ConfigValidationError(path, f"expected mapping for {config_type.__name__}")
spec = _get_dataclass_spec(config_type)
self._validate_keys(raw, spec, path)
values: dict[str, Any] = {}
for name, field in spec.fields_by_name.items():
field_path = _join_path(path, name)
if name in raw:
values[name] = self.parse_value(spec.type_hints[name], raw[name], field_path)
continue
if _field_is_required(field):
raise ConfigValidationError(field_path, "missing required field")
return config_type(**values)
def parse_value(self, annotation: Any, value: Any, path: str) -> Any:
if annotation is Any:
return value
origin = get_origin(annotation)
if origin in _UNION_ORIGINS:
return self._parse_union(annotation, value, path)
if origin is Literal:
return self._parse_literal(annotation, value, path)
if origin is list:
return self._parse_list(annotation, value, path)
if origin is dict:
return self._parse_dict(annotation, value, path)
if origin is tuple:
return self._parse_tuple(annotation, value, path)
if isinstance(annotation, type) and dataclasses.is_dataclass(annotation):
tags = tagged_union_tags(annotation)
if tags is not None:
return self._parse_tagged_union(annotation, tags, value, path)
return self.parse_dataclass(annotation, value, path)
if isinstance(annotation, type) and issubclass(annotation, enum.Enum):
return self._parse_enum(annotation, value, path)
scalar_parser = _SCALAR_PARSERS.get(annotation)
if scalar_parser is not None:
return scalar_parser(value, path)
return self._parse_instance(annotation, value, path)
def _validate_keys(
self,
raw: Mapping[str, Any],
spec: _DataclassSpec,
path: str,
) -> None:
for key in raw:
if not isinstance(key, str):
raise ConfigValidationError(path, "expected mapping keys to be strings")
if key not in spec.fields_by_name:
raise ConfigValidationError(_join_path(path, key), "unknown field")
def _parse_tagged_union(self, union_type: type[Any], tags: Mapping[str, type[Any]], value: Any, path: str) -> Any:
"""Parse ``{tag: fields}`` into the member class that ``tags[tag]`` names, with ``TAG_FIELD`` set to the tag."""
if not isinstance(value, Mapping):
raise ConfigValidationError(path, f"expected mapping with one key for {union_type.__name__}")
choices = ", ".join(sorted(tags))
if len(value) != 1:
raise ConfigValidationError(path, f"expected exactly one key, one of ({choices}); got {sorted(value)!r}")
tag, fields = next(iter(value.items()))
if tag not in tags:
raise ConfigValidationError(_join_path(path, str(tag)), f"unknown key; expected one of ({choices})")
member_path = _join_path(path, tag)
if isinstance(fields, Mapping) and TAG_FIELD in fields:
raise ConfigValidationError(_join_path(member_path, TAG_FIELD), f"set by the key {tag!r}; remove it")
return self.parse_dataclass(tags[tag], fields, member_path)
def _parse_union(self, annotation: Any, value: Any, path: str) -> Any:
candidates = [candidate for candidate in get_args(annotation) if candidate is not type(None)]
if value is None and len(candidates) != len(get_args(annotation)):
return None
if len(candidates) == 1:
return self.parse_value(candidates[0], value, path)
errors: list[str] = []
for candidate in candidates:
try:
return self.parse_value(candidate, value, path)
except ConfigValidationError as exc:
errors.append(exc.message)
expected = ", ".join(_type_name(candidate) for candidate in candidates)
detail = errors[0] if errors else f"expected one of ({expected})"
raise ConfigValidationError(path, detail)
def _parse_literal(self, annotation: Any, value: Any, path: str) -> Any:
allowed = get_args(annotation)
if value not in allowed:
raise ConfigValidationError(path, f"expected one of {sorted(allowed)!r}")
return value
def _parse_enum(self, annotation: type[enum.Enum], value: Any, path: str) -> enum.Enum:
"""An enum member passes through; any other value must be the value of a member."""
if isinstance(value, annotation):
return value
try:
return annotation(value)
except (TypeError, ValueError):
allowed = [member.value for member in annotation]
raise ConfigValidationError(path, f"expected one of {allowed!r}") from None
def _parse_list(self, annotation: Any, value: Any, path: str) -> list[Any]:
if not isinstance(value, list):
raise ConfigValidationError(path, "expected list")
item_type = get_args(annotation)[0] if get_args(annotation) else Any
return [self.parse_value(item_type, item, f"{path}[{index}]") for index, item in enumerate(value)]
def _parse_dict(self, annotation: Any, value: Any, path: str) -> dict[Any, Any]:
if not isinstance(value, Mapping):
raise ConfigValidationError(path, "expected mapping")
key_type, value_type = (get_args(annotation) + (Any, Any))[:2]
parsed: dict[Any, Any] = {}
for key, item in value.items():
parsed_key = self._parse_dict_key(key_type, key, path)
item_path = _join_path(path, str(key))
parsed[parsed_key] = self.parse_value(value_type, item, item_path)
return parsed
def _parse_tuple(self, annotation: Any, value: Any, path: str) -> tuple[Any, ...]:
if not isinstance(value, list | tuple):
raise ConfigValidationError(path, "expected tuple")
item_types = get_args(annotation)
if len(item_types) == 2 and item_types[1] is Ellipsis:
return tuple(self.parse_value(item_types[0], item, f"{path}[{index}]") for index, item in enumerate(value))
if len(value) != len(item_types):
raise ConfigValidationError(path, f"expected tuple of length {len(item_types)}")
return tuple(
self.parse_value(item_type, item, f"{path}[{index}]")
for index, (item_type, item) in enumerate(zip(item_types, value, strict=True)))
def _parse_dict_key(self, annotation: Any, value: Any, path: str) -> Any:
if annotation is Any:
return value
if annotation is str:
if not isinstance(value, str):
raise ConfigValidationError(path, "expected string dictionary keys")
return value
if annotation is int:
if not isinstance(value, int) or isinstance(value, bool):
raise ConfigValidationError(path, "expected integer dictionary keys")
return value
return value
def _parse_instance(self, annotation: Any, value: Any, path: str) -> Any:
if isinstance(annotation, type) and not isinstance(value, annotation):
raise ConfigValidationError(path, f"expected {annotation.__name__}")
return value
def _parse_bool(value: Any, path: str) -> bool:
if type(value) is not bool:
raise ConfigValidationError(path, "expected bool")
return value
def _parse_int(value: Any, path: str) -> int:
if not isinstance(value, int) or isinstance(value, bool):
raise ConfigValidationError(path, "expected int")
return value
def _parse_float(value: Any, path: str) -> float:
if not isinstance(value, int | float) or isinstance(value, bool):
raise ConfigValidationError(path, "expected float")
return float(value)
def _parse_str(value: Any, path: str) -> str:
if not isinstance(value, str):
raise ConfigValidationError(path, "expected str")
return value
_SCALAR_PARSERS: dict[Any, Any] = {
bool: _parse_bool,
int: _parse_int,
float: _parse_float,
str: _parse_str,
}
def _field_is_required(field: dataclasses.Field[Any]) -> bool:
return (field.default is dataclasses.MISSING and field.default_factory is dataclasses.MISSING)
def _get_dataclass_spec(config_type: type[Any]) -> _DataclassSpec:
spec = _DATACLASS_SPEC_CACHE.get(config_type)
if spec is not None:
return spec
spec = _DataclassSpec(
cls=config_type,
type_hints=get_type_hints(config_type),
fields_by_name={field.name: field
for field in dataclasses.fields(config_type)},
)
_DATACLASS_SPEC_CACHE[config_type] = spec
return spec
_DATACLASS_SPEC_CACHE: dict[type[Any], _DataclassSpec] = {}
def _join_path(prefix: str, suffix: str) -> str:
if not prefix:
return suffix
return f"{prefix}.{suffix}"
def _type_name(annotation: Any) -> str:
origin = get_origin(annotation)
if origin is not None:
return str(annotation)
if hasattr(annotation, "__name__"):
return annotation.__name__
return str(annotation)
__all__ = [
"TAG_FIELD",
"config_to_dict",
"load_config",
"load_raw_config",
"load_run_config",
"load_serve_config",
"parse_config",
"strip_union_tags",
"tagged_union_tags",
"union_tag",
]