mirror of
https://github.com/NVIDIA/Model-Optimizer.git
synced 2026-10-20 05:59:32 +08:00
## What does this PR do? **Type of change:** new feature **Overview:** Support Wan2.2 t2v diffusers quantization 1. fix torch2.9 support 2. add Wan2.2 t2v diffusers pipeline quantization Main difference of the Wan2.2 pipeline comparing to exisiting pipelines is that there are 2 backbone models for denoising. For the quantization therefore we need to quantize both of them. However, it turns out our base library does not well support quantization of multiple models in the same time. Therefore, the change here just stick to quantize a single model each time, and then run the quantization multiple times. So, we need to allow users to pick which backbone to quantize, therefore adding a new argment for it 3. add a workaround for the exporting ONNX issue when we upgrade diffusers to >= 0.35.0. The issue lies is the exporting of the torch.nn.RMSNorm. Some pipelines in the diffusers > 0.35.0 use the torch version RMSNorm while before that they use the diffusers' own version of RMSNorm. It turns out they are directly replacable so the workaround is to simply replace the torch RMSNorm usages with diffusers RMSNorm. But we need to fix it properly soon by porting our ONNX export to be based on torch dynamo instead of torchscript. Issue reported from external user: https://github.com/NVIDIA/TensorRT-Model-Optimizer/issues/262 4. allow use of a prompts file, which is simply a text file with a list of prompts, one prompt each line 5. allow each component of a pipeline to have different dtype accuracy. added a new list stype command line arg --component-dtype for this. example: --component-dtype vae:Float 6. print the summary of the quantized model so users can capture issues from log ## Usage python quantize.py \ --model wan2.2-t2v-14b \ --format fp8 \ --batch-size 4 \ --calib-size 64 \ --n-steps 20 \ --backbone transformer \ --model-dtype BFloat16 \ --component-dtype vae:Float \ --trt-high-precision-dtype BFloat16 \ --quantized-torch-ckpt-save-path ./wan_transformer.pt \ --onnx-dir wan-transformer-onnx \ --prompts-file wan-prompts.txt ## Testing Tested SDXL_BASE, LTX_VIDEO_DEV, WAN22_T2V ## Before your PR is "*Ready for review*" <!-- If you haven't finished some of the above items you can still open `Draft` PR. --> - **Make sure you read and follow [Contributor guidelines](https://github.com/NVIDIA/TensorRT-Model-Optimizer/blob/main/CONTRIBUTING.md)** and your commits are signed. - **Is this change backward compatible?**: Yes/No <!--- If No, explain why. --> - **Did you write any new necessary tests?**: Yes/No - **Did you add or update any necessary documentation?**: Yes/No - **Did you update [Changelog](https://github.com/NVIDIA/TensorRT-Model-Optimizer/blob/main/CHANGELOG.rst)?**: Yes/No <!--- Only for new features, API changes, critical bug fixes or bw breaking changes. --> ## Additional Information https://github.com/NVIDIA/TensorRT-Model-Optimizer/issues/262 --------- Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
136 lines
5.0 KiB
Python
136 lines
5.0 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 os
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import torch
|
|
import torch.nn.functional as F
|
|
from datasets import load_dataset
|
|
from diffusers.models.attention_processor import Attention, AttnProcessor
|
|
from diffusers.models.lora import LoRACompatibleConv, LoRACompatibleLinear
|
|
from diffusers.utils import load_image
|
|
|
|
import modelopt.torch.quantization as mtq
|
|
|
|
USE_PEFT = True
|
|
try:
|
|
from peft.tuners.lora.layer import Conv2d as PEFTLoRAConv2d
|
|
from peft.tuners.lora.layer import Linear as PEFTLoRALinear
|
|
except ModuleNotFoundError:
|
|
USE_PEFT = False
|
|
|
|
|
|
# Model-specific filter functions for quantization
|
|
def filter_func_default(name: str) -> bool:
|
|
"""Default filter function for general models."""
|
|
pattern = re.compile(
|
|
r".*(time_emb_proj|time_embedding|conv_in|conv_out|conv_shortcut|add_embedding|pos_embed|time_text_embed|context_embedder|norm_out|x_embedder).*"
|
|
)
|
|
return pattern.match(name) is not None
|
|
|
|
|
|
def check_conv_and_mha(backbone, if_fp4, quantize_mha):
|
|
for _, module in backbone.named_modules():
|
|
if isinstance(module, (torch.nn.Conv1d, torch.nn.Conv2d, torch.nn.Conv3d)) and if_fp4:
|
|
module.weight_quantizer.disable()
|
|
module.input_quantizer.disable()
|
|
elif isinstance(module, Attention):
|
|
if not quantize_mha:
|
|
continue
|
|
head_size = int(module.inner_dim / module.heads)
|
|
module.q_bmm_quantizer.disable()
|
|
module.k_bmm_quantizer.disable()
|
|
module.v_bmm_quantizer.disable()
|
|
module.softmax_quantizer.disable()
|
|
module.bmm2_output_quantizer.disable()
|
|
if head_size % 16 != 0:
|
|
setattr(module, "_disable_fp8_mha", True)
|
|
else:
|
|
setattr(module, "_disable_fp8_mha", False)
|
|
|
|
|
|
def filter_func_ltx_video(name: str) -> bool:
|
|
"""Filter function specifically for LTX-Video models."""
|
|
pattern = re.compile(r".*(proj_in|time_embed|caption_projection|proj_out).*")
|
|
return pattern.match(name) is not None
|
|
|
|
|
|
def filter_func_wan_video(name: str) -> bool:
|
|
"""Filter function specifically for LTX-Video models."""
|
|
pattern = re.compile(r".*(patch_embedding|condition_embedder).*")
|
|
return pattern.match(name) is not None
|
|
|
|
|
|
def load_calib_prompts(
|
|
batch_size,
|
|
calib_data_path: str | Path = "Gustavosta/Stable-Diffusion-Prompts",
|
|
split="train",
|
|
column="Prompt",
|
|
) -> list[list[str]]:
|
|
prompt_list: list[str] = []
|
|
if isinstance(calib_data_path, Path):
|
|
with open(calib_data_path) as f:
|
|
prompt_list = f.readlines()
|
|
else:
|
|
dataset = load_dataset(calib_data_path)
|
|
prompt_list = list(dataset[split][column])
|
|
return [prompt_list[i : i + batch_size] for i in range(0, len(prompt_list), batch_size)]
|
|
|
|
|
|
def load_calib_images(folder_path):
|
|
images = []
|
|
for filename in os.listdir(folder_path):
|
|
img_path = os.path.join(folder_path, filename)
|
|
if os.path.isfile(img_path):
|
|
image = load_image(img_path)
|
|
if image is not None:
|
|
images.append(image)
|
|
return images
|
|
|
|
|
|
def set_fmha(unet):
|
|
for name, module in unet.named_modules():
|
|
if isinstance(module, Attention):
|
|
module.set_processor(AttnProcessor())
|
|
|
|
|
|
def check_lora(unet):
|
|
for name, module in unet.named_modules():
|
|
if isinstance(module, (LoRACompatibleConv, LoRACompatibleLinear)):
|
|
assert module.lora_layer is None, (
|
|
f"To quantize {name}, LoRA layer should be fused/merged. Please"
|
|
" fuse the LoRA layer before quantization."
|
|
)
|
|
elif USE_PEFT and isinstance(module, (PEFTLoRAConv2d, PEFTLoRALinear)):
|
|
assert module.merged, (
|
|
f"To quantize {name}, LoRA layer should be fused/merged. Please"
|
|
" fuse the LoRA layer before quantization."
|
|
)
|
|
|
|
|
|
def fp8_mha_disable(backbone, quantized_mha_output: bool = True):
|
|
def mha_filter_func(name):
|
|
pattern = re.compile(
|
|
r".*(q_bmm_quantizer|k_bmm_quantizer|v_bmm_quantizer|softmax_quantizer).*"
|
|
if quantized_mha_output
|
|
else r".*(q_bmm_quantizer|k_bmm_quantizer|v_bmm_quantizer|softmax_quantizer|bmm2_output_quantizer).*"
|
|
)
|
|
return pattern.match(name) is not None
|
|
|
|
if hasattr(F, "scaled_dot_product_attention"):
|
|
mtq.disable_quantizer(backbone, mha_filter_func)
|