Files
2026-07-13 12:31:40 +08:00

175 lines
6.5 KiB
Python

# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# To view a copy of this license, visit http://www.apache.org/licenses/LICENSE-2.0
#
# No warranties are given. The work is provided "AS IS", without warranty of any kind, express or implied.
#
# SPDX-License-Identifier: Apache-2.0
from omegaconf import OmegaConf
DEFAULT_NEGATIVE_PROMPT = (
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,"
"整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,"
"画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,"
"手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
)
wan_default_config = {
"Wan2.2-TI2V-5B": {
"resolution": [1280, 704],
"temporal_compression_ratio": 4,
"spatial_compression_ratio": 16,
"num_heads": 24,
"head_dim": 128,
"num_transformer_blocks": 30,
"fps": 24,
}
}
SECTION_KEYS = (
"infra",
"algorithm",
"training",
"data",
"evaluation",
"inference",
"logging",
"checkpoints",
)
def _set_once(config, key, value, source):
if value is None:
return
if key in config and config[key] != value:
raise ValueError(
f"{key} is defined more than once with different values: "
f"{config[key]} vs {value} from {source}."
)
config[key] = value
def section_get(config, section_key, key, default=None, aliases=()):
"""Read a grouped config value, falling back to legacy flat names."""
section = config.get(section_key, None)
candidate_keys = (key, *aliases)
if section is not None:
for candidate in candidate_keys:
if candidate in section:
return section[candidate]
for candidate in candidate_keys:
if candidate in config:
return config[candidate]
return default
def normalize_config(config):
"""Expand grouped release configs into the flat runtime schema.
The training and inference code historically reads fields such as
``config.batch_size`` and ``config.model_kwargs`` directly. Release
configs can group those fields for readability, then call this function at
the entry point to preserve the existing runtime contract.
"""
for section_key in SECTION_KEYS:
section = config.get(section_key, None)
if section is None:
continue
for key, value in section.items():
config[key] = value
evaluation = config.get("evaluation", None)
if evaluation is not None:
if "interval" in evaluation:
_set_once(config, "generate_interval", evaluation.interval, "evaluation.interval")
_set_once(config, "vis_interval", evaluation.interval, "evaluation.interval")
if "num_frames" in evaluation:
num_frames = evaluation.num_frames
if isinstance(num_frames, (list, tuple)):
vis_lengths = list(num_frames)
inference_num_frames = vis_lengths[0] if vis_lengths else 0
else:
inference_num_frames = int(num_frames)
vis_lengths = [inference_num_frames]
_set_once(config, "inference_num_frames", inference_num_frames, "evaluation.num_frames")
_set_once(config, "vis_video_lengths", vis_lengths, "evaluation.num_frames")
if "use_ema" in evaluation:
_set_once(config, "vis_ema", evaluation.use_ema, "evaluation.use_ema")
model_section = config.get("model", None)
base_model_kwargs = config.get("model_kwargs", None)
model_kwargs = OmegaConf.create({})
if base_model_kwargs is not None:
model_kwargs = OmegaConf.merge(model_kwargs, base_model_kwargs)
if model_section is not None:
section_kwargs = model_section.get("kwargs", None)
if section_kwargs is not None:
model_kwargs = OmegaConf.merge(model_kwargs, section_kwargs)
model_name = model_section.get("name", None)
if model_name is not None:
model_kwargs.model_name = model_name
config.model_name = model_name
_set_once(
config,
"num_frame_per_block",
model_section.get("num_frame_per_block", None),
"model.num_frame_per_block",
)
if "model_name" in config and "model_name" not in model_kwargs:
model_kwargs.model_name = config.model_name
if "timestep_shift" in config and "timestep_shift" not in model_kwargs:
model_kwargs.timestep_shift = config.timestep_shift
if "timestep_shift" in model_kwargs:
_set_once(config, "timestep_shift", model_kwargs.timestep_shift, "model_kwargs.timestep_shift")
model_num_frame_per_block = model_kwargs.get("num_frame_per_block", None)
if model_num_frame_per_block is not None:
_set_once(config, "num_frame_per_block", model_num_frame_per_block, "model_kwargs.num_frame_per_block")
if len(model_kwargs) > 0:
config.model_kwargs = model_kwargs
if "wandb_host" not in config:
config.wandb_host = "https://api.wandb.ai"
if "negative_prompt" not in config:
config.negative_prompt = DEFAULT_NEGATIVE_PROMPT
if config.get("trainer", None) == "score_distillation":
dmd_defaults = {
"i2v": False,
"teacher_forcing": False,
"backward_simulation": True,
"independent_first_frame": False,
"num_train_timestep": 1000,
"denoising_loss_type": "flow",
"real_guidance_scale": 3.0,
"fake_guidance_scale": 0.0,
}
for key, value in dmd_defaults.items():
if key not in config:
config[key] = value
if "causal" not in config:
config.causal = bool(config.get("all_causal", True))
# Causal DMD uses the same Wan backbone for generator/teacher/critic unless
# a role-specific override is explicitly provided.
if getattr(config, "all_causal", False) and "model_kwargs" in config:
for role_key in ("real_model_kwargs", "fake_model_kwargs"):
if config.get(role_key, None) is None:
config[role_key] = OmegaConf.create(
OmegaConf.to_container(config.model_kwargs, resolve=True)
)
return config