Files
Zach Nagengast 94dfc6b548 Support for SDXL refiner (#227)
* Initial support for SDXL refiner

* Cleanup

* Add arg for converting Unet in float32 precision

* Setup scale factor with pipeline in CLI

* Update cli arg and future warning

* Bundle refiner unet if specified

* Update script for bundled refiner

- Also skip loading model if check_output_correctness is missing, since the model does not require inferencing at conversion time

* Flip skip_model_load bool

* Cleanup

* Support bundled UnetRefiner

* Add seperate refiner config value

- Includes unloading base unet when swapping to refiner

* Update readme for SDXL refiner

* Add condition for new SDXL coreml input features

* Revert pipeline interface change, add extra logging on pipe load

* Reset model_version after refiner conversion

* Reset model_version before refiner conversion but after pipe init

* Add refiner chunking

* Ensure unets are unloaded for reduceMemory true

* Handle missing UnetRefiner.mlmodelc on pipeline load

Co-authored-by: Pedro Cuenca <pedro@huggingface.co>

* Prewarm refiner on load, unload on complete

* Force cpu_and_gpu for VAE until it can be fixed

* Include output dtype of np.float32 for all conversions

* Allow a custom VAE to be converted.

* Revert hardcoded reduceMemory

* Fix merge

* Default chunking arg for --merge-chunks-in-pipeline-model when called from torch2coreml

---------

Co-authored-by: Pedro Cuenca <pedro@huggingface.co>
2023-09-26 13:09:58 -07:00

109 lines
3.8 KiB
Swift

// For licensing see accompanying LICENSE.md file.
// Copyright (C) 2022 Apple Inc. All Rights Reserved.
import Foundation
import CoreML
/// A encoder model which produces latent samples from RGB images
@available(iOS 16.2, macOS 13.1, *)
public struct Encoder: ResourceManaging {
public enum Error: String, Swift.Error {
case sampleInputShapeNotCorrect
}
/// VAE encoder model + post math and adding noise from schedular
var model: ManagedMLModel
/// Create encoder from Core ML model
///
/// - Parameters:
/// - url: Location of compiled VAE encoder Core ML model
/// - configuration: configuration to be used when the model is loaded
/// - Returns: An encoder that will lazily load its required resources when needed or requested
public init(modelAt url: URL, configuration: MLModelConfiguration) {
self.model = ManagedMLModel(modelAt: url, configuration: configuration)
}
/// Ensure the model has been loaded into memory
public func loadResources() throws {
try model.loadResources()
}
/// Unload the underlying model to free up memory
public func unloadResources() {
model.unloadResources()
}
/// Prediction queue
let queue = DispatchQueue(label: "encoder.predict")
/// Encode image into latent sample
///
/// - Parameters:
/// - image: Input image
/// - scaleFactor: scalar multiplier on latents before encoding image
/// - random
/// - Returns: The encoded latent space as MLShapedArray
public func encode(
_ image: CGImage,
scaleFactor: Float32,
random: inout RandomSource
) throws -> MLShapedArray<Float32> {
let imageData = try image.planarRGBShapedArray(minValue: -1.0, maxValue: 1.0)
guard imageData.shape == inputShape else {
// TODO: Consider auto resizing and croping similar to how Vision or CoreML auto-generated Swift code can accomplish with `MLFeatureValue`
throw Error.sampleInputShapeNotCorrect
}
let dict = [inputName: MLMultiArray(imageData)]
let input = try MLDictionaryFeatureProvider(dictionary: dict)
let result = try model.perform { model in
try model.prediction(from: input)
}
let outputName = result.featureNames.first!
let outputValue = result.featureValue(for: outputName)!.multiArrayValue!
let output = MLShapedArray<Float32>(converting: outputValue)
// DiagonalGaussianDistribution
let mean = output[0][0..<4]
let logvar = MLShapedArray<Float32>(
scalars: output[0][4..<8].scalars.map { min(max($0, -30), 20) },
shape: mean.shape
)
let std = MLShapedArray<Float32>(
scalars: logvar.scalars.map { exp(0.5 * $0) },
shape: logvar.shape
)
let latent = MLShapedArray<Float32>(
scalars: zip(mean.scalars, std.scalars).map {
Float32(random.nextNormal(mean: Double($0), stdev: Double($1)))
},
shape: logvar.shape
)
// Reference pipeline scales the latent after encoding
let latentScaled = MLShapedArray<Float32>(
scalars: latent.scalars.map { $0 * scaleFactor },
shape: [1] + latent.shape
)
return latentScaled
}
var inputDescription: MLFeatureDescription {
try! model.perform { model in
model.modelDescription.inputDescriptionsByName.first!.value
}
}
var inputName: String {
inputDescription.name
}
/// The expected shape of the models latent sample input
var inputShape: [Int] {
inputDescription.multiArrayConstraint!.shape.map { $0.intValue }
}
}