Skip to content

Commit 9e02241

Browse files
committed
Multi-Model Selection
* Added selection of available models via the UI * Overhauled the README to reflect all the new changes
1 parent 8b39749 commit 9e02241

File tree

3 files changed

+63
-34
lines changed

3 files changed

+63
-34
lines changed

Diffusion/Support/AppState.swift

+22-16
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ class AppState: ObservableObject {
2424
}
2525
}
2626

27+
var currentModel: String = "" {
28+
didSet {
29+
Task {
30+
await load(model: currentModel)
31+
}
32+
}
33+
}
34+
2735
private init() {
2836
NSLog("*** AppState initialized")
2937
// Does the model path exist?
@@ -53,19 +61,10 @@ class AppState: ObservableObject {
5361
state = .error("No models found under model directory: \(dir.path)")
5462
return
5563
}
56-
Task {
57-
do {
58-
try await load(model: model)
59-
} catch {
60-
NSLog("Error loading model: \(error)")
61-
DispatchQueue.main.async {
62-
self.state = .error(error.localizedDescription)
63-
}
64-
}
65-
}
64+
currentModel = model
6665
}
6766

68-
func load(model: String) async throws {
67+
func load(model: String) async {
6968
NSLog("*** Loading model: \(model)")
7069
let dir = modelDir.appending(component: model, directoryHint: .isDirectory)
7170
let fm = FileManager.default
@@ -80,11 +79,18 @@ class AppState: ObservableObject {
8079
// .all works for v1.4, but not for v1.5
8180
configuration.computeUnits = .cpuAndGPU
8281
// TODO: measure performance on different devices
83-
let pipeline = try StableDiffusionPipeline(resourcesAt: dir, configuration: configuration, disableSafety: true)
84-
NSLog("Pipeline loaded in \(Date().timeIntervalSince(beginDate))")
85-
DispatchQueue.main.async {
86-
self.pipeline = Pipeline(pipeline)
87-
self.state = .ready("Ready")
82+
do {
83+
let pipeline = try StableDiffusionPipeline(resourcesAt: dir, configuration: configuration, disableSafety: true)
84+
NSLog("Pipeline loaded in \(Date().timeIntervalSince(beginDate))")
85+
DispatchQueue.main.async {
86+
self.pipeline = Pipeline(pipeline)
87+
self.state = .ready("Ready")
88+
}
89+
} catch {
90+
NSLog("Error loading model: \(error)")
91+
DispatchQueue.main.async {
92+
self.state = .error(error.localizedDescription)
93+
}
8894
}
8995
}
9096
}

Diffusion/Views/MainAppView.swift

+29-9
Original file line numberDiff line numberDiff line change
@@ -71,39 +71,59 @@ struct MainAppView: View {
7171
.buttonStyle(.borderedProminent)
7272
.disabled(isBusy)
7373
}
74+
7475
Spacer().frame(height: 16)
76+
7577
HStack(alignment: .top) {
7678
VStack(alignment: .leading) {
7779
Group {
80+
Picker("Model", selection: $context.currentModel) {
81+
ForEach(context.models, id: \.self) { s in
82+
Text(s).tag(s)
83+
}
84+
}
85+
86+
Spacer().frame(height: 16)
87+
7888
Picker("Scheduler", selection: $scheduler) {
7989
ForEach(StableDiffusionScheduler.allCases, id: \.self) { s in
8090
Text(s.rawValue).tag(s)
8191
}
8292
}
83-
Text("")
93+
94+
Spacer().frame(height: 16)
95+
8496
Text("Guidance Scale: \(String(format: "%.1f", guidance))")
8597
Slider(value: $guidance, in: 0...15, step: 0.1, label: {},
8698
minimumValueLabel: {Text("0")},
8799
maximumValueLabel: {Text("15")})
88-
Text("")
100+
101+
Spacer().frame(height: 16)
89102
}
90103
Group {
91104
Text("Number of Inference Steps: \(String(format: "%.0f", steps))")
92105
Slider(value: $steps, in: 1...300, step: 1, label: {},
93106
minimumValueLabel: {Text("1")},
94107
maximumValueLabel: {Text("300")})
95-
Text("")
108+
109+
Spacer().frame(height: 16)
110+
96111
Text("Number of Images: \(String(format: "%.0f", numImages))")
97112
Slider(value: $numImages, in: 1...8, step: 1, label: {},
98113
minimumValueLabel: {Text("1")},
99114
maximumValueLabel: {Text("8")})
100-
Text("")
115+
116+
Spacer().frame(height: 16)
117+
}
118+
Group {
119+
Text("Safety Check On?")
120+
Toggle("", isOn: $safetyOn)
121+
122+
Spacer().frame(height: 16)
123+
124+
Text("Seed")
125+
TextField("", value: $seed, format: .number)
101126
}
102-
Text("Safety Check On?")
103-
Toggle("", isOn: $safetyOn)
104-
Text("")
105-
Text("Seed")
106-
TextField("", value: $seed, format: .number)
107127
// Group {
108128
// Text("Image Width")
109129
// Slider(value: $width, in: 64...2048, step: 8, label: {},

README.md

+12-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
# Diffusion
22

3-
This is a simple app that shows how to integrate Apple's [Core ML Stable Diffusion implementation](https://github.com/apple/ml-stable-diffusion) in a native Swift UI application. It can be used for faster iteration, or as sample code for other use cases.
3+
This app integrates Apple's [Core ML Stable Diffusion implementation](https://github.com/apple/ml-stable-diffusion) in a native Swift UI application. It can be used for simple image generation, or as a test platform for trying out new features and functionality. You'd have to compile the code yourself though since there is no binary release — but compiling the code should be fairly straightforward since everything is self-contained 🙂
44

55
This is what it looks like:
66
![App Screenshot](assets/screenshot.jpg)
77

8-
On first launch, the application downloads a zipped archive with a Core ML version of Runway's Stable Diffusion v1.5, from [this location in the Hugging Face Hub](https://huggingface.co/pcuenq/coreml-stable-diffusion/tree/main). This process takes a while, as several GB of data have to be downloaded and unarchived.
9-
10-
For faster inference, we use a very fast scheduler: [DPM-Solver++](https://github.com/LuChengTHU/dpm-solver) that we ported to Swift. Since this scheduler is still not available in Apple's GitHub repository, the application depends on the following fork instead: https://github.com/pcuenca/ml-stable-diffusion. Our Swift port is based on [Diffusers' DPMSolverMultistepScheduler](https://github.com/huggingface/diffusers/blob/main/src/diffusers/schedulers/scheduling_dpmsolver_multistep.py), with a number of simplifications.
11-
128
## Installing
139

1410
### The Project
@@ -38,14 +34,21 @@ The plan is to add the ability to switch between multiple modesl by selecting th
3834

3935
## Limitations
4036

41-
- The UI does not expose a way to configure the scheduler, number of inference steps, or generation seed. These are all available in the underlying code.
42-
- A single model (Stable Diffusion v1.5) is considered. The Core ML compute units have been hardcoded to CPU and GPU, since that's what gives best results on my Mac (M1 Max MacBook Pro).
37+
- ~~The UI does not expose a way to configure the scheduler, number of inference steps, or generation seed. These are all available in the underlying code.~~
38+
- ~~A single model (Stable Diffusion v1.5) is considered.~~
39+
- The Core ML compute units have been hardcoded to CPU and GPU, since that's what gives best results on my Mac (M1 Max MacBook Pro).
4340
- Sometimes generation returns a `nil` image. This needs to be investigated.
4441

4542
## Next Steps
4643

4744
- ~~Improve UI. Allow the user to select generation parameters.~~
4845
- ~~Implement negative prompts.~~
4946
- Implement other interesting schedulers.
50-
- Allow other models to run. Provide a recommended "compute units" configuration based on model and platform.
51-
- Explore other features (image to image, for example).
47+
- ~~Allow other models to run.~~
48+
- Provide a recommended "compute units" configuration based on model and platform.
49+
- Explore other features (image to image, for example)
50+
51+
## Credit
52+
53+
For faster inference, the project uses a very fast scheduler: [DPM-Solver++](https://github.com/LuChengTHU/dpm-solver) that was ported to Swift by the Hugging Face team/[Pedro Cuenca](https://github.com/pcuenca/). Since this scheduler is still not available in Apple's GitHub repository, the application depends on the following fork instead: https://github.com/pcuenca/ml-stable-diffusion. The Swift port is based on [Diffusers' DPMSolverMultistepScheduler](https://github.com/huggingface/diffusers/blob/main/src/diffusers/schedulers/scheduling_dpmsolver_multistep.py), with a number of simplifications.
54+

0 commit comments

Comments
 (0)