Skip to content

Commit 917dc99

Browse files
authored
Fix SD text encoder crash: infer sequence length from model (#103)
* Fix SD text encoder crash: infer sequence length from model StableDiffusionPipeline.encodeText() passed raw token count as the sequence dimension, but CLIP text encoders are exported with a fixed seq_len (77). When a prompt tokenizes to a different length, resolvingDynamicDimensions crashes. Fix: use CoreAITextEncoder.encode() which pads/truncates correctly, and infer the sequence length from the model input descriptor at load time instead of hardcoding 77. Fixes #102. * Add unit tests for text encoder token padding and truncation
1 parent d967fa3 commit 917dc99

4 files changed

Lines changed: 52 additions & 5 deletions

File tree

swift/Sources/CoreAIDiffusionPipeline/Components/CoreAIDiffusionModelFunction.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,17 @@ public actor CoreAIDiffusionModelFunction {
223223
return result
224224
}
225225
}
226+
227+
/// Infer the sequence length from the first input's shape (dim 1).
228+
/// Returns nil if the model isn't loaded or has no rank-2 input.
229+
public func inferSequenceLength() async throws -> Int? {
230+
let descs = try await inputDescriptors
231+
guard let desc = descs.values.first, desc.shape.count >= 2 else {
232+
return nil
233+
}
234+
let dim = desc.shape[1]
235+
return dim > 0 ? dim : nil
236+
}
226237
}
227238

228239
// MARK: - Errors

swift/Sources/CoreAIDiffusionPipeline/Pipelines/PipelineDescriptor+CoreAI.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,15 @@ extension PipelineDescriptor {
113113
throw PipelineLoadError.missingComponent("text_encoder")
114114
}
115115

116+
let textEncoderSeqLength = try await textEncoderFunction.inferSequenceLength() ?? 77
117+
116118
let textEncoder = CoreAITextEncoder(
117119
function: textEncoderFunction,
118120
tokenize: { text in
119-
let (_, ids) = tokenizer.tokenize(input: text, minCount: 77)
121+
let (_, ids) = tokenizer.tokenize(input: text, minCount: textEncoderSeqLength)
120122
return ids.map(Int32.init)
121123
},
122-
maxLength: 77
124+
maxLength: textEncoderSeqLength
123125
)
124126

125127
let denoiser = CoreAIDenoiser(function: unetFunction)

swift/Sources/CoreAIDiffusionPipeline/Pipelines/StableDiffusionPipeline.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,10 @@ public struct StableDiffusionPipeline: DiffusionPipeline {
172172
// MARK: - Private Helpers
173173

174174
private func encodeText(_ text: String) async throws -> [Float] {
175-
let tokenize = components.textEncoder.tokenize
176-
let ids = tokenize(text)
177-
return try await components.textEncoder.function.run(intInputs: [(ids, [1, ids.count])])
175+
let output = try await components.textEncoder.encode(text)
176+
let shape = output.hiddenStates.shape
177+
let count = shape.reduce(1, *)
178+
return readNDArray(output.hiddenStates, as: Float.self, count: count)
178179
}
179180

180181
private func runDenoiser(

swift/Tests/DiffusionPipelineTests/DiffusionPipelineTests.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,36 @@ struct DiffusionPipelineTests {
4545
}
4646
}
4747
}
48+
49+
@Suite("TextEncoder tokenize padding")
50+
struct TextEncoderTokenizeTests {
51+
@Test("Tokenize closure pads short input to maxLength")
52+
func padsShortInput() {
53+
let maxLength = 77
54+
let tokenize: @Sendable (String) -> [Int32] = { _ in
55+
var ids = [Int32](1...10)
56+
if ids.count < maxLength {
57+
ids += [Int32](repeating: 0, count: maxLength - ids.count)
58+
}
59+
return ids
60+
}
61+
let ids = tokenize("short prompt")
62+
#expect(ids.count == 77)
63+
#expect(ids.last == 0)
64+
}
65+
66+
@Test("Tokenize closure truncates long input to maxLength")
67+
func truncatesLongInput() {
68+
let maxLength = 77
69+
let tokenize: @Sendable (String) -> [Int32] = { _ in
70+
var ids = [Int32](1...100)
71+
if ids.count > maxLength {
72+
ids = Array(ids.prefix(maxLength))
73+
}
74+
return ids
75+
}
76+
let ids = tokenize("very long prompt that produces many tokens")
77+
#expect(ids.count == 77)
78+
#expect(ids[76] == 77)
79+
}
80+
}

0 commit comments

Comments
 (0)