mirror of
https://github.com/NVIDIA/Model-Optimizer.git
synced 2026-10-20 05:59:32 +08:00
337 lines
11 KiB
Python
337 lines
11 KiB
Python
# SPDX-FileCopyrightText: Copyright (c) 2024 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.
|
|
|
|
import argparse
|
|
from typing import Any
|
|
|
|
import torch
|
|
from config import (
|
|
FP8_DEFAULT_CONFIG,
|
|
NVFP4_DEFAULT_CONFIG,
|
|
NVFP4_FP8_MHA_FLUX_CONFIG,
|
|
get_int8_config,
|
|
set_quant_config_attr,
|
|
)
|
|
from diffusers import DiffusionPipeline, FluxPipeline, StableDiffusion3Pipeline
|
|
from onnx_utils.export import generate_fp8_scales, modelopt_export_sd
|
|
from utils import check_lora, filter_func, fp8_mha_disable, load_calib_prompts, quantize_lvl
|
|
|
|
import modelopt.torch.opt as mto
|
|
import modelopt.torch.quantization as mtq
|
|
|
|
MODEL_ID: dict[str, str] = {
|
|
"sdxl-1.0": "stabilityai/stable-diffusion-xl-base-1.0",
|
|
"sdxl-turbo": "stabilityai/sdxl-turbo",
|
|
"sd3-medium": "stabilityai/stable-diffusion-3-medium-diffusers",
|
|
"flux-dev": "black-forest-labs/FLUX.1-dev",
|
|
"flux-schnell": "black-forest-labs/FLUX.1-schnell",
|
|
}
|
|
|
|
# Additional model-specific arguments for calibration
|
|
ADDITIONAL_ARGS: dict[str, dict[str, Any]] = {
|
|
"flux-dev": {
|
|
"height": 1024,
|
|
"width": 1024,
|
|
"guidance_scale": 3.5,
|
|
"max_sequence_length": 512,
|
|
},
|
|
"flux-schnell": {
|
|
"height": 1024,
|
|
"width": 1024,
|
|
"guidance_scale": 3.5,
|
|
"max_sequence_length": 512,
|
|
},
|
|
}
|
|
|
|
|
|
def create_pipeline(
|
|
model_name: str,
|
|
model_dtype: str,
|
|
override_model_path: str | None = None,
|
|
) -> DiffusionPipeline:
|
|
"""Create and return an appropriate pipeline based on the model_name provided."""
|
|
# Convert string to torch.dtype
|
|
if model_dtype == "Half":
|
|
torch_dtype = torch.float16
|
|
elif model_dtype == "BFloat16":
|
|
torch_dtype = torch.bfloat16
|
|
elif model_dtype == "Float":
|
|
torch_dtype = torch.float32
|
|
else:
|
|
raise ValueError(f"Unknown model dtype {model_dtype}.")
|
|
|
|
model_path = override_model_path if override_model_path else MODEL_ID[model_name]
|
|
|
|
if model_name == "sd3-medium":
|
|
return StableDiffusion3Pipeline.from_pretrained(model_path, torch_dtype=torch_dtype)
|
|
elif model_name in ["flux-dev", "flux-schnell"]:
|
|
return FluxPipeline.from_pretrained(model_path, torch_dtype=torch_dtype)
|
|
else:
|
|
# Example for stable-diffusion-based models
|
|
return DiffusionPipeline.from_pretrained(
|
|
model_path,
|
|
torch_dtype=torch_dtype,
|
|
use_safetensors=True,
|
|
)
|
|
|
|
|
|
def do_calibrate(
|
|
pipe: DiffusionPipeline,
|
|
calibration_prompts: list[str],
|
|
model_id: str,
|
|
calib_size: int,
|
|
n_steps: int,
|
|
) -> None:
|
|
"""
|
|
Run calibration steps on the pipeline using the given prompts.
|
|
"""
|
|
for i, prompts in enumerate(calibration_prompts):
|
|
if i >= calib_size:
|
|
break
|
|
common_args = {
|
|
"prompt": prompts,
|
|
"num_inference_steps": n_steps,
|
|
}
|
|
extra_args = ADDITIONAL_ARGS.get(model_id, {})
|
|
# If needed, add negative_prompt or other custom logic here
|
|
pipe(**common_args, **extra_args).images
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
"""
|
|
Parse command-line arguments.
|
|
"""
|
|
parser = argparse.ArgumentParser(description="Quantization and Calibration Script")
|
|
|
|
# Model hyperparameters
|
|
parser.add_argument(
|
|
"--quantized-torch-ckpt-save-path",
|
|
default=None,
|
|
help="File path for the quantized Torch checkpoint (should end with .pt).",
|
|
)
|
|
parser.add_argument(
|
|
"--model",
|
|
type=str,
|
|
default="flux-dev",
|
|
choices=["sdxl-1.0", "sdxl-turbo", "sd3-medium", "flux-dev", "flux-schnell"],
|
|
help="Which model to load and quantize.",
|
|
)
|
|
parser.add_argument(
|
|
"--cpu-offloading",
|
|
action="store_true",
|
|
help="CPU offloading calibration during inference for GPUs with limited VRAM.",
|
|
)
|
|
parser.add_argument(
|
|
"--override-model-path",
|
|
type=str,
|
|
default=None,
|
|
help="Path to the model if not using default paths in MODEL_ID mapping.",
|
|
)
|
|
parser.add_argument(
|
|
"--restore-from",
|
|
type=str,
|
|
default=None,
|
|
help="Path to a previously quantized checkpoint to restore from.",
|
|
)
|
|
parser.add_argument(
|
|
"--n-steps",
|
|
type=int,
|
|
default=30,
|
|
help="Number of denoising steps.",
|
|
)
|
|
parser.add_argument(
|
|
"--model-dtype",
|
|
type=str,
|
|
default="Half",
|
|
choices=["Half", "BFloat16", "Float"],
|
|
help="Precision used to load the model.",
|
|
)
|
|
parser.add_argument(
|
|
"--trt-high-precision-dtype",
|
|
type=str,
|
|
default="Half",
|
|
choices=["Half", "BFloat16", "Float"],
|
|
help="Precision used in TensorRT high-precision layers.",
|
|
)
|
|
parser.add_argument(
|
|
"--quant-algo",
|
|
type=str,
|
|
default="max",
|
|
choices=["max", "svdquant", "smoothquant"],
|
|
help="Quantization algo",
|
|
)
|
|
|
|
# Calibration and quantization parameters
|
|
parser.add_argument(
|
|
"--format",
|
|
type=str,
|
|
default="int8",
|
|
choices=["int8", "fp8", "fp4"],
|
|
help="Quantization format.",
|
|
)
|
|
parser.add_argument(
|
|
"--percentile",
|
|
type=float,
|
|
default=1.0,
|
|
help="Percentile used for calibration (relevant in some calibrators).",
|
|
)
|
|
parser.add_argument(
|
|
"--collect-method",
|
|
type=str,
|
|
default="default",
|
|
choices=["global_min", "min-max", "min-mean", "mean-max", "default"],
|
|
help="How to collect amax values during calibration.",
|
|
)
|
|
parser.add_argument("--batch-size", type=int, default=2, help="Batch size for calibration.")
|
|
parser.add_argument("--calib-size", type=int, default=128, help="Number of calibration steps.")
|
|
parser.add_argument(
|
|
"--alpha", type=float, default=1.0, help="SmoothQuant alpha hyperparameter."
|
|
)
|
|
parser.add_argument("--lowrank", type=int, default=32, help="SVDQuant lowrank hyperparameter.")
|
|
parser.add_argument(
|
|
"--quant-level",
|
|
type=float,
|
|
default=3.0,
|
|
choices=[1.0, 2.0, 2.5, 3.0, 4.0],
|
|
help="Quantization level, 1: CNN, 2: CNN+FFN, 2.5: CNN+FFN+QKV, 3: CNN+FC, 4: CNN+FC+fMHA",
|
|
)
|
|
parser.add_argument(
|
|
"--onnx-dir",
|
|
type=str,
|
|
default=None,
|
|
help="Directory to export ONNX models. If None, no export is done.",
|
|
)
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
"""
|
|
Main entrypoint for quantizing and calibrating the pipeline, then optionally exporting.
|
|
"""
|
|
args = parse_args()
|
|
|
|
if args.quant_level == 4.0 and args.format == "int8":
|
|
raise ValueError("Level 4 quantization is only supported for fp8, not int8.")
|
|
|
|
# Create pipeline
|
|
pipe = create_pipeline(args.model, args.model_dtype, args.override_model_path)
|
|
pipe.to("cuda") if not args.cpu_offloading else pipe.enable_model_cpu_offload()
|
|
|
|
# Choose correct backbone (unet or transformer) for the loaded pipeline
|
|
backbone = (
|
|
pipe.transformer if args.model in ["sd3-medium", "flux-dev", "flux-schnell"] else pipe.unet
|
|
)
|
|
|
|
# Adjust calibration steps to be number of batches
|
|
args.calib_size = args.calib_size // args.batch_size
|
|
|
|
# If restore_from is not specified, run calibration and quantize
|
|
if not args.restore_from:
|
|
calibration_prompts = load_calib_prompts(
|
|
args.batch_size,
|
|
"./calib/calib_prompts.txt",
|
|
)
|
|
|
|
# Build quant_config based on format
|
|
if args.format == "int8":
|
|
if args.collect_method == "default":
|
|
raise ValueError(
|
|
"You must specify an explicit --collect-method (e.g., 'global_min') for int8."
|
|
)
|
|
if args.quant_algo != "smoothquant":
|
|
raise ValueError(
|
|
"INT8 quantization only works well when combined with SmoothQuant;"
|
|
"otherwise, it will produce very poor quality results."
|
|
)
|
|
quant_config = get_int8_config(
|
|
backbone,
|
|
args.quant_level,
|
|
args.percentile,
|
|
args.n_steps,
|
|
collect_method=args.collect_method,
|
|
)
|
|
elif args.format == "fp8":
|
|
if args.collect_method != "default":
|
|
raise NotImplementedError("Only 'default' collect method is implemented for fp8.")
|
|
quant_config = FP8_DEFAULT_CONFIG
|
|
elif args.format == "fp4":
|
|
quant_config = (
|
|
NVFP4_FP8_MHA_FLUX_CONFIG if args.model.startswith("flux") else NVFP4_DEFAULT_CONFIG
|
|
)
|
|
else:
|
|
raise NotImplementedError(f"Unknown format {args.format}.")
|
|
|
|
# Adjust the quant config
|
|
set_quant_config_attr(
|
|
quant_config,
|
|
args.trt_high_precision_dtype,
|
|
args.quant_algo,
|
|
alpha=args.alpha,
|
|
lowrank=args.lowrank,
|
|
)
|
|
|
|
def forward_loop(mod):
|
|
# Switch the pipeline's backbone, run calibration
|
|
if args.model not in ["sd3-medium", "flux-dev", "flux-schnell"]:
|
|
pipe.unet = mod
|
|
else:
|
|
pipe.transformer = mod
|
|
do_calibrate(
|
|
pipe=pipe,
|
|
calibration_prompts=calibration_prompts,
|
|
model_id=args.model,
|
|
calib_size=args.calib_size,
|
|
n_steps=args.n_steps,
|
|
)
|
|
|
|
# Fuse LoRA layers, then quantize
|
|
check_lora(backbone)
|
|
mtq.quantize(backbone, quant_config, forward_loop)
|
|
|
|
# Save the quantized checkpoint if path is provided
|
|
if args.quantized_torch_ckpt_save_path:
|
|
mto.save(backbone, args.quantized_torch_ckpt_save_path)
|
|
else:
|
|
# Restore the previously quantized model
|
|
mto.restore(backbone, args.restore_from)
|
|
|
|
# Additional quantization adjustments by level
|
|
quantize_lvl(args.model, backbone, args.quant_level)
|
|
|
|
# Disable some quantizers
|
|
mtq.disable_quantizer(backbone, filter_func)
|
|
|
|
# Optional ONNX export
|
|
if args.onnx_dir:
|
|
if args.format == "fp8" and args.model != "flux-dev":
|
|
generate_fp8_scales(backbone)
|
|
|
|
pipe.to("cpu")
|
|
torch.cuda.empty_cache()
|
|
backbone.to("cuda")
|
|
if args.quant_level == 4.0:
|
|
fp8_mha_disable(backbone, quantized_mha_output=False)
|
|
|
|
backbone.eval()
|
|
with torch.no_grad():
|
|
modelopt_export_sd(backbone, args.onnx_dir, args.model, args.format)
|
|
|
|
print("Done!\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|