[diffusion] feat: support passing component path via server args (#19108)

This commit is contained in:
Mick
2026-02-21 21:22:47 +08:00
committed by GitHub
parent c36a10aabb
commit 6503f94211
18 changed files with 170 additions and 77 deletions
+26 -1
View File
@@ -12,7 +12,6 @@ The SGLang-diffusion CLI provides a quick way to access the inference pipeline f
### Server Arguments ### Server Arguments
- `--model-path {MODEL_PATH}`: Path to the model or model ID - `--model-path {MODEL_PATH}`: Path to the model or model ID
- `--vae-path {VAE_PATH}`: Path to a custom VAE model or HuggingFace model ID (e.g., `fal/FLUX.2-Tiny-AutoEncoder`). If not specified, the VAE will be loaded from the main model path.
- `--lora-path {LORA_PATH}`: Path to a LoRA adapter (local path or HuggingFace model ID). If not specified, LoRA will not be applied. - `--lora-path {LORA_PATH}`: Path to a LoRA adapter (local path or HuggingFace model ID). If not specified, LoRA will not be applied.
- `--lora-nickname {NAME}`: Nickname for the LoRA adapter. (default: `default`). - `--lora-nickname {NAME}`: Nickname for the LoRA adapter. (default: `default`).
- `--num-gpus {NUM_GPUS}`: Number of GPUs to use - `--num-gpus {NUM_GPUS}`: Number of GPUs to use
@@ -218,6 +217,32 @@ Once the generation task has finished, the server will shut down automatically.
> [!NOTE] > [!NOTE]
> The HTTP server-related arguments are ignored in this subcommand. > The HTTP server-related arguments are ignored in this subcommand.
## Component Path Overrides
SGLang diffusion allows you to override any pipeline component (e.g., `vae`, `transformer`, `text_encoder`) by specifying a custom checkpoint path. This is useful for:
### Example: FLUX.2-dev with Tiny AutoEncoder
You can override **any** component by using `--<component>-path`, where `<component>` matches the key in the model's `model_index.json`:
For example, replace the default VAE with a distilled tiny autoencoder for ~3x faster decoding:
```bash
sglang serve \
--model-path=black-forest-labs/FLUX.2-dev \
# with a Huggingface Repo ID
--vae-path=fal/FLUX.2-Tiny-AutoEncoder
# or use a local path
--vae-path=~/.cache/huggingface/hub/models--fal--FLUX.2-Tiny-AutoEncoder/snapshots/.../vae
```
**Important:**
- The component key must match the one in your model's `model_index.json` (e.g., `vae`).
- The path must:
- either be a Huggingface Repo ID (e.g., fal/FLUX.2-Tiny-AutoEncoder)
- or point to a **complete component folder**, containing `config.json` and safetensors files
## Diffusers Backend ## Diffusers Backend
SGLang diffusion supports a **diffusers backend** that allows you to run any diffusers-compatible model through SGLang's infrastructure using vanilla diffusers pipelines. This is useful for running models without native SGLang implementations or models with custom pipeline classes. SGLang diffusion supports a **diffusers backend** that allows you to run any diffusers-compatible model through SGLang's infrastructure using vanilla diffusers pipelines. This is useful for running models without native SGLang implementations or models with custom pipeline classes.
+2 -2
View File
@@ -25,8 +25,8 @@ def generate(args, extra_argv):
parser = argparse.ArgumentParser(description="SGLang Multimodal Generation") parser = argparse.ArgumentParser(description="SGLang Multimodal Generation")
add_multimodal_gen_generate_args(parser) add_multimodal_gen_generate_args(parser)
parsed_args = parser.parse_args(extra_argv) parsed_args, unknown_args = parser.parse_known_args(extra_argv)
generate_cmd(parsed_args) generate_cmd(parsed_args, unknown_args)
else: else:
raise Exception( raise Exception(
f"Generate subcommand is not yet supported for model: {model_path}" f"Generate subcommand is not yet supported for model: {model_path}"
@@ -13,7 +13,9 @@ class CLISubcommand:
name: str name: str
def cmd(self, args: argparse.Namespace) -> None: def cmd(
self, args: argparse.Namespace, unknown_args: list[str] | None = None
) -> None:
"""Execute the command with the given arguments""" """Execute the command with the given arguments"""
raise NotImplementedError raise NotImplementedError
@@ -106,11 +106,11 @@ def maybe_dump_performance(
) )
def generate_cmd(args: argparse.Namespace): def generate_cmd(args: argparse.Namespace, unknown_args: list[str] | None = None):
"""The entry point for the generate command.""" """The entry point for the generate command."""
args.request_id = "mocked_fake_id_for_offline_generate" args.request_id = "mocked_fake_id_for_offline_generate"
server_args = ServerArgs.from_cli_args(args) server_args = ServerArgs.from_cli_args(args, unknown_args)
sampling_params_kwargs = SamplingParams.get_cli_args(args) sampling_params_kwargs = SamplingParams.get_cli_args(args)
sampling_params_kwargs["request_id"] = generate_request_id() sampling_params_kwargs["request_id"] = generate_request_id()
@@ -158,8 +158,10 @@ class GenerateSubcommand(CLISubcommand):
"""Get names of arguments for generate_video method""" """Get names of arguments for generate_video method"""
return [field.name for field in dataclasses.fields(SamplingParams)] return [field.name for field in dataclasses.fields(SamplingParams)]
def cmd(self, args: argparse.Namespace) -> None: def cmd(
generate_cmd(args) self, args: argparse.Namespace, unknown_args: list[str] | None = None
) -> None:
generate_cmd(args, unknown_args)
def validate(self, args: argparse.Namespace) -> None: def validate(self, args: argparse.Namespace) -> None:
"""Validate the arguments for this command""" """Validate the arguments for this command"""
@@ -30,12 +30,12 @@ def main() -> None:
for cmd in cmd_init(): for cmd in cmd_init():
cmd.subparser_init(subparsers).set_defaults(dispatch_function=cmd.cmd) cmd.subparser_init(subparsers).set_defaults(dispatch_function=cmd.cmd)
cmds[cmd.name] = cmd cmds[cmd.name] = cmd
args = parser.parse_args() args, unknown_args = parser.parse_known_args()
if args.subparser in cmds: if args.subparser in cmds:
cmds[args.subparser].validate(args) cmds[args.subparser].validate(args)
if hasattr(args, "dispatch_function"): if hasattr(args, "dispatch_function"):
args.dispatch_function(args) args.dispatch_function(args, unknown_args=unknown_args)
else: else:
parser.print_help() parser.print_help()
@@ -31,7 +31,7 @@ class AdapterLoader(ComponentLoader):
def load_customized( def load_customized(
self, component_model_path: str, server_args: ServerArgs, *args self, component_model_path: str, server_args: ServerArgs, *args
): ):
config = get_diffusers_component_config(model_path=component_model_path) config = get_diffusers_component_config(component_path=component_model_path)
cls_name = config.pop("_class_name", None) cls_name = config.pop("_class_name", None)
if cls_name is None: if cls_name is None:
@@ -30,7 +30,7 @@ class BridgeLoader(ComponentLoader):
def load_customized( def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str self, component_model_path: str, server_args: ServerArgs, component_name: str
): ):
config = get_diffusers_component_config(model_path=component_model_path) config = get_diffusers_component_config(component_path=component_model_path)
hf_config = deepcopy(config) hf_config = deepcopy(config)
class_name = config.pop("_class_name", None) class_name = config.pop("_class_name", None)
if class_name is None: if class_name is None:
@@ -21,7 +21,7 @@ class SchedulerLoader(ComponentLoader):
self, component_model_path: str, server_args: ServerArgs, *args self, component_model_path: str, server_args: ServerArgs, *args
): ):
"""Load the scheduler based on the model path, and inference args.""" """Load the scheduler based on the model path, and inference args."""
config = get_diffusers_component_config(model_path=component_model_path) config = get_diffusers_component_config(component_path=component_model_path)
class_name = config.pop("_class_name") class_name = config.pop("_class_name")
assert ( assert (
@@ -182,7 +182,9 @@ class TextEncoderLoader(ComponentLoader):
diffusers_pretrained_config = get_config( diffusers_pretrained_config = get_config(
component_model_path, trust_remote_code=True component_model_path, trust_remote_code=True
) )
model_config = get_diffusers_component_config(model_path=component_model_path) model_config = get_diffusers_component_config(
component_path=component_model_path
)
_clean_hf_config_inplace(model_config) _clean_hf_config_inplace(model_config)
logger.debug("HF model config: %s", model_config) logger.debug("HF model config: %s", model_config)
@@ -69,7 +69,7 @@ class TransformerLoader(ComponentLoader):
self, component_model_path: str, server_args: ServerArgs, component_name: str self, component_model_path: str, server_args: ServerArgs, component_name: str
): ):
"""Load the transformer based on the model path, and inference args.""" """Load the transformer based on the model path, and inference args."""
config = get_diffusers_component_config(model_path=component_model_path) config = get_diffusers_component_config(component_path=component_model_path)
hf_config = deepcopy(config) hf_config = deepcopy(config)
cls_name = config.pop("_class_name") cls_name = config.pop("_class_name")
if cls_name is None: if cls_name is None:
@@ -60,7 +60,7 @@ class VAELoader(ComponentLoader):
self, component_model_path: str, server_args: ServerArgs, component_name: str self, component_model_path: str, server_args: ServerArgs, component_name: str
): ):
"""Load the VAE based on the model path, and inference args.""" """Load the VAE based on the model path, and inference args."""
config = get_diffusers_component_config(model_path=component_model_path) config = get_diffusers_component_config(component_path=component_model_path)
class_name = config.pop("_class_name", None) class_name = config.pop("_class_name", None)
assert ( assert (
class_name is not None class_name is not None
@@ -32,7 +32,7 @@ class VocoderLoader(ComponentLoader):
def load_customized( def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str self, component_model_path: str, server_args: ServerArgs, component_name: str
): ):
config = get_diffusers_component_config(model_path=component_model_path) config = get_diffusers_component_config(component_path=component_model_path)
class_name = config.pop("_class_name", None) class_name = config.pop("_class_name", None)
assert ( assert (
class_name is not None class_name is not None
@@ -391,7 +391,7 @@ class DiffusersPipeline(ComposedPipelineBase):
""" """
original_model_path = model_path # Keep original for custom_pipeline original_model_path = model_path # Keep original for custom_pipeline
model_path = maybe_download_model(model_path) model_path = maybe_download_model(model_path, force_diffusers_model=True)
self.model_path = model_path self.model_path = model_path
dtype = self._get_dtype(server_args) dtype = self._get_dtype(server_args)
@@ -126,9 +126,8 @@ class ComposedPipelineBase(ABC):
self.modules[module_name] = module self.modules[module_name] = module
def _load_config(self) -> dict[str, Any]: def _load_config(self) -> dict[str, Any]:
model_path = maybe_download_model(self.model_path) model_path = maybe_download_model(self.model_path, force_diffusers_model=True)
self.model_path = model_path self.model_path = model_path
# server_args.downloaded_model_path = model_path
logger.info("Model path: %s", model_path) logger.info("Model path: %s", model_path)
config = verify_model_config_and_directory(model_path) config = verify_model_config_and_directory(model_path)
return cast(dict[str, Any], config) return cast(dict[str, Any], config)
@@ -171,6 +170,19 @@ class ComposedPipelineBase(ABC):
""" """
return return
def _resolve_component_path(
self, server_args: ServerArgs, module_name: str, load_module_name: str
) -> str:
override_path = server_args.component_paths.get(module_name)
if override_path is not None:
# overridden with args like --vae-path
component_model_path = maybe_download_model(override_path)
else:
component_model_path = os.path.join(self.model_path, load_module_name)
logger.debug("Resolved component path: %s", component_model_path)
return component_model_path
def load_modules( def load_modules(
self, self,
server_args: ServerArgs, server_args: ServerArgs,
@@ -285,19 +297,9 @@ class ComposedPipelineBase(ABC):
else: else:
load_module_name = module_name load_module_name = module_name
# Use custom VAE path if provided, otherwise use default path component_model_path = self._resolve_component_path(
if module_name == "vae" and server_args.vae_path is not None: server_args, module_name, load_module_name
component_model_path = server_args.vae_path )
# Download from HuggingFace Hub if path doesn't exist locally
if not os.path.exists(component_model_path):
component_model_path = maybe_download_model(component_model_path)
logger.info(
"Using custom VAE path: %s instead of default path: %s",
component_model_path,
os.path.join(self.model_path, load_module_name),
)
else:
component_model_path = os.path.join(self.model_path, load_module_name)
module, memory_usage = PipelineComponentLoader.load_component( module, memory_usage = PipelineComponentLoader.load_component(
component_name=load_module_name, component_name=load_module_name,
component_model_path=component_model_path, component_model_path=component_model_path,
@@ -287,8 +287,8 @@ class ServerArgs:
lora_nickname: str = "default" # for swapping adapters in the pipeline lora_nickname: str = "default" # for swapping adapters in the pipeline
lora_scale: float = 1.0 # LoRA scale for merging (e.g., 0.125 for Hyper-SD) lora_scale: float = 1.0 # LoRA scale for merging (e.g., 0.125 for Hyper-SD)
# VAE parameters # Component path overrides (key = model_index.json component name, value = path)
vae_path: str | None = None # Custom VAE path (e.g., for distilled autoencoder) component_paths: dict[str, str] = field(default_factory=dict)
# can restrict layers to adapt, e.g. ["q_proj"] # can restrict layers to adapt, e.g. ["q_proj"]
# Will adapt only q, k, v, o by default. # Will adapt only q, k, v, o by default.
lora_target_modules: list[str] | None = None lora_target_modules: list[str] | None = None
@@ -623,13 +623,6 @@ class ServerArgs:
type=str, type=str,
help="The path of the model weights. This can be a local folder or a Hugging Face repo ID.", help="The path of the model weights. This can be a local folder or a Hugging Face repo ID.",
) )
parser.add_argument(
"--vae-path",
type=str,
default=ServerArgs.vae_path,
help="Custom path to VAE model (e.g., for distilled autoencoder). If not specified, VAE will be loaded from the main model path.",
)
# attention # attention
parser.add_argument( parser.add_argument(
"--attention-backend", "--attention-backend",
@@ -963,24 +956,66 @@ class ServerArgs:
f"(started from port {original_port})" f"(started from port {original_port})"
) )
@staticmethod
def _extract_component_paths(
unknown_args: list[str],
) -> tuple[dict[str, str], list[str]]:
"""
Extract dynamic ``--<component>-path`` args from unrecognised CLI args.
"""
component_paths: dict[str, str] = {}
remaining: list[str] = []
i = 0
while i < len(unknown_args):
arg = unknown_args[i]
key_part = arg.split("=", 1)[0] if "=" in arg else arg
if key_part.startswith("--") and key_part.endswith("-path"):
component = key_part[2:-5].replace("-", "_")
if "=" in arg:
component_paths[component] = arg.split("=", 1)[1]
elif i + 1 < len(unknown_args) and not unknown_args[i + 1].startswith(
"-"
):
i += 1
component_paths[component] = unknown_args[i]
else:
remaining.append(arg)
i += 1
continue
else:
remaining.append(arg)
i += 1
# canonicalize and validate
for component, path in component_paths.items():
path = os.path.expanduser(path)
component_paths[component] = path
return component_paths, remaining
@classmethod @classmethod
def from_cli_args( def from_cli_args(
cls, args: argparse.Namespace, unknown_args: list[str] | None = None cls, args: argparse.Namespace, unknown_args: list[str] | None = None
) -> "ServerArgs": ) -> "ServerArgs":
if unknown_args is None: if unknown_args is None:
unknown_args = [] unknown_args = []
# extract dynamic --<component>-path from unknown args
dynamic_paths, remaining = cls._extract_component_paths(unknown_args)
if remaining:
raise SystemExit(f"error: unrecognized arguments: {' '.join(remaining)}")
provided_args = cls.get_provided_args(args, unknown_args) provided_args = cls.get_provided_args(args, unknown_args)
# Handle config file # Handle config file
config_file = provided_args.get("config") config_file = provided_args.get("config")
if config_file: if config_file:
config_args = cls.load_config_file(config_file) config_args = cls.load_config_file(config_file)
# Provided args override config file args
provided_args = {**config_args, **provided_args} provided_args = {**config_args, **provided_args}
# Handle special cases if dynamic_paths:
# if "tp_size" in provided_args: existing = dict(provided_args.get("component_paths") or {})
# provided_args["tp"] = provided_args.pop("tp_size") existing.update(dynamic_paths)
provided_args["component_paths"] = existing
return cls.from_dict(provided_args) return cls.from_dict(provided_args)
@@ -990,6 +1025,10 @@ class ServerArgs:
attrs = [attr.name for attr in dataclasses.fields(cls)] attrs = [attr.name for attr in dataclasses.fields(cls)]
server_args_kwargs: dict[str, Any] = {} server_args_kwargs: dict[str, Any] = {}
component_paths = dict(kwargs.get("component_paths") or {})
if component_paths:
server_args_kwargs["component_paths"] = component_paths
for attr in attrs: for attr in attrs:
if attr == "pipeline_config": if attr == "pipeline_config":
pipeline_config = PipelineConfig.from_kwargs(kwargs) pipeline_config = PipelineConfig.from_kwargs(kwargs)
@@ -1239,8 +1278,8 @@ def prepare_server_args(argv: list[str]) -> ServerArgs:
""" """
parser = FlexibleArgumentParser() parser = FlexibleArgumentParser()
ServerArgs.add_cli_args(parser) ServerArgs.add_cli_args(parser)
raw_args = parser.parse_args(argv) raw_args, unknown_args = parser.parse_known_args(argv)
server_args = ServerArgs.from_cli_args(raw_args) server_args = ServerArgs.from_cli_args(raw_args, unknown_args)
return server_args return server_args
@@ -69,9 +69,6 @@ def _check_index_files_for_missing_shards(
missing_files = [] missing_files = []
checked_subdirs = [] checked_subdirs = []
# Check the root directory and all subdirectories that might contain model weights
dirs_to_check = [model_path]
# Add common subdirectories for diffusers models # Add common subdirectories for diffusers models
try: try:
subdirs = os.listdir(model_path) subdirs = os.listdir(model_path)
@@ -79,6 +76,9 @@ def _check_index_files_for_missing_shards(
logger.warning("Failed to list model directory %s: %s", model_path, e) logger.warning("Failed to list model directory %s: %s", model_path, e)
return True, [], [] # Assume valid if we can't check return True, [], [] # Assume valid if we can't check
# Check the root directory and all subdirectories that might contain model weights
dirs_to_check = [model_path]
for subdir in subdirs: for subdir in subdirs:
subdir_path = os.path.join(model_path, subdir) subdir_path = os.path.join(model_path, subdir)
if os.path.isdir(subdir_path): if os.path.isdir(subdir_path):
@@ -176,7 +176,6 @@ def _ci_validate_diffusers_model(model_path: str) -> tuple[bool, bool]:
""" """
if not is_in_ci(): if not is_in_ci():
return True, False return True, False
is_valid, missing_files, checked_subdirs = _check_index_files_for_missing_shards( is_valid, missing_files, checked_subdirs = _check_index_files_for_missing_shards(
model_path model_path
) )
@@ -295,24 +294,23 @@ def load_dict(file_path):
def get_diffusers_component_config( def get_diffusers_component_config(
model_path: str, component_path: str,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Gets a configuration of a submodule for the given diffusers model.""" """Gets a configuration of a submodule for the given diffusers model."""
# Download from HuggingFace Hub if path doesn't exist locally # Download from HuggingFace Hub if path doesn't exist locally
if not os.path.exists(model_path): if not os.path.exists(component_path):
model_path = maybe_download_model(model_path) component_path = maybe_download_model(component_path)
# tokenizer # tokenizer
config_names = ["generation_config.json"] config_names = ["generation_config.json"]
# By default, we load config.json, but scheduler_config.json for scheduler # By default, we load config.json, but scheduler_config.json for scheduler
if "scheduler" in model_path: if "scheduler" in component_path:
config_names.append("scheduler_config.json") config_names.append("scheduler_config.json")
else: else:
config_names.append("config.json") config_names.append("config.json")
config_file_paths = [ config_file_paths = [
os.path.join(model_path, config_name) for config_name in config_names os.path.join(component_path, config_name) for config_name in config_names
] ]
combined_config = reduce( combined_config = reduce(
@@ -544,6 +542,7 @@ def maybe_download_model(
download: bool = True, download: bool = True,
is_lora: bool = False, is_lora: bool = False,
allow_patterns: list[str] | None = None, allow_patterns: list[str] | None = None,
force_diffusers_model: bool = False,
) -> str: ) -> str:
""" """
Check if the model path is a Hugging Face Hub model ID and download it if needed. Check if the model path is a Hugging Face Hub model ID and download it if needed.
@@ -553,13 +552,13 @@ def maybe_download_model(
local_dir: Local directory to save the model local_dir: Local directory to save the model
download: Whether to download the model from Hugging Face Hub download: Whether to download the model from Hugging Face Hub
is_lora: If True, skip model completeness verification (LoRA models don't have transformer/vae directories) is_lora: If True, skip model completeness verification (LoRA models don't have transformer/vae directories)
force_diffusers_model: If True, apply diffusers model check. Otherwise it should be a component model
Returns: Returns:
Local path to the model Local path to the model
""" """
def _verify_model_complete(path: str) -> bool: def _verify_diffusers_model_complete(path: str) -> bool:
"""Check if model directory has required subdirectories.""" """Check if model directory (of a diffusers model, not a component) has required subdirectories."""
config_path = os.path.join(path, "model_index.json") config_path = os.path.join(path, "model_index.json")
if not os.path.exists(config_path): if not os.path.exists(config_path):
return False return False
@@ -592,7 +591,10 @@ def maybe_download_model(
# 1. Local path check: if path exists locally, verify it's complete (skip for LoRA) # 1. Local path check: if path exists locally, verify it's complete (skip for LoRA)
if os.path.exists(model_name_or_path): if os.path.exists(model_name_or_path):
if is_lora or _verify_model_complete(model_name_or_path): # TODO: lots of duplication here
if not force_diffusers_model:
return model_name_or_path
elif is_lora or _verify_diffusers_model_complete(model_name_or_path):
# CI validation: check all subdirectories for missing shards # CI validation: check all subdirectories for missing shards
if not is_lora: if not is_lora:
is_valid, cleanup_performed = _ci_validate_diffusers_model( is_valid, cleanup_performed = _ci_validate_diffusers_model(
@@ -640,7 +642,9 @@ def maybe_download_model(
local_files_only=True, local_files_only=True,
max_workers=8, max_workers=8,
) )
if is_lora or _verify_model_complete(local_path): if not force_diffusers_model:
return str(local_path)
elif is_lora or _verify_diffusers_model_complete(local_path):
# CI validation: check all subdirectories for missing shards # CI validation: check all subdirectories for missing shards
if not is_lora: if not is_lora:
is_valid, cleanup_performed = _ci_validate_diffusers_model(local_path) is_valid, cleanup_performed = _ci_validate_diffusers_model(local_path)
@@ -710,8 +714,10 @@ def maybe_download_model(
max_workers=8, max_workers=8,
) )
if not force_diffusers_model:
return str(local_path)
# Verify downloaded model is complete (skip for LoRA) # Verify downloaded model is complete (skip for LoRA)
if not is_lora and not _verify_model_complete(local_path): elif not is_lora and not _verify_diffusers_model_complete(local_path):
logger.warning( logger.warning(
"Downloaded model at %s is incomplete, retrying with force_download=True", "Downloaded model at %s is incomplete, retrying with force_download=True",
local_path, local_path,
@@ -724,7 +730,7 @@ def maybe_download_model(
max_workers=8, max_workers=8,
force_download=True, force_download=True,
) )
if not _verify_model_complete(local_path): if not _verify_diffusers_model_complete(local_path):
raise ValueError( raise ValueError(
f"Downloaded model at {local_path} is still incomplete after forced re-download. " f"Downloaded model at {local_path} is still incomplete after forced re-download. "
"The model repository may be missing required components (model_index.json, transformer/, or vae/)." "The model repository may be missing required components (model_index.json, transformer/, or vae/)."
@@ -243,20 +243,24 @@ Consider updating perf_baselines.json with the snippets below:
summary = validator.collect_metrics(perf_record) summary = validator.collect_metrics(perf_record)
if is_baseline_generation_mode or missing_scenario: if case.run_perf_check:
self._dump_baseline_for_testcase(case, summary, missing_scenario) if is_baseline_generation_mode or missing_scenario:
if missing_scenario: self._dump_baseline_for_testcase(case, summary, missing_scenario)
pytest.fail(f"Testcase '{case.id}' not found in perf_baselines.json") if missing_scenario:
return pytest.fail(
f"Testcase '{case.id}' not found in perf_baselines.json"
)
return
self._check_for_improvement(case, summary, scenario) self._check_for_improvement(case, summary, scenario)
try: # only run performance validation if run_perf_check is True
validator.validate(perf_record, case.sampling_params.num_frames) try:
except AssertionError as e: validator.validate(perf_record, case.sampling_params.num_frames)
logger.error(f"Performance validation failed for {case.id}:\n{e}") except AssertionError as e:
self._dump_baseline_for_testcase(case, summary, missing_scenario) logger.error(f"Performance validation failed for {case.id}:\n{e}")
raise self._dump_baseline_for_testcase(case, summary, missing_scenario)
raise
result = { result = {
"test_name": case.id, "test_name": case.id,
@@ -226,6 +226,7 @@ class DiffusionTestCase:
id: str # pytest test id and scenario name id: str # pytest test id and scenario name
server_args: DiffusionServerArgs server_args: DiffusionServerArgs
sampling_params: DiffusionSamplingParams sampling_params: DiffusionSamplingParams
run_perf_check: bool = True
def sample_step_indices( def sample_step_indices(
@@ -507,6 +508,16 @@ ONE_GPU_CASES_B: list[DiffusionTestCase] = [
), ),
TI2I_sampling_params, TI2I_sampling_params,
), ),
DiffusionTestCase(
"flux_2_t2i_customized_vae_path",
DiffusionServerArgs(
model_path="black-forest-labs/FLUX.2-dev",
modality="image",
extras=["--vae-path=fal/FLUX.2-Tiny-AutoEncoder"],
),
T2I_sampling_params,
run_perf_check=False,
),
DiffusionTestCase( DiffusionTestCase(
"fast_hunyuan_video", "fast_hunyuan_video",
DiffusionServerArgs( DiffusionServerArgs(