# # 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 : pairs. For example: " "--custom-onnx-paths=transformer:/path/to/transformer.onnx,vae:/path/to/vae.onnx. Call " ".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 " ": pairs. For example: " "--custom-onnx-paths=transformer:/path/to/transformer.plan,vae:/path/to/vae.plan. Call " ".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