CoolFace
Apppublic

tsi-org/tango

sourceHugging Faceupdated 3y agoView on Hugging Face
0likes
convert_stable_diffusion_checkpoint_to_onnx.py266 linesDownload Raw Back to scripts
1# Copyright 2023 The HuggingFace Team. All rights reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#     http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14 15import argparse16import os17import shutil18from pathlib import Path19 20import onnx21import torch22from packaging import version23from torch.onnx import export24 25from diffusers import OnnxRuntimeModel, OnnxStableDiffusionPipeline, StableDiffusionPipeline26 27 28is_torch_less_than_1_11 = version.parse(version.parse(torch.__version__).base_version) < version.parse("1.11")29 30 31def onnx_export(32    model,33    model_args: tuple,34    output_path: Path,35    ordered_input_names,36    output_names,37    dynamic_axes,38    opset,39    use_external_data_format=False,40):41    output_path.parent.mkdir(parents=True, exist_ok=True)42    # PyTorch deprecated the `enable_onnx_checker` and `use_external_data_format` arguments in v1.11,43    # so we check the torch version for backwards compatibility44    if is_torch_less_than_1_11:45        export(46            model,47            model_args,48            f=output_path.as_posix(),49            input_names=ordered_input_names,50            output_names=output_names,51            dynamic_axes=dynamic_axes,52            do_constant_folding=True,53            use_external_data_format=use_external_data_format,54            enable_onnx_checker=True,55            opset_version=opset,56        )57    else:58        export(59            model,60            model_args,61            f=output_path.as_posix(),62            input_names=ordered_input_names,63            output_names=output_names,64            dynamic_axes=dynamic_axes,65            do_constant_folding=True,66            opset_version=opset,67        )68 69 70@torch.no_grad()71def convert_models(model_path: str, output_path: str, opset: int, fp16: bool = False):72    dtype = torch.float16 if fp16 else torch.float3273    if fp16 and torch.cuda.is_available():74        device = "cuda"75    elif fp16 and not torch.cuda.is_available():76        raise ValueError("`float16` model export is only supported on GPUs with CUDA")77    else:78        device = "cpu"79    pipeline = StableDiffusionPipeline.from_pretrained(model_path, torch_dtype=dtype).to(device)80    output_path = Path(output_path)81 82    # TEXT ENCODER83    num_tokens = pipeline.text_encoder.config.max_position_embeddings84    text_hidden_size = pipeline.text_encoder.config.hidden_size85    text_input = pipeline.tokenizer(86        "A sample prompt",87        padding="max_length",88        max_length=pipeline.tokenizer.model_max_length,89        truncation=True,90        return_tensors="pt",91    )92    onnx_export(93        pipeline.text_encoder,94        # casting to torch.int32 until the CLIP fix is released: https://github.com/huggingface/transformers/pull/18515/files95        model_args=(text_input.input_ids.to(device=device, dtype=torch.int32)),96        output_path=output_path / "text_encoder" / "model.onnx",97        ordered_input_names=["input_ids"],98        output_names=["last_hidden_state", "pooler_output"],99        dynamic_axes={100            "input_ids": {0: "batch", 1: "sequence"},101        },102        opset=opset,103    )104    del pipeline.text_encoder105 106    # UNET107    unet_in_channels = pipeline.unet.config.in_channels108    unet_sample_size = pipeline.unet.config.sample_size109    unet_path = output_path / "unet" / "model.onnx"110    onnx_export(111        pipeline.unet,112        model_args=(113            torch.randn(2, unet_in_channels, unet_sample_size, unet_sample_size).to(device=device, dtype=dtype),114            torch.randn(2).to(device=device, dtype=dtype),115            torch.randn(2, num_tokens, text_hidden_size).to(device=device, dtype=dtype),116            False,117        ),118        output_path=unet_path,119        ordered_input_names=["sample", "timestep", "encoder_hidden_states", "return_dict"],120        output_names=["out_sample"],  # has to be different from "sample" for correct tracing121        dynamic_axes={122            "sample": {0: "batch", 1: "channels", 2: "height", 3: "width"},123            "timestep": {0: "batch"},124            "encoder_hidden_states": {0: "batch", 1: "sequence"},125        },126        opset=opset,127        use_external_data_format=True,  # UNet is > 2GB, so the weights need to be split128    )129    unet_model_path = str(unet_path.absolute().as_posix())130    unet_dir = os.path.dirname(unet_model_path)131    unet = onnx.load(unet_model_path)132    # clean up existing tensor files133    shutil.rmtree(unet_dir)134    os.mkdir(unet_dir)135    # collate external tensor files into one136    onnx.save_model(137        unet,138        unet_model_path,139        save_as_external_data=True,140        all_tensors_to_one_file=True,141        location="weights.pb",142        convert_attribute=False,143    )144    del pipeline.unet145 146    # VAE ENCODER147    vae_encoder = pipeline.vae148    vae_in_channels = vae_encoder.config.in_channels149    vae_sample_size = vae_encoder.config.sample_size150    # need to get the raw tensor output (sample) from the encoder151    vae_encoder.forward = lambda sample, return_dict: vae_encoder.encode(sample, return_dict)[0].sample()152    onnx_export(153        vae_encoder,154        model_args=(155            torch.randn(1, vae_in_channels, vae_sample_size, vae_sample_size).to(device=device, dtype=dtype),156            False,157        ),158        output_path=output_path / "vae_encoder" / "model.onnx",159        ordered_input_names=["sample", "return_dict"],160        output_names=["latent_sample"],161        dynamic_axes={162            "sample": {0: "batch", 1: "channels", 2: "height", 3: "width"},163        },164        opset=opset,165    )166 167    # VAE DECODER168    vae_decoder = pipeline.vae169    vae_latent_channels = vae_decoder.config.latent_channels170    vae_out_channels = vae_decoder.config.out_channels171    # forward only through the decoder part172    vae_decoder.forward = vae_encoder.decode173    onnx_export(174        vae_decoder,175        model_args=(176            torch.randn(1, vae_latent_channels, unet_sample_size, unet_sample_size).to(device=device, dtype=dtype),177            False,178        ),179        output_path=output_path / "vae_decoder" / "model.onnx",180        ordered_input_names=["latent_sample", "return_dict"],181        output_names=["sample"],182        dynamic_axes={183            "latent_sample": {0: "batch", 1: "channels", 2: "height", 3: "width"},184        },185        opset=opset,186    )187    del pipeline.vae188 189    # SAFETY CHECKER190    if pipeline.safety_checker is not None:191        safety_checker = pipeline.safety_checker192        clip_num_channels = safety_checker.config.vision_config.num_channels193        clip_image_size = safety_checker.config.vision_config.image_size194        safety_checker.forward = safety_checker.forward_onnx195        onnx_export(196            pipeline.safety_checker,197            model_args=(198                torch.randn(199                    1,200                    clip_num_channels,201                    clip_image_size,202                    clip_image_size,203                ).to(device=device, dtype=dtype),204                torch.randn(1, vae_sample_size, vae_sample_size, vae_out_channels).to(device=device, dtype=dtype),205            ),206            output_path=output_path / "safety_checker" / "model.onnx",207            ordered_input_names=["clip_input", "images"],208            output_names=["out_images", "has_nsfw_concepts"],209            dynamic_axes={210                "clip_input": {0: "batch", 1: "channels", 2: "height", 3: "width"},211                "images": {0: "batch", 1: "height", 2: "width", 3: "channels"},212            },213            opset=opset,214        )215        del pipeline.safety_checker216        safety_checker = OnnxRuntimeModel.from_pretrained(output_path / "safety_checker")217        feature_extractor = pipeline.feature_extractor218    else:219        safety_checker = None220        feature_extractor = None221 222    onnx_pipeline = OnnxStableDiffusionPipeline(223        vae_encoder=OnnxRuntimeModel.from_pretrained(output_path / "vae_encoder"),224        vae_decoder=OnnxRuntimeModel.from_pretrained(output_path / "vae_decoder"),225        text_encoder=OnnxRuntimeModel.from_pretrained(output_path / "text_encoder"),226        tokenizer=pipeline.tokenizer,227        unet=OnnxRuntimeModel.from_pretrained(output_path / "unet"),228        scheduler=pipeline.scheduler,229        safety_checker=safety_checker,230        feature_extractor=feature_extractor,231        requires_safety_checker=safety_checker is not None,232    )233 234    onnx_pipeline.save_pretrained(output_path)235    print("ONNX pipeline saved to", output_path)236 237    del pipeline238    del onnx_pipeline239    _ = OnnxStableDiffusionPipeline.from_pretrained(output_path, provider="CPUExecutionProvider")240    print("ONNX pipeline is loadable")241 242 243if __name__ == "__main__":244    parser = argparse.ArgumentParser()245 246    parser.add_argument(247        "--model_path",248        type=str,249        required=True,250        help="Path to the `diffusers` checkpoint to convert (either a local directory or on the Hub).",251    )252 253    parser.add_argument("--output_path", type=str, required=True, help="Path to the output model.")254 255    parser.add_argument(256        "--opset",257        default=14,258        type=int,259        help="The version of the ONNX operator set to use.",260    )261    parser.add_argument("--fp16", action="store_true", default=False, help="Export the models in `float16` mode")262 263    args = parser.parse_args()264 265    convert_models(args.model_path, args.output_path, args.opset, args.fp16)266