Formatting, linting, and hopefully a CI build.

This commit is contained in:
2023-04-22 15:43:22 -07:00
parent 2759c8d7fb
commit 4bf5ceefbe
13 changed files with 117 additions and 68 deletions

View File

@ -0,0 +1,7 @@
import Foundation
public enum SdCoreError: Error {
case modelNotLoaded
case imageEncode
case modelNotFound
}

View File

@ -0,0 +1,22 @@
import CoreImage
import Foundation
import UniformTypeIdentifiers
extension CGImage {
func toPngData() throws -> Data {
guard let data = CFDataCreateMutable(nil, 0) else {
throw SdCoreError.imageEncode
}
guard let destination = CGImageDestinationCreateWithData(data, "public.png" as CFString, 1, nil) else {
throw SdCoreError.imageEncode
}
CGImageDestinationAddImage(destination, self, nil)
if CGImageDestinationFinalize(destination) {
return data as Data
} else {
throw SdCoreError.imageEncode
}
}
}

View File

@ -0,0 +1,66 @@
import Foundation
import StableDiffusion
import StableDiffusionProtos
public actor ModelManager {
private var modelInfos: [String: SdModelInfo] = [:]
private var modelUrls: [String: URL] = [:]
private var modelStates: [String: ModelState] = [:]
private let modelBaseURL: URL
public init(modelBaseURL: URL) {
self.modelBaseURL = modelBaseURL
}
public func reloadModels() throws {
modelInfos.removeAll()
modelStates.removeAll()
let contents = try FileManager.default.contentsOfDirectory(at: modelBaseURL.resolvingSymlinksInPath(), includingPropertiesForKeys: [.isDirectoryKey])
for subdirectoryURL in contents {
let values = try subdirectoryURL.resourceValues(forKeys: [.isDirectoryKey])
if values.isDirectory ?? false {
try addModel(url: subdirectoryURL)
}
}
}
public func listModels() -> [SdModelInfo] {
Array(modelInfos.values)
}
public func getModelState(name: String) -> ModelState? {
modelStates[name]
}
private func addModel(url: URL) throws {
var info = SdModelInfo()
info.name = url.lastPathComponent
let attention = getModelAttention(url)
info.attention = attention ?? "unknown"
modelInfos[info.name] = info
modelUrls[info.name] = url
modelStates[info.name] = try ModelState(url: url)
}
private func getModelAttention(_ url: URL) -> String? {
let unetMetadataURL = url.appending(components: "Unet.mlmodelc", "metadata.json")
struct ModelMetadata: Decodable {
let mlProgramOperationTypeHistogram: [String: Int]
}
do {
let jsonData = try Data(contentsOf: unetMetadataURL)
let metadatas = try JSONDecoder().decode([ModelMetadata].self, from: jsonData)
guard metadatas.count == 1 else {
return nil
}
return metadatas[0].mlProgramOperationTypeHistogram["Ios16.einsum"] != nil ? "split-einsum" : "original"
} catch {
return nil
}
}
}

View File

@ -0,0 +1,52 @@
import CoreML
import Foundation
import StableDiffusion
import StableDiffusionProtos
public actor ModelState {
private let url: URL
private var pipeline: StableDiffusionPipeline?
private var tokenizer: BPETokenizer?
public init(url: URL) throws {
self.url = url
}
public func load() throws {
let config = MLModelConfiguration()
config.computeUnits = .all
pipeline = try StableDiffusionPipeline(
resourcesAt: url,
controlNet: [],
configuration: config,
disableSafety: true,
reduceMemory: false
)
let mergesUrl = url.appending(component: "merges.txt")
let vocabUrl = url.appending(component: "vocab.json")
tokenizer = try BPETokenizer(mergesAt: mergesUrl, vocabularyAt: vocabUrl)
}
public func generate(_ request: SdGenerateImagesRequest) throws -> SdGenerateImagesResponse {
guard let pipeline else {
throw SdCoreError.modelNotLoaded
}
var pipelineConfig = StableDiffusionPipeline.Configuration(prompt: request.prompt)
pipelineConfig.negativePrompt = request.negativePrompt
pipelineConfig.seed = UInt32.random(in: 0 ..< UInt32.max)
var response = SdGenerateImagesResponse()
for _ in 0 ..< request.imageCount {
let images = try pipeline.generateImages(configuration: pipelineConfig)
for cgImage in images {
guard let cgImage else { continue }
var image = SdImage()
image.content = try cgImage.toPngData()
response.images.append(image)
}
}
return response
}
}