Files
wehub-resource-sync c8a779b1bb
Docker Image CI / build-ubuntu2004 (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:55 +08:00

466 lines
20 KiB
Python

#
# SPDX-FileCopyrightText: Copyright (c) 1993-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from __future__ import annotations
import argparse
from typing import Any, Dict, Tuple
import torch
# Define valid optimization levels for TensorRT engine build
VALID_OPTIMIZATION_LEVELS = list(range(6))
def parse_key_value_pairs(string: str) -> Dict[str, str]:
"""Parse a string of comma-separated key-value pairs into a dictionary.
Args:
string (str): A string of comma-separated key-value pairs.
Returns:
Dict[str, str]: Parsed dictionary of key-value pairs.
Example:
>>> parse_key_value_pairs("key1:value1,key2:value2")
{"key1": "value1", "key2": "value2"}
"""
parsed = {}
for key_value_pair in string.split(","):
if not key_value_pair:
continue
key_value_pair = key_value_pair.split(":")
if len(key_value_pair) != 2:
raise argparse.ArgumentTypeError(f"Invalid key-value pair: {key_value_pair}. Must have length 2.")
key, value = key_value_pair
parsed[key] = value
return parsed
def add_arguments(parser):
# Stable Diffusion configuration
parser.add_argument(
"--version",
type=str,
default="1.4",
choices=(
"1.4",
"dreamshaper-7",
"xl-1.0",
"xl-turbo",
"svd-xt-1.1",
"sd3",
"3.5-medium",
"3.5-large",
"cascade",
"flux.1-dev",
"flux.1-schnell",
"flux.1-dev-canny",
"flux.1-dev-depth",
"flux.1-kontext-dev",
"cosmos-predict2-2b-text2image",
"cosmos-predict2-14b-text2image",
"cosmos-predict2-2b-video2world",
"cosmos-predict2-14b-video2world",
),
help="Version of Stable Diffusion",
)
parser.add_argument("prompt", nargs="*", help="Text prompt(s) to guide image generation")
parser.add_argument(
"--negative-prompt", nargs="*", default=[""], help="The negative prompt(s) to guide the image generation."
)
parser.add_argument("--batch-size", type=int, default=1, choices=[1, 2, 4], help="Batch size (repeat prompt)")
parser.add_argument(
"--batch-count", type=int, default=1, help="Number of images to generate in sequence, one at a time."
)
parser.add_argument("--height", type=int, default=512, help="Height of image to generate (must be multiple of 8)")
parser.add_argument("--width", type=int, default=512, help="Height of image to generate (must be multiple of 8)")
parser.add_argument("--denoising-steps", type=int, default=30, help="Number of denoising steps")
parser.add_argument(
"--scheduler",
type=str,
default=None,
choices=("DDIM", "DDPM", "EulerA", "Euler", "LCM", "LMSD", "PNDM", "UniPC", "DDPMWuerstchen", "FlowMatchEuler"),
help="Scheduler for diffusion process",
)
parser.add_argument(
"--guidance-scale",
type=float,
default=7.5,
help="Value of classifier-free guidance scale (must be greater than 1)",
)
parser.add_argument(
"--lora-scale",
type=float,
default=1.0,
help="Controls how much to influence the outputs with the LoRA parameters. (must between 0 and 1)",
)
parser.add_argument(
"--lora-weight",
type=float,
nargs="+",
default=None,
help="The LoRA adapter(s) weights to use with the UNet. (must between 0 and 1)",
)
parser.add_argument(
"--lora-path",
type=str,
nargs="+",
default=None,
help="Path to LoRA adaptor. Ex: 'latent-consistency/lcm-lora-sdv1-5'",
)
parser.add_argument("--bf16", action="store_true", help="Run pipeline in BFloat16 precision")
# ONNX export
parser.add_argument(
"--onnx-opset",
type=int,
default=19,
choices=range(7, 24),
help="Select ONNX opset version to target for exported models",
)
parser.add_argument("--onnx-dir", default="onnx", help="Output directory for ONNX export")
parser.add_argument(
"--custom-onnx-paths",
type=parse_key_value_pairs,
help=(
"[FLUX, Stable Diffusion 3.5-large, Cosmos only] Custom override paths to pre-exported ONNX model files. These ONNX models are directly used to "
"build TRT engines without further optimization on the ONNX graphs. Paths should be a comma-separated list "
"of <model_name>:<path> pairs. For example: "
"--custom-onnx-paths=transformer:/path/to/transformer.onnx,vae:/path/to/vae.onnx. Call "
"<PipelineClass>.get_model_names(...) for the list of supported model names."
),
)
parser.add_argument(
"--onnx-export-only",
action="store_true",
help="If set, only performs the export of models to ONNX, skipping engine build and inference.",
)
parser.add_argument(
"--download-onnx-models",
action="store_true",
help=("[FLUX and Stable Diffusion 3.5-large only] Download pre-exported ONNX models"),
)
# Framework model ckpt
parser.add_argument("--framework-model-dir", default="pytorch_model", help="Directory for HF saved models")
# TensorRT engine build
parser.add_argument("--engine-dir", default="engine", help="Output directory for TensorRT engines")
parser.add_argument(
"--custom-engine-paths",
type=parse_key_value_pairs,
help=(
"[FLUX only] Custom override paths to pre-built engine files. Paths should be a comma-separated list of "
"<model_name>:<path> pairs. For example: "
"--custom-onnx-paths=transformer:/path/to/transformer.plan,vae:/path/to/vae.plan. Call "
"<PipelineClass>.get_model_names(...) for the list of supported model names."
),
)
parser.add_argument(
"--optimization-level",
type=int,
default=None,
help=f"Set the builder optimization level to build the engine with. A higher level allows TensorRT to spend more building time for more optimization options. Must be one of {VALID_OPTIMIZATION_LEVELS}.",
)
parser.add_argument(
"--build-static-batch", action="store_true", help="Build TensorRT engines with fixed batch size."
)
parser.add_argument(
"--build-dynamic-shape", action="store_true", help="Build TensorRT engines with dynamic image shapes."
)
parser.add_argument(
"--build-enable-refit", action="store_true", help="Enable Refit option in TensorRT engines during build."
)
parser.add_argument(
"--build-all-tactics", action="store_true", help="Build TensorRT engines using all tactic sources."
)
parser.add_argument(
"--timing-cache", default=None, type=str, help="Path to the precached timing measurements to accelerate build."
)
parser.add_argument("--ws", action="store_true", help="Build TensorRT engines with weight streaming enabled.")
# Quantization configuration.
parser.add_argument("--int8", action="store_true", help="Apply int8 quantization.")
parser.add_argument("--fp8", action="store_true", help="Apply fp8 quantization.")
parser.add_argument("--fp4", action="store_true", help="Apply fp4 quantization.")
parser.add_argument(
"--quantization-level",
type=float,
default=0.0,
choices=[0.0, 1.0, 2.0, 2.5, 3.0, 4.0],
help="int8/fp8 quantization level, 1: CNN, 2: CNN + FFN, 2.5: CNN + FFN + QKV, 3: CNN + Almost all Linear (Including FFN, QKV, Proj and others), 4: CNN + Almost all Linear + fMHA, 0: Default to 2.5 for int8 and 4.0 for fp8.",
)
parser.add_argument(
"--quantization-percentile",
type=float,
default=1.0,
help="Control quantization scaling factors (amax) collecting range, where the minimum amax in range(n_steps * percentile) will be collected. Recommendation: 1.0.",
)
parser.add_argument(
"--quantization-alpha",
type=float,
default=0.8,
help="The alpha parameter for SmoothQuant quantization used for linear layers. Recommendation: 0.8 for SDXL.",
)
parser.add_argument(
"--calibration-size",
type=int,
default=32,
help="The number of steps to use for calibrating the model for quantization. Recommendation: 32, 64, 128 for SDXL",
)
# Inference
parser.add_argument(
"--num-warmup-runs", type=int, default=5, help="Number of warmup runs before benchmarking performance"
)
parser.add_argument("--use-cuda-graph", action="store_true", help="Enable cuda graph")
parser.add_argument("--nvtx-profile", action="store_true", help="Enable NVTX markers for performance profiling")
parser.add_argument(
"--torch-inference",
default="",
help="Run inference with PyTorch (using specified compilation mode) instead of TensorRT.",
)
parser.add_argument(
"--torch-fallback",
default=None,
type=str,
help="[FLUX, SD3.5, and Wan] Comma separated list of models to be inferenced using PyTorch instead of TRT. For example --torch-fallback text_encoder,transformer,transformer_2. If --torch-inference set, this parameter will be ignored.",
)
parser.add_argument(
"--low-vram",
action="store_true",
help="[FLUX, SD3.5, and Wan] Optimize for low VRAM usage, possibly at the expense of inference performance. Disabled by default.",
)
parser.add_argument("--seed", type=int, default=None, help="Seed for random generator to get consistent results")
parser.add_argument("--output-dir", default="output", help="Output directory for logs and image artifacts")
parser.add_argument("--hf-token", type=str, help="HuggingFace API access token for downloading model checkpoints")
parser.add_argument("-v", "--verbose", action="store_true", help="Show verbose output")
return parser
def process_pipeline_args(args: argparse.Namespace) -> Tuple[Dict[str, Any], Dict[str, Any], Tuple]:
"""Validate parsed arguments and process argument values.
Some argument values are resolved or overwritten during processing.
Args:
args (argparse.Namespace): Parsed argument. This is modified in-place.
Returns:
Dict[str, Any]: Keyword arguments for initializing a pipeline. This is only used in legacy pipelines that do not
have factory methods `FromArgs` that construct the pipeline directly from the parsed argument.
Dict[str, Any]: Keyword arguments for calling the `.load_engine` method of the pipeline.
Tuple: Arguments for calling the `.run` method of the pipeline.
"""
# GPU device info
device_info = torch.cuda.get_device_properties(0)
sm_version = device_info.major * 10 + device_info.minor
is_flux = args.version.startswith("flux")
is_sd35 = args.version.startswith("3.5")
is_wan = args.version.startswith("wan")
is_cosmos = args.version.startswith("cosmos")
if args.height % 8 != 0 or args.width % 8 != 0:
raise ValueError(
f"Image height and width have to be divisible by 8 but specified as: {args.image_height} and {args.width}."
)
# Handle batch size
max_batch_size = 4
if args.batch_size > max_batch_size:
raise ValueError(f"Batch size {args.batch_size} is larger than allowed {max_batch_size}.")
if args.use_cuda_graph and (not args.build_static_batch or args.build_dynamic_shape):
raise ValueError(
"Using CUDA graph requires static dimensions. Enable `--build-static-batch` and do not specify `--build-dynamic-shape`"
)
# TensorRT builder optimization level
if args.optimization_level is None:
# optimization level set to 3 for all Flux pipelines to reduce GPU memory usage
if args.int8 or args.fp8 and not is_flux:
args.optimization_level = 4
else:
args.optimization_level = 3
if args.optimization_level not in VALID_OPTIMIZATION_LEVELS:
raise ValueError(
f"Optimization level {args.optimization_level} not valid. Valid values are: {VALID_OPTIMIZATION_LEVELS}"
)
# Quantized pipeline
# int8 support
if args.int8 and not any(args.version.startswith(prefix) for prefix in ("xl", "1.4")):
raise ValueError("int8 quantization is only supported for SDXL and SD1.4 pipelines.")
# fp8 support validation
if args.fp8:
# Check version compatibility
supported_versions = ("xl", "1.4", "3.5-large")
if not (any(args.version.startswith(prefix) for prefix in supported_versions) or is_flux):
raise ValueError(
"fp8 quantization is only supported for SDXL, SD1.4, SD3.5-large and FLUX pipelines."
)
# Check controlnet compatibility
if getattr(args, "controlnet_type", None) is not None:
if args.version not in ("xl-1.0", "3.5-large"):
raise ValueError("fp8 controlnet quantization is only supported for SDXL and SD3.5-large.")
if args.version == "3.5-large" and args.controlnet_type == "blur":
raise ValueError("Blur controlnet type is not supported for SD3.5.")
# Check for conflicting quantization
if args.int8:
raise ValueError("Cannot apply both int8 and fp8 quantization, please choose only one.")
# Check GPU compute capability
if sm_version < 89:
raise ValueError(
f"Cannot apply FP8 quantization for GPU with compute capability {sm_version / 10.0}. A minimum compute capability of 8.9 is required."
)
# Check SD3.5-large specific requirement
if args.version == "3.5-large" and not args.download_onnx_models:
raise ValueError(
"Native FP8 quantization is not supported for SD3.5-large. Please pass --download-onnx-models."
)
# TensorRT ModelOpt quantization level
if args.quantization_level == 0.0:
def override_quant_level(level: float, dtype_str: str):
args.quantization_level = level
print(f"[W] The default quantization level has been set to {level} for {dtype_str}.")
if args.fp8:
# L4 fp8 fMHA on Hopper not yet enabled.
if sm_version == 90 and is_flux:
override_quant_level(3.0, "FP8")
else:
override_quant_level(3.0 if args.version == "1.4" else 4.0, "FP8")
elif args.int8:
override_quant_level(3.0, "INT8")
if args.version.startswith("flux") and args.quantization_level == 3.0 and args.download_onnx_models:
raise ValueError(
"Transformer ONNX model for Quantization level 3 is not available for download. Please export the quantized Transformer model natively with the removal of --download-onnx-models."
)
if args.fp4:
# FP4 precision is only supported for the Flux pipeline
assert is_flux, "FP4 precision is only supported for the Flux pipeline"
# Handle LoRA
# FLUX canny and depth official LoRAs are not supported because they modify the transformer architecture, conflicting with refit
if args.lora_path and not any(args.version.startswith(prefix) for prefix in ("xl", "flux.1-dev", "flux.1-schnell")):
raise ValueError("LoRA adapter support is only supported for SDXL, FLUX.1-dev and FLUX.1-schnell pipelines")
if args.lora_weight:
for weight in (weight for weight in args.lora_weight if not 0 <= weight <= 1):
raise ValueError(f"LoRA adapter weights must be between 0 and 1, provided {weight}")
if not 0 <= args.lora_scale <= 1:
raise ValueError(f"LoRA scale value must be between 0 and 1, provided {args.lora_scale}")
# Force lora merge when fp8 or int8 is used with LoRA
if args.build_enable_refit and args.lora_path and (args.int8 or args.fp8):
raise ValueError(
"Engine refit should not be enabled for quantized models with LoRA. ModelOpt recommends fusing the LoRA to the model before quantization. \
See https://github.com/NVIDIA/TensorRT-Model-Optimizer/tree/main/examples/diffusers/quantization#lora"
)
# Torch-fallback and Torch-inference
if args.torch_fallback and not args.torch_inference:
assert (
is_flux or is_sd35 or is_wan or is_cosmos
), "PyTorch Fallback is only supported for Flux, Stable Diffusion 3.5, Wan and Cosmos pipelines."
args.torch_fallback = args.torch_fallback.split(",")
if args.torch_fallback and args.torch_inference:
print(
"[W] All models will run in PyTorch when --torch-inference is set. Parameter --torch-fallback will be ignored."
)
args.torch_fallback = None
# low-vram
if args.low_vram:
assert (
is_flux or is_sd35 or is_wan or is_cosmos
), "low-vram mode is only supported for Flux, Stable Diffusion 3.5, Wan and Cosmos pipelines."
# Disable SDXL LCM pipeline
if args.version == "xl-1.0" and args.scheduler == "LCM":
raise ValueError("SDXL pipeline does not support the LCM scheduler currently. Please use a different scheduler.")
# Pack arguments
kwargs_init_pipeline = {
"version": args.version,
"max_batch_size": max_batch_size,
"denoising_steps": args.denoising_steps,
"scheduler": args.scheduler,
"guidance_scale": args.guidance_scale,
"output_dir": args.output_dir,
"hf_token": args.hf_token,
"verbose": args.verbose,
"nvtx_profile": args.nvtx_profile,
"use_cuda_graph": args.use_cuda_graph,
"lora_scale": args.lora_scale,
"lora_weight": args.lora_weight,
"lora_path": args.lora_path,
"framework_model_dir": args.framework_model_dir,
"torch_inference": args.torch_inference,
}
kwargs_load_engine = {
"onnx_opset": args.onnx_opset,
"opt_batch_size": args.batch_size,
"opt_image_height": args.height,
"opt_image_width": args.width,
"optimization_level": args.optimization_level,
"static_batch": args.build_static_batch,
"static_shape": not args.build_dynamic_shape,
"enable_all_tactics": args.build_all_tactics,
"enable_refit": args.build_enable_refit,
"timing_cache": args.timing_cache,
"int8": args.int8,
"fp8": args.fp8,
"fp4": args.fp4,
"quantization_level": args.quantization_level,
"quantization_percentile": args.quantization_percentile,
"quantization_alpha": args.quantization_alpha,
"calibration_size": args.calibration_size,
"onnx_export_only": args.onnx_export_only,
"download_onnx_models": args.download_onnx_models,
}
args_run_demo = (
args.prompt,
args.negative_prompt,
args.height,
args.width,
args.batch_size,
args.batch_count,
args.num_warmup_runs,
args.use_cuda_graph,
)
return kwargs_init_pipeline, kwargs_load_engine, args_run_demo