20 Commits

Author SHA1 Message Date
msiracusa 5a170d29cf Update pull_request_template.md after changing to MIT license 2024-05-03 18:03:21 -07:00
msiracusa c5be2739d0 Update CONTRIBUTING.md to reflect the MIT license 2024-05-03 17:59:39 -07:00
msiracusa 5d8cdbb92d Update LICENSE.md to MIT
Changing repository license to MIT
2024-05-03 17:58:28 -07:00
Joshua Park f7d2f5e9fb Update swift-argument-parser to latest minor version
This will also make ml-stable-diffusion more tolerant with regard to which swift-argument-parser version it needs, resulting in fewer version conflicts in Xcode projects.
2024-03-12 12:38:44 -07:00
Toby Roseman d456a972cd Merge pull request #311 from TobyRoseman/mbp-new-api
Applying mixed bit compression using new optimize API
2024-01-23 16:42:12 -08:00
Toby Roseman ec09fe064f code review feedback 2024-01-23 16:41:35 -08:00
Toby Roseman a9b84c658e Update Documentation 2024-01-23 08:32:22 -08:00
Toby Roseman 9a3d463397 Applying mixed bit compression using new optimize API 2024-01-17 15:43:08 -08:00
Stuart Moore 7449ce46a4 Update CGImage+vImage.swift 2023-11-14 10:35:27 -08:00
Stuart Moore 5c62fab073 Use sRGB colorspace 2023-11-14 10:35:27 -08:00
Toby Roseman 3a0aeba5e5 Merge pull request #296 from TobyRoseman/compiled_models
Compiled Models in Python
2023-11-07 13:18:01 -08:00
Toby Roseman 46c94e8a86 Link to Core ML documentation about why compiled models are faster 2023-11-07 12:46:13 -08:00
Toby Roseman 4c54204bb0 Compiled Models in Python 2023-11-07 12:13:31 -08:00
Toby Roseman bea04420b5 Merge pull request #287 from TobyRoseman/minor-fix
Fix pipeline.py for Python 3.8
2023-10-24 08:26:53 -07:00
Toby Roseman d159b9254c Fix pipeline.py for Python 3.8 2023-10-23 10:27:56 -07:00
Atila Orhon 567dbf2757 fixes #281 2023-10-20 22:45:43 -07:00
Jiangdi f4466f0b73 fix error (#279) 2023-10-10 12:15:08 -07:00
Zach Nagengast 7fe1bf23b9 Cast safety_checker to float32 (#276) 2023-10-06 20:54:39 -07:00
Atila Orhon 11ba3a0965 Add custom_vae_version in MBP pre-analysis 2023-09-30 19:44:01 -07:00
Atila Orhon 96c044e07b Add iPhone 12 Pro Benchmark 2023-09-28 14:34:09 -07:00
12 changed files with 70 additions and 113 deletions
+1 -8
View File
@@ -1,8 +1 @@
Thank you for your interest in contributing to Core ML Stable Diffusion! Please review [CONTRIBUTING.md](../CONTRIBUTING.md) first. If you would like to proceed with making a pull request, please indicate your agreement to the terms outlined in CONTRIBUTING.md by checking the box below. If not, please go ahead and fork this repo and make your updates.
We appreciate your interest in the project!
Do not erase the below when submitting your pull request:
#########
- [ ] I agree to the terms outlined in CONTRIBUTING.md
Thank you for your interest in contributing to Core ML Stable Diffusion! Please review [CONTRIBUTING.md](../CONTRIBUTING.md) first. We appreciate your interest in the project!
+2
View File
@@ -1,3 +1,5 @@
*~
# Swift Package
.DS_Store
/.build
+3 -6
View File
@@ -1,14 +1,11 @@
# Contribution Guide
Thank you for your interest in contributing to Core ML Stable Diffusion! This project was released for system demonstration purposes and there are limited plans for future development of the repository. While we welcome new pull requests and issues please note that our response may be limited. Forks and out-of-tree improvements are strongly encouraged.
Thank you for your interest in contributing to Core ML Stable Diffusion! This project was released for system demonstration purposes and there are limited plans for future development of the repository. While we welcome new pull requests and issues please note that our response may be limited.
## Submitting a Pull Request
Under the project's [LICENSE](LICENSE.md), confirmation of the following is required to merge your changes:
*"I agree that all information entered is original and owned by me, and I hereby provide an irrevocable, royalty-free license to Apple to use, modify, copy, publish, prepare derivate works of, distribute (including under the Apple Sample Code License), such information and all intellectual property therein in whole or part, in perpetuity and worldwide, without any attribution."*
By submitting a pull request, you represent that you have the right to license your contribution to Apple and the community, and agree by submitting the patch that your contributions are licensed under the [LICENSE](LICENSE.md).
The project is licensed under the MIT license. By submitting a pull request, you represent that you have the right to license your contribution to Apple and the community, and agree by submitting the patch that your contributions are licensed under the MIT license.
## Code of Conduct
+5 -35
View File
@@ -1,39 +1,9 @@
Copyright (C) 2022 Apple Inc. All Rights Reserved.
MIT License
IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
Copyright (c) 2024 Apple Inc.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+1 -1
View File
@@ -18,7 +18,7 @@ let package = Package(
targets: ["StableDiffusionCLI"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", exact: "1.2.0")
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.3")
],
targets: [
.target(
+5 -4
View File
@@ -97,6 +97,7 @@ Target Device Hardware Generation:
| Device | `--compute-unit`| `--attention-implementation` | End-to-End Latency (s) | Diffusion Speed (iter/s) |
| --------------------- | --------------- | ---------------------------- | ---------------------- | ------------------------ |
| iPhone 12 Pro | `CPU_AND_NE` | `SPLIT_EINSUM` | 116* | 0.50 |
| iPhone 13 Pro Max | `CPU_AND_NE` | `SPLIT_EINSUM` | 86* | 0.68 |
| iPhone 14 Pro Max | `CPU_AND_NE` | `SPLIT_EINSUM` | 77* | 0.83 |
| iPhone 15 Pro Max | `CPU_AND_NE` | `SPLIT_EINSUM` | 31 | 0.85 |
@@ -182,7 +183,7 @@ Resources:
<details>
<summary> Details (Click to expand) </summary>
This section describes an advanced compression algorithm called [Mixed-Bit Palettization (MBP)](https://huggingface.co/blog/stable-diffusion-xl-coreml#what-is-mixed-bit-palettization) built on top of the [Post-Training Weight Palettization tools from coremltools-7.0](https://apple.github.io/coremltools/docs-guides/source/post-training-palettization.html).
This section describes an advanced compression algorithm called [Mixed-Bit Palettization (MBP)](https://huggingface.co/blog/stable-diffusion-xl-coreml#what-is-mixed-bit-palettization) built on top of the [Post-Training Weight Palettization tools](https://apple.github.io/coremltools/docs-guides/source/post-training-palettization.html) and using the [Weights Metadata API](https://apple.github.io/coremltools/docs-guides/source/mlmodel-utilities.html#get-weights-metadata) from [coremltools](https://github.com/apple/coremltools).
MBP builds a per-layer "palettization recipe" by picking a suitable number of bits among the Neural Engine supported bit-widths of 1, 2, 4, 6 and 8 in order to achieve the minimum average bit-width while maintaining a desired level of signal strength. The signal strength is measured by comparing the compressed model's output to that of the original float16 model. Given the same random seed and text prompts, PSNR between denoised latents is computed. The compression rate will depend on the model version as well as the tolerance for signal loss (drop in PSNR) since this algorithm is adaptive.
@@ -428,7 +429,7 @@ This generally takes 15-20 minutes on an M1 MacBook Pro. Upon successful executi
- `--refiner-version`: The refiner version name as published on the [Hugging Face Hub](https://huggingface.co/models?search=stable-diffusion). This is optional and if specified, this argument will convert and bundle the refiner unet alongside the model unet.
- `--bundle-resources-for-swift-cli`: Compiles all 4 models and bundles them along with necessary resources for text tokenization into `<output-mlpackages-directory>/Resources` which should provided as input to the Swift package. This flag is not necessary for the diffusers-based Python pipeline.
- `--bundle-resources-for-swift-cli`: Compiles all 4 models and bundles them along with necessary resources for text tokenization into `<output-mlpackages-directory>/Resources` which should provided as input to the Swift package. This flag is not necessary for the diffusers-based Python pipeline. [However using these compiled models in Python will significantly speed up inference](https://apple.github.io/coremltools/docs-guides/source/model-prediction.html#why-use-a-compiled-model).
- `--quantize-nbits`: Quantizes the weights of unet and text_encoder models down to 2, 4, 6 or 8 bits using a globally optimal k-means clustering algorithm. By default all models are weight-quantized to 16 bits even if this argument is not specified. Please refer to [this section](#compression-6-bits-and-higher for details and further guidance on weight compression.
@@ -454,11 +455,11 @@ This generally takes 15-20 minutes on an M1 MacBook Pro. Upon successful executi
Run text-to-image generation using the example Python pipeline based on [diffusers](https://github.com/huggingface/diffusers):
```shell
python -m python_coreml_stable_diffusion.pipeline --prompt "a photo of an astronaut riding a horse on mars" -i <output-mlpackages-directory> -o </path/to/output/image> --compute-unit ALL --seed 93
python -m python_coreml_stable_diffusion.pipeline --prompt "a photo of an astronaut riding a horse on mars" -i <core-ml-model-directory> -o </path/to/output/image> --compute-unit ALL --seed 93
```
Please refer to the help menu for all available arguments: `python -m python_coreml_stable_diffusion.pipeline -h`. Some notable arguments:
- `-i`: Should point to the `-o` directory from Step 4 of [Converting Models to Core ML](#converting-models-to-coreml) section from above. If you had specified `--bundle-resources-for-swift-cli` during conversion, then `-i` should point to the resulting `Resources` folder which holds the compiled `.mlmodelc` files. The compiled models load much faster after first use.
- `-i`: Should point to the `-o` directory from Step 4 of [Converting Models to Core ML](#converting-models-to-coreml) section from above. If you specified `--bundle-resources-for-swift-cli` during conversion, then use the resulting `Resources` folder (which holds the compiled `.mlmodelc` files). [The compiled models load much faster after first use](https://apple.github.io/coremltools/docs-guides/source/model-prediction.html#why-use-a-compiled-model).
- `--model-version`: If you overrode the default model version while converting models to Core ML, you will need to specify the same model version here.
- `--compute-unit`: Note that the most performant compute unit for this particular implementation may differ across different hardware. `CPU_AND_GPU` or `CPU_AND_NE` may be faster than `ALL`. Please refer to the [Performance Benchmark](#performance-benchmark) section for further guidance.
- `--scheduler`: If you would like to experiment with different schedulers, you may specify it here. For available options, please see the help menu. You may also specify a custom number of inference steps by `--num-inference-steps` which defaults to 50.
@@ -159,8 +159,8 @@ def _load_mlpackage(submodule_name,
logger.info(f"Loading {submodule_name} mlmodelc")
# FixMe: Submodule names and compiled resources names differ. Can change if names match in the future.
submodule_names = ["text_encoder", "text_encoder_2", "unet", "vae_decoder"]
compiled_names = ['TextEncoder', 'TextEncoder2', 'Unet', 'VAEDecoder', 'VAEEncoder']
submodule_names = ["text_encoder", "text_encoder_2", "unet", "vae_decoder", "vae_encoder", "safety_checker"]
compiled_names = ['TextEncoder', 'TextEncoder2', 'Unet', 'VAEDecoder', 'VAEEncoder', 'SafetyChecker']
name_map = dict(zip(submodule_names, compiled_names))
cname = name_map[submodule_name] + '.mlmodelc'
@@ -1,18 +1,20 @@
from pprint import pprint
import argparse
import coremltools as ct
import gc
import json
import logging
import numpy as np
import os
import coremltools as ct
import coremltools.optimize.coreml as cto
import numpy as np
from python_coreml_stable_diffusion.torch2coreml import get_pipeline
from python_coreml_stable_diffusion.mixed_bit_compression_pre_analysis import (
NBITS,
PALETTIZE_MIN_SIZE as MIN_SIZE
)
logging.basicConfig()
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
@@ -23,9 +25,6 @@ def main(args):
coreml_model = ct.models.MLModel(args.mlpackage_path, compute_units=ct.ComputeUnit.CPU_ONLY)
logger.info(f"Loaded {args.mlpackage_path}")
# Keep track of precision stats
precision_stats = {nbits:{'num_tensors': 0, 'numel': 0} for nbits in NBITS}
# Load palettization recipe
with open(args.pre_analysis_json_path, 'r') as f:
pre_analysis = json.load(f)
@@ -62,53 +61,29 @@ def main(args):
del pipe
gc.collect()
current_nbits: int
def op_selector(const):
parameter_tensor = const.val.val
if parameter_tensor.size < MIN_SIZE:
return False
if parameter_tensor.dtype != np.float16:
# These are the tensors that were compressed to look-up indices in previous passes
return False
tensor_hash = get_tensor_hash(parameter_tensor)
tensor_spec = f"{tensor_hash} with shape {parameter_tensor.shape}"
hashes = list(hashed_recipe)
pdist = np.abs(np.array(hashes) - tensor_hash)
op_name_configs = {}
weight_metadata = cto.get_weights_metadata(coreml_model, weight_threshold=MIN_SIZE)
hashes = np.array(list(hashed_recipe))
for name, metadata in weight_metadata.items():
# Look up target bits for this weight
tensor_hash = get_tensor_hash(metadata.val)
pdist = np.abs(hashes - tensor_hash)
assert(pdist.min() < 0.01)
matched = pdist.argmin()
logger.debug(f"{tensor_spec}: {tensor_hash} matched with {hashes[matched]} (hash error={pdist.min()})")
target_nbits = hashed_recipe[hashes[matched]]
do_palettize = current_nbits == target_nbits
if do_palettize:
logger.debug(f"{tensor_spec}: Palettizing to {target_nbits}-bit palette")
precision_stats[current_nbits]['num_tensors'] += 1
precision_stats[current_nbits]['numel'] += np.prod(parameter_tensor.shape)
return True
return False
for nbits in NBITS:
logger.info(f"Processing tensors targeting {nbits}-bit palettes")
current_nbits = nbits
if target_nbits == 16:
continue
config = ct.optimize.coreml.OptimizationConfig(
global_config=ct.optimize.coreml.OpPalettizerConfig(mode="kmeans", nbits=nbits, weight_threshold=None,),
is_deprecated=True,
op_selector=op_selector,
op_name_configs[name] = cto.OpPalettizerConfig(
mode="kmeans",
nbits=target_nbits,
weight_threshold=int(MIN_SIZE)
)
coreml_model = ct.optimize.coreml.palettize_weights(coreml_model, config=config)
logger.info(f"{precision_stats[nbits]['num_tensors']} tensors are palettized with {nbits} bits")
config = ct.optimize.coreml.OptimizationConfig(op_name_configs=op_name_configs)
coreml_model = ct.optimize.coreml.palettize_weights(coreml_model, config)
tot_numel = sum([precision_stats[nbits]['numel'] for nbits in NBITS])
final_size = sum([precision_stats[nbits]['numel'] * nbits for nbits in NBITS])
logger.info(f"Palettization result: {final_size / tot_numel:.2f}-bits resulting in {final_size / (8*1e6)} MB")
pprint(precision_stats)
coreml_model.save(args.o)
@@ -136,6 +111,15 @@ if __name__ == "__main__":
type=str,
help=("The string key into --pre-analysis-json-path's baselines dict"
))
parser.add_argument(
"--custom-vae-version",
type=str,
default=None,
help=
("Custom VAE checkpoint to override the pipeline's built-in VAE. "
"If specified, the specified VAE will be converted instead of the one associated to the `--model-version` checkpoint. "
"No precision override is applied when using a custom VAE."
))
args = parser.parse_args()
@@ -18,9 +18,10 @@ import time
import torch
import torch.nn as nn
import requests
torch.set_grad_enabled(False)
from tqdm import tqdm, trange
from tqdm import tqdm
# Bit-widths the Neural Engine is capable of accelerating
NBITS = [1, 2, 4, 6, 8]
@@ -341,8 +342,8 @@ def simulate_quant_fn(ref_pipe, quantization_to_simulate):
ref_out = run_pipe(ref_pipe)
simulated_psnr = sum([
float(f"{compute_psnr(r,t):.1f}")
for r,t in zip(ref_out, simulated_out)
float(f"{compute_psnr(r, t):.1f}")
for r, t in zip(ref_out, simulated_out)
]) / len(ref_out)
return simulated_out, simulated_psnr
@@ -458,9 +459,7 @@ def main(args):
json_name = f"{args.model_version.replace('/','-')}_palettization_recipe.json"
candidates, sizes = get_palettizable_modules(pipe.unet)
sizes_table = {
candidate:size for candidate, size in zip(candidates, sizes)
}
sizes_table = dict(zip(candidates, sizes))
if os.path.isfile(os.path.join(args.o, json_name)):
with open(os.path.join(args.o, json_name), "r") as f:
@@ -570,6 +569,15 @@ if __name__ == "__main__":
default=7,
type=int,
)
parser.add_argument(
"--custom-vae-version",
type=str,
default=None,
help=
("Custom VAE checkpoint to override the pipeline's built-in VAE. "
"If specified, the specified VAE will be converted instead of the one associated to the `--model-version` checkpoint. "
"No precision override is applied when using a custom VAE."
))
args = parser.parse_args()
main(args)
+2 -2
View File
@@ -5,6 +5,7 @@
import argparse
from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline
from diffusers.pipelines.pipeline_utils import DiffusionPipeline
from diffusers.pipelines.stable_diffusion import StableDiffusionPipelineOutput
from diffusers.schedulers import (
@@ -517,7 +518,7 @@ class CoreMLStableDiffusionPipeline(DiffusionPipeline):
control_net_additional_residuals = {}
# predict the noise residual
unet_additional_kwargs = unet_additional_kwargs | control_net_additional_residuals
unet_additional_kwargs.update(control_net_additional_residuals)
noise_pred = self.unet(
sample=latent_model_input.astype(np.float16),
@@ -697,7 +698,6 @@ def main(args):
logger.info("Initializing PyTorch pipe for reference configuration")
from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline
SDP = StableDiffusionXLPipeline if 'xl' in args.model_version else StableDiffusionPipeline
pytorch_pipe = SDP.from_pretrained(
@@ -480,7 +480,7 @@ def convert_vae_decoder(pipe, args):
1, # B
pipe.vae.config.latent_channels, # C
args.latent_h or pipe.unet.config.sample_size, # H
args.latent_w or pipe.unet.config.sample_size, # w
args.latent_w or pipe.unet.config.sample_size, # W
)
if args.custom_vae_version is None and args.xl_version:
@@ -827,7 +827,7 @@ def convert_unet(pipe, args, model_name = None):
logger.info("Done.")
if args.check_output_correctness:
baseline_out = pipe.unet(**baseline_sample_unet_inputs,
baseline_out = pipe.unet.to(torch.float32)(**baseline_sample_unet_inputs,
return_dict=False)[0].numpy()
reference_out = reference_unet(*sample_unet_inputs.values())[0].numpy()
report_correctness(baseline_out, reference_out,
@@ -926,6 +926,8 @@ def convert_safety_checker(pipe, args):
)
return
pipe.safety_checker = pipe.safety_checker.to(torch.float32)
im_h = pipe.vae.config.sample_size
im_w = pipe.vae.config.sample_size
@@ -58,7 +58,7 @@ extension CGImage {
let cgImage = uint8Image.makeCGImage(cgImageFormat:
.init(bitsPerComponent: 8,
bitsPerPixel: 3*8,
colorSpace: CGColorSpaceCreateDeviceRGB(),
colorSpace: CGColorSpace(name: CGColorSpace.sRGB) ?? CGColorSpaceCreateDeviceRGB(),
bitmapInfo: bitmapInfo)!)!
return cgImage