Skip to content

Commit efba3c2

Browse files
authoredJul 16, 2023
Preview progress images (huggingface#65)
* Render preview image per step, upgrade steps UI * Update UI for preview options * Reset preview image if disabled or at startup * Update preview for ios * Update preview help content text * Add new option for denoised intermediates * Cleanup edits from CR - Also includes new Package.resolved
1 parent 333e40c commit efba3c2

File tree

8 files changed

+156
-20
lines changed

8 files changed

+156
-20
lines changed
 

‎Diffusion-macOS/ControlsView.swift

+31-3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ struct ControlsView: View {
5555
@State private var disclosedPrompt = true
5656
@State private var disclosedGuidance = false
5757
@State private var disclosedSteps = false
58+
@State private var disclosedPreview = false
5859
@State private var disclosedSeed = false
5960
@State private var disclosedAdvanced = false
6061

@@ -71,6 +72,7 @@ struct ControlsView: View {
7172
@State private var showPromptsHelp = false
7273
@State private var showGuidanceHelp = false
7374
@State private var showStepsHelp = false
75+
@State private var showPreviewHelp = false
7476
@State private var showSeedHelp = false
7577
@State private var showAdvancedHelp = false
7678
@State private var positiveTokenCount: Int = 0
@@ -261,7 +263,7 @@ struct ControlsView: View {
261263
}
262264

263265
DisclosureGroup(isExpanded: $disclosedSteps) {
264-
CompactSlider(value: $generation.steps, in: 0...150, step: 5) {
266+
CompactSlider(value: $generation.steps, in: 1...150, step: 1) {
265267
Text("Steps")
266268
Spacer()
267269
Text("\(Int(generation.steps))")
@@ -285,7 +287,33 @@ struct ControlsView: View {
285287
}
286288
}.foregroundColor(.secondary)
287289
}
288-
290+
291+
DisclosureGroup(isExpanded: $disclosedPreview) {
292+
CompactSlider(value: $generation.previews, in: 0...25, step: 1) {
293+
Text("Previews")
294+
Spacer()
295+
Text("\(Int(generation.previews))")
296+
}.padding(.leading, 10)
297+
} label: {
298+
HStack {
299+
Label("Preview count", systemImage: "eye.square").foregroundColor(.secondary)
300+
Spacer()
301+
if disclosedPreview {
302+
Button {
303+
showPreviewHelp.toggle()
304+
} label: {
305+
Image(systemName: "info.circle")
306+
}
307+
.buttonStyle(.plain)
308+
.popover(isPresented: $showPreviewHelp, arrowEdge: .trailing) {
309+
previewHelp($showPreviewHelp)
310+
}
311+
} else {
312+
Text("\(Int(generation.previews))")
313+
}
314+
}.foregroundColor(.secondary)
315+
}
316+
289317
DisclosureGroup(isExpanded: $disclosedSeed) {
290318
let sliderLabel = generation.seed < 0 ? "Random Seed" : "Seed"
291319
CompactSlider(value: $generation.seed, in: -1...Double(maxSeed), step: 1) {
@@ -312,7 +340,7 @@ struct ControlsView: View {
312340
}
313341
}.foregroundColor(.secondary)
314342
}
315-
343+
316344
if Capabilities.hasANE {
317345
Divider()
318346
DisclosureGroup(isExpanded: $disclosedAdvanced) {

‎Diffusion-macOS/GeneratedImageView.swift

+20-9
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import SwiftUI
1010

1111
struct GeneratedImageView: View {
1212
@EnvironmentObject var generation: GenerationContext
13-
13+
1414
var body: some View {
1515
switch generation.state {
1616
case .startup: return AnyView(Image("placeholder").resizable())
@@ -19,23 +19,34 @@ struct GeneratedImageView: View {
1919
// The first time it takes a little bit before generation starts
2020
return AnyView(ProgressView())
2121
}
22+
2223
let step = Int(progress.step) + 1
2324
let fraction = Double(step) / Double(progress.stepCount)
2425
let label = "Step \(step) of \(progress.stepCount)"
25-
return AnyView(HStack {
26-
ProgressView(label, value: fraction, total: 1).padding()
27-
Button {
28-
generation.cancelGeneration()
29-
} label: {
30-
Image(systemName: "x.circle.fill").foregroundColor(.gray)
26+
27+
return AnyView(VStack {
28+
Group {
29+
if let safeImage = generation.previewImage {
30+
Image(safeImage, scale: 1, label: Text("generated"))
31+
.resizable()
32+
.clipShape(RoundedRectangle(cornerRadius: 20))
33+
}
34+
}
35+
HStack {
36+
ProgressView(label, value: fraction, total: 1).padding()
37+
Button {
38+
generation.cancelGeneration()
39+
} label: {
40+
Image(systemName: "x.circle.fill").foregroundColor(.gray)
41+
}
42+
.buttonStyle(.plain)
3143
}
32-
.buttonStyle(.plain)
3344
})
3445
case .complete(_, let image, _, _):
3546
guard let theImage = image else {
3647
return AnyView(Image(systemName: "exclamationmark.triangle").resizable())
3748
}
38-
49+
3950
return AnyView(
4051
Image(theImage, scale: 1, label: Text("generated"))
4152
.resizable()

‎Diffusion-macOS/HelpContent.swift

+19
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,25 @@ func stepsHelp(_ showing: Binding<Bool>) -> some View {
110110
return helpContent(title: "Inference Steps", description: description, showing: showing)
111111
}
112112

113+
func previewHelp(_ showing: Binding<Bool>) -> some View {
114+
let description =
115+
"""
116+
This number controls how many previews to display throughout the image generation process.
117+
118+
Using more previews can be useful if you want more visibility into how \
119+
generation is progressing.
120+
121+
However, computing each preview takes some time and can slow down \
122+
generation. If the process is too slow you can reduce the preview count, \
123+
which will result in less visibility of intermediate steps during generation.
124+
125+
You can try different values to see what works best for your hardware.
126+
127+
For the absolute fastest generation times, use 0 previews.
128+
"""
129+
return helpContent(title: "Preview Count", description: description, showing: showing)
130+
}
131+
113132
func seedHelp(_ showing: Binding<Bool>) -> some View {
114133
let description =
115134
"""

‎Diffusion.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"location" : "https://github.com/buh/CompactSlider.git",
77
"state" : {
88
"branch" : "main",
9-
"revision" : "3cb37fb7385913835b6844c6af2680c64000dcd2"
9+
"revision" : "6d591a76caecd583ad69fbcd06f2fb83135318c0"
1010
}
1111
},
1212
{
@@ -15,7 +15,7 @@
1515
"location" : "https://github.com/apple/ml-stable-diffusion",
1616
"state" : {
1717
"branch" : "main",
18-
"revision" : "48f07f24891155a14c51dd835bba7371bdf32d0e"
18+
"revision" : "b61c9aea05370d4bc06fce2dc00a002b21f13da5"
1919
}
2020
},
2121
{

‎Diffusion/Common/Pipeline/Pipeline.swift

+28-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,24 @@ import Combine
1212

1313
import StableDiffusion
1414

15-
typealias StableDiffusionProgress = StableDiffusionPipeline.Progress
15+
struct StableDiffusionProgress {
16+
var progress: StableDiffusionPipeline.Progress
17+
18+
var step: Int { progress.step }
19+
var stepCount: Int { progress.stepCount }
20+
21+
var currentImages: [CGImage?]
22+
23+
init(progress: StableDiffusionPipeline.Progress, previewIndices: [Bool]) {
24+
self.progress = progress
25+
self.currentImages = [nil]
26+
27+
// Since currentImages is a computed property, only access the preview image if necessary
28+
if progress.step < previewIndices.count, previewIndices[progress.step] {
29+
self.currentImages = progress.currentImages
30+
}
31+
}
32+
}
1633

1734
struct GenerationResult {
1835
var image: CGImage?
@@ -45,6 +62,7 @@ class Pipeline {
4562
negativePrompt: String = "",
4663
scheduler: StableDiffusionScheduler,
4764
numInferenceSteps stepCount: Int = 50,
65+
numPreviews previewCount: Int = 5,
4866
seed: UInt32? = nil,
4967
guidanceScale: Float = 7.5,
5068
disableSafety: Bool = false
@@ -63,10 +81,16 @@ class Pipeline {
6381
config.guidanceScale = guidanceScale
6482
config.disableSafety = disableSafety
6583
config.schedulerType = scheduler
66-
84+
config.useDenoisedIntermediates = true
85+
86+
// Evenly distribute previews based on inference steps
87+
let previewIndices = previewIndices(stepCount, previewCount)
88+
6789
let images = try pipeline.generateImages(configuration: config) { progress in
6890
sampleTimer.stop()
69-
handleProgress(progress, sampleTimer: sampleTimer)
91+
handleProgress(StableDiffusionProgress(progress: progress,
92+
previewIndices: previewIndices),
93+
sampleTimer: sampleTimer)
7094
if progress.stepCount != progress.step {
7195
sampleTimer.start()
7296
}
@@ -80,7 +104,7 @@ class Pipeline {
80104
return GenerationResult(image: image, lastSeed: theSeed, interval: interval, userCanceled: canceled, itsPerSecond: 1.0/sampleTimer.median)
81105
}
82106

83-
func handleProgress(_ progress: StableDiffusionPipeline.Progress, sampleTimer: SampleTimer) {
107+
func handleProgress(_ progress: StableDiffusionProgress, sampleTimer: SampleTimer) {
84108
self.progress = progress
85109
}
86110

‎Diffusion/Common/State.swift

+15-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class GenerationContext: ObservableObject {
3535
.receive(on: DispatchQueue.main)
3636
.sink { progress in
3737
guard let progress = progress else { return }
38+
self.updatePreviewIfNeeded(progress)
3839
self.state = .running(progress)
3940
}
4041
}
@@ -50,12 +51,24 @@ class GenerationContext: ObservableObject {
5051
@Published var numImages = 1.0
5152
@Published var seed = -1.0
5253
@Published var guidanceScale = 7.5
54+
@Published var previews = 5.0
5355
@Published var disableSafety = false
54-
56+
@Published var previewImage: CGImage? = nil
57+
5558
@Published var computeUnits: ComputeUnits = Settings.shared.userSelectedComputeUnits ?? ModelInfo.defaultComputeUnits
5659

5760
private var progressSubscriber: Cancellable?
5861

62+
private func updatePreviewIfNeeded(_ progress: StableDiffusionProgress) {
63+
if previews == 0 || progress.step == 0 {
64+
previewImage = nil
65+
}
66+
67+
if previews > 0, let newImage = progress.currentImages.first, newImage != nil {
68+
previewImage = newImage
69+
}
70+
}
71+
5972
func generate() async throws -> GenerationResult {
6073
guard let pipeline = pipeline else { throw "No pipeline" }
6174
let seed = self.seed >= 0 ? UInt32(self.seed) : nil
@@ -64,6 +77,7 @@ class GenerationContext: ObservableObject {
6477
negativePrompt: negativePrompt,
6578
scheduler: scheduler,
6679
numInferenceSteps: Int(steps),
80+
numPreviews: Int(previews),
6781
seed: seed,
6882
guidanceScale: Float(guidanceScale),
6983
disableSafety: disableSafety

‎Diffusion/Common/Utils.swift

+28
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,31 @@ extension Double {
1515
return String(format: "\(format)", self)
1616
}
1717
}
18+
19+
/// Returns an array of booleans that indicates at which steps a preview should be generated.
20+
///
21+
/// - Parameters:
22+
/// - numInferenceSteps: The total number of inference steps.
23+
/// - numPreviews: The desired number of previews.
24+
///
25+
/// - Returns: An array of booleans of size `numInferenceSteps`, where `true` values represent steps at which a preview should be made.
26+
func previewIndices(_ numInferenceSteps: Int, _ numPreviews: Int) -> [Bool] {
27+
// Ensure valid parameters
28+
guard numInferenceSteps > 0, numPreviews > 0 else {
29+
return [Bool](repeating: false, count: numInferenceSteps)
30+
}
31+
32+
// Compute the ideal (floating-point) step size, which represents the average number of steps between previews
33+
let idealStep = Double(numInferenceSteps) / Double(numPreviews)
34+
35+
// Compute the actual steps at which previews should be made. For each preview, we multiply the ideal step size by the preview number, and round to the nearest integer.
36+
// The result is converted to a `Set` for fast membership tests.
37+
let previewIndices: Set<Int> = Set((0..<numPreviews).map { previewIndex in
38+
return Int(round(Double(previewIndex) * idealStep))
39+
})
40+
41+
// Construct an array of booleans where each value indicates whether or not a preview should be made at that step.
42+
let previewArray = (0..<numInferenceSteps).map { previewIndices.contains($0) }
43+
44+
return previewArray
45+
}

‎Diffusion/Views/TextToImage.swift

+13-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ struct ShareButtons: View {
5252
}
5353

5454
struct ImageWithPlaceholder: View {
55+
@EnvironmentObject var generation: GenerationContext
5556
var state: Binding<GenerationState>
5657

5758
var body: some View {
@@ -62,10 +63,20 @@ struct ImageWithPlaceholder: View {
6263
// The first time it takes a little bit before generation starts
6364
return AnyView(ProgressView())
6465
}
66+
6567
let step = Int(progress.step) + 1
6668
let fraction = Double(step) / Double(progress.stepCount)
6769
let label = "Step \(step) of \(progress.stepCount)"
68-
return AnyView(ProgressView(label, value: fraction, total: 1).padding())
70+
return AnyView(VStack {
71+
Group {
72+
if let safeImage = generation.previewImage {
73+
Image(safeImage, scale: 1, label: Text("generated"))
74+
.resizable()
75+
.clipShape(RoundedRectangle(cornerRadius: 20))
76+
}
77+
}
78+
ProgressView(label, value: fraction, total: 1).padding()
79+
})
6980
case .complete(let lastPrompt, let image, _, let interval):
7081
guard let theImage = image else {
7182
return AnyView(Image(systemName: "exclamationmark.triangle").resizable())
@@ -125,5 +136,6 @@ struct TextToImage: View {
125136
Spacer()
126137
}
127138
.padding()
139+
.environmentObject(generation)
128140
}
129141
}

0 commit comments

Comments
 (0)