code: def convert_controlnet(pipe, args):
      """Converts each ControlNet for Stable Diffusion"""
      if not hasattr(pipe, "unet"):
          raise RuntimeError(
              "convert_unet() deletes pipe.unet to save RAM. "
              "Please use convert_vae_encoder() before convert_unet()"
          )
  
      if not hasattr(pipe, "text_encoder"):
          raise RuntimeError(
              "convert_text_encoder() deletes pipe.text_encoder to save RAM. "
              "Please use convert_unet() before convert_text_encoder()"
          )
  
      for i, controlnet_model_version in enumerate(
          args.convert_controlnet
      ):
          base_model = _get_controlnet_base_model(
              controlnet_model_version
          )
  
          if (
              base_model is None
              and args.model_version != "runwayml/stable-diffusion-v1-5"
          ):
              logger.warning(
                  f"The original ControlNet models were trained using Stable Diffusion v1.5. "
                  f"It is possible that model {args.model_version} is not compatible with controlnet."
              )
          if (
              base_model is not None
              and base_model != args.model_version
          ):
              raise RuntimeError(
                  f"ControlNet model {controlnet_model_version} was trained using "
                  f"Stable Diffusion model {base_model}.\n However, you specified "
                  f"version {args.model_version} in the command line. Please, use "
                  f"--model-version {base_model} to convert this model."
              )
  
          controlnet_model_name = controlnet_model_version.replace(
              "/", "_"
          )
          fname = f"ControlNet_{controlnet_model_name}.mlpackage"
          out_path = os.path.join(args.o, fname)
  
          if os.path.exists(out_path):
              logger.info(
                  f"`controlnet_{controlnet_model_name}` already exists at {out_path}, skipping conversion."
              )
              continue
  
          if i == 0:
              batch_size = 2  # for classifier-free guidance
              sample_shape = (
                  batch_size,  # B
                  pipe.unet.config.in_channels,  # C
                  (args.latent_h or pipe.unet.config.sample_size),  # H
                  (args.latent_w or pipe.unet.config.sample_size),  # W
              )
  
              encoder_hidden_states_shape = (
                  batch_size,
                  args.text_encoder_hidden_size
                  or pipe.text_encoder.config.hidden_size,
                  1,
                  args.text_token_sequence_length
                  or pipe.text_encoder.config.max_position_embeddings,
              )
  
              controlnet_cond_shape = (
                  batch_size,  # B
                  3,  # C
                  (args.latent_h or pipe.unet.config.sample_size)
                  * 8,  # H
                  (args.latent_w or pipe.unet.config.sample_size)
                  * 8,  # w
              )
  
              # Create the scheduled timesteps for downstream use
              DEFAULT_NUM_INFERENCE_STEPS = 50
              pipe.scheduler.set_timesteps(DEFAULT_NUM_INFERENCE_STEPS)
  
              # Prepare inputs
              sample_controlnet_inputs = OrderedDict(
                  [
                      ("sample", torch.rand(*sample_shape)),
                      (
                          "timestep",
                          torch.tensor(
                              [pipe.scheduler.timesteps[0].item()]
                              * (batch_size)
                          ).to(torch.float32),
                      ),
                      (
                          "encoder_hidden_states",
                          torch.rand(*encoder_hidden_states_shape),
                      ),
                      (
                          "controlnet_cond",
                          torch.rand(*controlnet_cond_shape),
                      ),
                  ]
              )
              sample_controlnet_inputs_spec = {
                  k: (v.shape, v.dtype)
                  for k, v in sample_controlnet_inputs.items()
              }
              logger.info(
                  f"Sample ControlNet inputs spec: {sample_controlnet_inputs_spec}"
              )
  
              baseline_sample_controlnet_inputs = deepcopy(
                  sample_controlnet_inputs
              )
              baseline_sample_controlnet_inputs[
                  "encoder_hidden_states"
              ] = (
                  baseline_sample_controlnet_inputs[
                      "encoder_hidden_states"
                  ]
                  .squeeze(2)
                  .transpose(1, 2)
              )
  
          # Import controlnet model and initialize reference controlnet
          original_controlnet = ControlNetModel.from_pretrained(
              controlnet_model_version, use_auth_token=True
          )
          reference_controlnet = controlnet.ControlNetModel(
              **original_controlnet.config
          ).eval()
          load_state_dict_summary = (
              reference_controlnet.load_state_dict(
                  original_controlnet.state_dict()
              )
          )
  
          num_residuals = reference_controlnet.get_num_residuals()
          output_keys = [
              f"additional_residual_{i}" for i in range(num_residuals)
          ]
  
          # JIT trace
          logger.info("JIT tracing..")
          reference_controlnet = torch.jit.trace(
              reference_controlnet,
              list(sample_controlnet_inputs.values()),
          )
          logger.info("Done.")
  
          if args.check_output_correctness:
              baseline_out = original_controlnet(
                  **baseline_sample_controlnet_inputs, return_dict=False
              )
              reference_out = reference_controlnet(
                  *sample_controlnet_inputs.values()
              )
              report_correctness(
                  baseline_out[-1].numpy(),
                  reference_out[-1].numpy(),
                  f"{controlnet_model_name} baseline to reference PyTorch",
              )
  
          del original_controlnet
          gc.collect()
  
          coreml_sample_controlnet_inputs = {
              k: v.numpy().astype(np.float16)
              for k, v in sample_controlnet_inputs.items()
          }
  
          coreml_controlnet, out_path = _convert_to_coreml(
              f"controlnet_{controlnet_model_name}",
              reference_controlnet,
              coreml_sample_controlnet_inputs,
              output_keys,
              args,
              out_path=out_path,
          )
  
          del reference_controlnet
          gc.collect()
  
          coreml_controlnet.author = f"Please refer to the Model Card available at huggingface.co/{controlnet_model_version}"
          coreml_controlnet.license = "OpenRAIL (https://huggingface.co/spaces/CompVis/stable-diffusion-license)"
          coreml_controlnet.version = controlnet_model_version
          coreml_controlnet.short_description = (
              "ControlNet is a neural network structure to control diffusion models by adding extra conditions. "
              "Please refer to https://arxiv.org/abs/2302.05543 for details."
          )
  
          # Set the input descriptions
          coreml_controlnet.input_description[
              "sample"
          ] = "The low resolution latent feature maps being denoised through reverse diffusion"
          coreml_controlnet.input_description[
              "timestep"
          ] = "A value emitted by the associated scheduler object to condition the model on a given noise schedule"
          coreml_controlnet.input_description[
              "encoder_hidden_states"
          ] = (
              "Output embeddings from the associated text_encoder model to condition to generated image on text. "
              "A maximum of 77 tokens (~40 words) are allowed. Longer text is truncated. "
              "Shorter text does not reduce computation."
          )
          coreml_controlnet.input_description[
              "controlnet_cond"
          ] = "An additional input image for ControlNet to condition the generated images."
  
          # Set the output descriptions
          for i in range(num_residuals):
              coreml_controlnet.output_description[
                  f"additional_residual_{i}"
              ] = (
                  "One of the outputs of each downsampling block in ControlNet. "
                  "The value added to the corresponding resnet output in UNet."
              )
  
          coreml_controlnet.save(out_path)
          logger.info(f"Saved controlnet into {out_path}")
  
          # Parity check PyTorch vs CoreML
          if args.check_output_correctness:
              coreml_out = coreml_controlnet.predict(
                  coreml_sample_controlnet_inputs
              )
              report_correctness(
                  baseline_out[-1].numpy(),
                  coreml_out[output_keys[-1]],
                  "controlnet baseline PyTorch to reference CoreML",
              )
  
          del coreml_controlnet
          gc.collect()
  
imports: from python_coreml_stable_diffusion import unet, controlnet, chunk_mlprogram
  import argparse
  from collections import OrderedDict, defaultdict
  from copy import deepcopy
  import coremltools
  from diffusers import StableDiffusionPipeline, DiffusionPipeline, ControlNetModel
  import gc
  import logging
  import numpy
  import os
  import requests
  import shutil
  import time
  import re
  import pathlib
  import torch
  import torch.nn
  import torch.nn.functional
  from types import MethodType
  from transformers.models.clip import modeling_clip
label_desc: Use image node_id 18 for calling a global function with example usage: convert_controlnet(pipe, args) without return types