bleam 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/cli.cjs +23 -4
  2. package/dist/cli.d.cts +2 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +23 -4
  5. package/dist/{ui-Ce-pnUUA.d.ts → ui-Bg11tvlc.d.ts} +7 -7
  6. package/dist/ui.d.ts +1 -1
  7. package/dist/window.d.ts +1 -1
  8. package/package.json +4 -1
  9. package/templates/image-generation/app/index.tsx +1 -1
  10. package/templates/native/.gitattributes +1 -0
  11. package/templates/native/App.tsx +26 -0
  12. package/templates/native/app.json +12 -0
  13. package/templates/native/index.ts +8 -0
  14. package/templates/native/ios/.xcode.env +11 -0
  15. package/templates/native/ios/Bleam/AI/Flux2/Configuration/Flux2Config.swift +288 -0
  16. package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryConfig.swift +233 -0
  17. package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryOptimizationConfig.swift +157 -0
  18. package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +425 -0
  19. package/templates/native/ios/Bleam/AI/Flux2/Configuration/QuantizationConfig.swift +138 -0
  20. package/templates/native/ios/Bleam/AI/Flux2/Configuration/TransformerRepoProfile.swift +59 -0
  21. package/templates/native/ios/Bleam/AI/Flux2/Configuration/VAEConfig.swift +134 -0
  22. package/templates/native/ios/Bleam/AI/Flux2/Flux2Core.swift +55 -0
  23. package/templates/native/ios/Bleam/AI/Flux2/Loading/WeightLoader.swift +1192 -0
  24. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +1282 -0
  25. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/LatentUtils.swift +191 -0
  26. package/templates/native/ios/Bleam/AI/Flux2/Scheduler/FlowMatchEulerScheduler.swift +279 -0
  27. package/templates/native/ios/Bleam/AI/Flux2/Text/KleinTextEncoder.swift +380 -0
  28. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Attention.swift +379 -0
  29. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Embeddings.swift +159 -0
  30. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FeedForward.swift +154 -0
  31. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FusedKernels.swift +198 -0
  32. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2KVCache.swift +36 -0
  33. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Modulation.swift +193 -0
  34. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2ParallelAttention.swift +389 -0
  35. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2RoPE.swift +304 -0
  36. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2SingleBlock.swift +290 -0
  37. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Transformer.swift +530 -0
  38. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2TransformerBlock.swift +362 -0
  39. package/templates/native/ios/Bleam/AI/Flux2/Utils/Flux2Debug.swift +101 -0
  40. package/templates/native/ios/Bleam/AI/Flux2/Utils/MLXCheckpoint.swift +118 -0
  41. package/templates/native/ios/Bleam/AI/Flux2/Utils/MemoryManager.swift +201 -0
  42. package/templates/native/ios/Bleam/AI/Flux2/VAE/AutoencoderKL.swift +476 -0
  43. package/templates/native/ios/Bleam/AI/Flux2/VAE/ResnetBlock.swift +316 -0
  44. package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEDecoder.swift +120 -0
  45. package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEEncoder.swift +136 -0
  46. package/templates/native/ios/Bleam/AppDelegate.swift +373 -0
  47. package/templates/native/ios/Bleam/Appearance.swift +62 -0
  48. package/templates/native/ios/Bleam/Bridging-Header.h +3 -0
  49. package/templates/native/ios/Bleam/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
  50. package/templates/native/ios/Bleam/Images.xcassets/Contents.json +6 -0
  51. package/templates/native/ios/Bleam/Info.plist +76 -0
  52. package/templates/native/ios/Bleam/PrivacyInfo.xcprivacy +48 -0
  53. package/templates/native/ios/Bleam/SceneDelegate.swift +58 -0
  54. package/templates/native/ios/Bleam/SplashScreen.storyboard +47 -0
  55. package/templates/native/ios/Bleam/Supporting/Expo.plist +6 -0
  56. package/templates/native/ios/Bleam/bleam.entitlements +10 -0
  57. package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +1146 -0
  58. package/templates/native/ios/Bleam.xcodeproj/xcshareddata/xcschemes/Bleam.xcscheme +88 -0
  59. package/templates/native/ios/GenerationService/GenerationService.entitlements +10 -0
  60. package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +591 -0
  61. package/templates/native/ios/GenerationService/Info.plist +31 -0
  62. package/templates/native/ios/GenerationService/main.swift +165 -0
  63. package/templates/native/ios/PlatformHelper/Info.plist +29 -0
  64. package/templates/native/ios/PlatformHelper/main.swift +335 -0
  65. package/templates/native/ios/Podfile +198 -0
  66. package/templates/native/ios/Podfile.lock +2284 -0
  67. package/templates/native/ios/Podfile.properties.json +5 -0
  68. package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +12 -0
  69. package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +124 -0
  70. package/templates/native/metro.config.js +6 -0
  71. package/templates/native/modules/bleam-runtime/BleamRuntime.podspec +14 -0
  72. package/templates/native/modules/bleam-runtime/expo-module.config.json +8 -0
  73. package/templates/native/modules/bleam-runtime/ios/AIModule.swift +1258 -0
  74. package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +135 -0
  75. package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +313 -0
  76. package/templates/native/modules/bleam-runtime/package.json +5 -0
  77. package/templates/native/package.json +39 -0
  78. package/templates/native/scripts/build/mlx-frameworks.ts +421 -0
  79. package/templates/native/scripts/start/index.ts +474 -0
  80. package/templates/native/scripts/start/known-failures.ts +20 -0
  81. package/templates/native/scripts/start/loader.ts +76 -0
  82. package/templates/native/scripts/start/native-state.ts +242 -0
  83. package/templates/native/scripts/start/package.json +4 -0
  84. package/templates/native/scripts/start/swift-packages.ts +414 -0
  85. package/templates/native/scripts/start/xcode-formatter.ts +106 -0
  86. package/templates/native/tsconfig.json +14 -0
  87. package/templates/native/yarn.lock +3749 -0
@@ -0,0 +1,380 @@
1
+ import Foundation
2
+ import MLX
3
+ import MLXNN
4
+
5
+ public struct Qwen3TextConfig: Codable, Sendable {
6
+ public let vocabSize: Int
7
+ public let hiddenSize: Int
8
+ public let intermediateSize: Int
9
+ public let numHiddenLayers: Int
10
+ public let numAttentionHeads: Int
11
+ public let numKeyValueHeads: Int
12
+ public let maxPositionEmbeddings: Int
13
+ public let rmsNormEps: Float
14
+ public let ropeTheta: Float
15
+ public let tieWordEmbeddings: Bool
16
+ public let hiddenAct: String
17
+ public let attentionBias: Bool
18
+ public let attentionDropout: Float
19
+ public let headDim: Int
20
+
21
+ enum CodingKeys: String, CodingKey {
22
+ case vocabSize = "vocab_size"
23
+ case hiddenSize = "hidden_size"
24
+ case intermediateSize = "intermediate_size"
25
+ case numHiddenLayers = "num_hidden_layers"
26
+ case numAttentionHeads = "num_attention_heads"
27
+ case numKeyValueHeads = "num_key_value_heads"
28
+ case maxPositionEmbeddings = "max_position_embeddings"
29
+ case rmsNormEps = "rms_norm_eps"
30
+ case ropeTheta = "rope_theta"
31
+ case tieWordEmbeddings = "tie_word_embeddings"
32
+ case hiddenAct = "hidden_act"
33
+ case attentionBias = "attention_bias"
34
+ case attentionDropout = "attention_dropout"
35
+ case headDim = "head_dim"
36
+ }
37
+
38
+ public init(from decoder: Decoder) throws {
39
+ let container = try decoder.container(keyedBy: CodingKeys.self)
40
+ vocabSize = try container.decode(Int.self, forKey: .vocabSize)
41
+ hiddenSize = try container.decode(Int.self, forKey: .hiddenSize)
42
+ intermediateSize = try container.decode(Int.self, forKey: .intermediateSize)
43
+ numHiddenLayers = try container.decode(Int.self, forKey: .numHiddenLayers)
44
+ numAttentionHeads = try container.decode(Int.self, forKey: .numAttentionHeads)
45
+ numKeyValueHeads = try container.decode(Int.self, forKey: .numKeyValueHeads)
46
+ maxPositionEmbeddings = try container.decode(Int.self, forKey: .maxPositionEmbeddings)
47
+ rmsNormEps = try container.decodeIfPresent(Float.self, forKey: .rmsNormEps) ?? 1e-6
48
+ ropeTheta = try container.decodeIfPresent(Float.self, forKey: .ropeTheta) ?? 1_000_000.0
49
+ tieWordEmbeddings = try container.decodeIfPresent(Bool.self, forKey: .tieWordEmbeddings) ?? true
50
+ hiddenAct = try container.decodeIfPresent(String.self, forKey: .hiddenAct) ?? "silu"
51
+ attentionBias = try container.decodeIfPresent(Bool.self, forKey: .attentionBias) ?? false
52
+ attentionDropout = try container.decodeIfPresent(Float.self, forKey: .attentionDropout) ?? 0.0
53
+ headDim = try container.decodeIfPresent(Int.self, forKey: .headDim) ?? (hiddenSize / numAttentionHeads)
54
+ }
55
+
56
+ public static func load(from url: URL) throws -> Qwen3TextConfig {
57
+ try JSONDecoder().decode(Qwen3TextConfig.self, from: Data(contentsOf: url))
58
+ }
59
+ }
60
+
61
+ private struct Qwen3QuantizationConfig: Codable {
62
+ let groupSize: Int
63
+ let bits: Int
64
+
65
+ enum CodingKeys: String, CodingKey {
66
+ case groupSize = "group_size"
67
+ case bits
68
+ }
69
+ }
70
+
71
+ private struct Qwen3ModelConfigWithQuantization: Codable {
72
+ let quantization: Qwen3QuantizationConfig?
73
+ }
74
+
75
+ private struct TextEncoderMemoryConfig: Sendable {
76
+ let evalFrequency: Int
77
+ let clearCacheOnEval: Bool
78
+
79
+ static let aggressive = TextEncoderMemoryConfig(evalFrequency: 4, clearCacheOnEval: true)
80
+ }
81
+
82
+ public class TextRMSNorm: Module, UnaryLayer {
83
+ var weight: MLXArray
84
+ let eps: Float
85
+
86
+ public init(dimensions: Int, eps: Float = 1e-5) {
87
+ self.weight = MLXArray.ones([dimensions])
88
+ self.eps = eps
89
+ super.init()
90
+ }
91
+
92
+ public func callAsFunction(_ x: MLXArray) -> MLXArray {
93
+ let variance = mean(x * x, axis: -1, keepDims: true)
94
+ return weight * x * rsqrt(variance + MLXArray([eps]))
95
+ }
96
+ }
97
+
98
+ private final class Qwen3RoPE: Module {
99
+ let dimensions: Int
100
+ let base: Float
101
+
102
+ init(dimensions: Int, base: Float) {
103
+ self.dimensions = dimensions
104
+ self.base = base
105
+ super.init()
106
+ }
107
+
108
+ func callAsFunction(_ x: MLXArray) -> MLXArray {
109
+ MLXFast.RoPE(x, dimensions: dimensions, traditional: false, base: base, scale: 1.0, offset: 0)
110
+ }
111
+ }
112
+
113
+ private final class Qwen3Attention: Module {
114
+ let hiddenSize: Int
115
+ let numHeads: Int
116
+ let numKVHeads: Int
117
+ let headDim: Int
118
+ let scale: Float
119
+
120
+ @ModuleInfo var q_proj: Linear
121
+ @ModuleInfo var k_proj: Linear
122
+ @ModuleInfo var v_proj: Linear
123
+ @ModuleInfo var o_proj: Linear
124
+ @ModuleInfo var q_norm: TextRMSNorm
125
+ @ModuleInfo var k_norm: TextRMSNorm
126
+ let rope: Qwen3RoPE
127
+
128
+ init(config: Qwen3TextConfig) {
129
+ hiddenSize = config.hiddenSize
130
+ numHeads = config.numAttentionHeads
131
+ numKVHeads = config.numKeyValueHeads
132
+ headDim = config.headDim
133
+ scale = 1.0 / sqrt(Float(headDim))
134
+ _q_proj = ModuleInfo(wrappedValue: Linear(hiddenSize, numHeads * headDim, bias: config.attentionBias))
135
+ _k_proj = ModuleInfo(wrappedValue: Linear(hiddenSize, numKVHeads * headDim, bias: config.attentionBias))
136
+ _v_proj = ModuleInfo(wrappedValue: Linear(hiddenSize, numKVHeads * headDim, bias: config.attentionBias))
137
+ _o_proj = ModuleInfo(wrappedValue: Linear(numHeads * headDim, hiddenSize, bias: config.attentionBias))
138
+ _q_norm = ModuleInfo(wrappedValue: TextRMSNorm(dimensions: headDim, eps: config.rmsNormEps))
139
+ _k_norm = ModuleInfo(wrappedValue: TextRMSNorm(dimensions: headDim, eps: config.rmsNormEps))
140
+ rope = Qwen3RoPE(dimensions: headDim, base: config.ropeTheta)
141
+ super.init()
142
+ }
143
+
144
+ func callAsFunction(_ hiddenStates: MLXArray, mask: MLXArray?) -> MLXArray {
145
+ let batchSize = hiddenStates.shape[0]
146
+ let seqLen = hiddenStates.shape[1]
147
+
148
+ var queries = q_proj(hiddenStates).reshaped([batchSize, seqLen, numHeads, headDim])
149
+ var keys = k_proj(hiddenStates).reshaped([batchSize, seqLen, numKVHeads, headDim])
150
+ var values = v_proj(hiddenStates).reshaped([batchSize, seqLen, numKVHeads, headDim])
151
+
152
+ queries = q_norm(queries).transposed(0, 2, 1, 3)
153
+ keys = k_norm(keys).transposed(0, 2, 1, 3)
154
+ values = values.transposed(0, 2, 1, 3)
155
+
156
+ queries = rope(queries)
157
+ keys = rope(keys)
158
+
159
+ let repeatFactor = numHeads / numKVHeads
160
+ if repeatFactor > 1 {
161
+ keys = MLX.broadcast(keys.expandedDimensions(axis: 2), to: [batchSize, numKVHeads, repeatFactor, seqLen, headDim])
162
+ .reshaped([batchSize, numHeads, seqLen, headDim])
163
+ values = MLX.broadcast(values.expandedDimensions(axis: 2), to: [batchSize, numKVHeads, repeatFactor, seqLen, headDim])
164
+ .reshaped([batchSize, numHeads, seqLen, headDim])
165
+ }
166
+
167
+ let output = MLXFast.scaledDotProductAttention(
168
+ queries: queries,
169
+ keys: keys,
170
+ values: values,
171
+ scale: scale,
172
+ mask: mask
173
+ )
174
+
175
+ return o_proj(output.transposed(0, 2, 1, 3).reshaped([batchSize, seqLen, numHeads * headDim]))
176
+ }
177
+ }
178
+
179
+ private final class Qwen3MLP: Module, @unchecked Sendable {
180
+ @ModuleInfo var gate_proj: Linear
181
+ @ModuleInfo var up_proj: Linear
182
+ @ModuleInfo var down_proj: Linear
183
+
184
+ nonisolated(unsafe) private static let compiledGate: (MLXArray, MLXArray) -> MLXArray = compile { gate, up in
185
+ silu(gate) * up
186
+ }
187
+
188
+ init(config: Qwen3TextConfig) {
189
+ _gate_proj = ModuleInfo(wrappedValue: Linear(config.hiddenSize, config.intermediateSize, bias: false))
190
+ _up_proj = ModuleInfo(wrappedValue: Linear(config.hiddenSize, config.intermediateSize, bias: false))
191
+ _down_proj = ModuleInfo(wrappedValue: Linear(config.intermediateSize, config.hiddenSize, bias: false))
192
+ super.init()
193
+ }
194
+
195
+ func callAsFunction(_ x: MLXArray) -> MLXArray {
196
+ down_proj(Self.compiledGate(gate_proj(x), up_proj(x)))
197
+ }
198
+ }
199
+
200
+ private final class Qwen3DecoderLayer: Module {
201
+ let self_attn: Qwen3Attention
202
+ let mlp: Qwen3MLP
203
+ let input_layernorm: TextRMSNorm
204
+ let post_attention_layernorm: TextRMSNorm
205
+
206
+ init(config: Qwen3TextConfig) {
207
+ self_attn = Qwen3Attention(config: config)
208
+ mlp = Qwen3MLP(config: config)
209
+ input_layernorm = TextRMSNorm(dimensions: config.hiddenSize, eps: config.rmsNormEps)
210
+ post_attention_layernorm = TextRMSNorm(dimensions: config.hiddenSize, eps: config.rmsNormEps)
211
+ super.init()
212
+ }
213
+
214
+ func callAsFunction(_ hiddenStates: MLXArray, mask: MLXArray?) -> MLXArray {
215
+ var hidden = hiddenStates + self_attn(input_layernorm(hiddenStates), mask: mask)
216
+ hidden = hidden + mlp(post_attention_layernorm(hidden))
217
+ return hidden
218
+ }
219
+ }
220
+
221
+ private final class Qwen3Model: Module {
222
+ let config: Qwen3TextConfig
223
+ var memoryConfig: TextEncoderMemoryConfig = .aggressive
224
+
225
+ @ModuleInfo var embed_tokens: Embedding
226
+ var layers: [Qwen3DecoderLayer]
227
+ let norm: TextRMSNorm
228
+
229
+ init(config: Qwen3TextConfig) {
230
+ self.config = config
231
+ _embed_tokens = ModuleInfo(wrappedValue: Embedding(embeddingCount: config.vocabSize, dimensions: config.hiddenSize))
232
+ layers = (0..<config.numHiddenLayers).map { _ in Qwen3DecoderLayer(config: config) }
233
+ norm = TextRMSNorm(dimensions: config.hiddenSize, eps: config.rmsNormEps)
234
+ super.init()
235
+ }
236
+
237
+ func forwardWithHiddenStates(
238
+ _ inputIds: MLXArray,
239
+ layerIndices: [Int],
240
+ attentionMask: MLXArray
241
+ ) -> [Int: MLXArray] {
242
+ var hiddenStates = embed_tokens(inputIds)
243
+ eval(hiddenStates)
244
+
245
+ let mask = createCausalMask(seqLen: inputIds.shape[1], attentionMask: attentionMask)
246
+ eval(mask)
247
+
248
+ let layerSet = Set(layerIndices)
249
+ var extractedStates: [Int: MLXArray] = [:]
250
+
251
+ for (index, layer) in layers.enumerated() {
252
+ hiddenStates = layer(hiddenStates, mask: mask)
253
+ let layerIndex = index + 1
254
+ let shouldEval = layerSet.contains(layerIndex) || (index + 1) % memoryConfig.evalFrequency == 0
255
+
256
+ if shouldEval {
257
+ eval(hiddenStates)
258
+ if memoryConfig.clearCacheOnEval {
259
+ MLX.Memory.clearCache()
260
+ }
261
+ }
262
+
263
+ if layerSet.contains(layerIndex) {
264
+ extractedStates[layerIndex] = hiddenStates
265
+ }
266
+ }
267
+
268
+ if layerSet.contains(layers.count) {
269
+ let normalizedStates = norm(hiddenStates)
270
+ eval(normalizedStates)
271
+ extractedStates[layers.count] = normalizedStates
272
+ }
273
+
274
+ MLX.Memory.clearCache()
275
+ return extractedStates
276
+ }
277
+
278
+ private func createCausalMask(seqLen: Int, attentionMask: MLXArray) -> MLXArray {
279
+ let rowIndices = MLXArray.arange(seqLen, dtype: .float32).expandedDimensions(axis: 1)
280
+ let colIndices = MLXArray.arange(seqLen, dtype: .float32).expandedDimensions(axis: 0)
281
+ let causalMask = MLX.where(
282
+ colIndices .<= rowIndices,
283
+ MLXArray(Float(0.0)),
284
+ MLXArray(-Float.infinity)
285
+ ).reshaped([1, 1, seqLen, seqLen])
286
+
287
+ let paddingMask = MLX.where(
288
+ attentionMask .== Int32(1),
289
+ MLXArray(Float(0.0)),
290
+ MLXArray(Float(-1e9))
291
+ ).reshaped([attentionMask.shape[0], 1, 1, attentionMask.shape[1]])
292
+
293
+ return causalMask + paddingMask
294
+ }
295
+ }
296
+
297
+ private final class Qwen3TextEncoderModel: Module {
298
+ var model: Qwen3Model
299
+
300
+ init(config: Qwen3TextConfig) {
301
+ model = Qwen3Model(config: config)
302
+ super.init()
303
+ }
304
+ }
305
+
306
+ public final class KleinTextEncoder: @unchecked Sendable {
307
+ private static let hiddenStateLayers = [9, 18, 27]
308
+
309
+ private let modelPath: URL
310
+ private var model: Qwen3TextEncoderModel?
311
+
312
+ public init(modelPath: URL) {
313
+ self.modelPath = modelPath
314
+ }
315
+
316
+ public func encode(inputIds: [Int], attentionMask: [Int]) throws -> MLXArray {
317
+ guard inputIds.count == attentionMask.count else {
318
+ throw Flux2Error.invalidConfiguration("inputIds and attentionMask must have the same length")
319
+ }
320
+
321
+ let model = try loadModel()
322
+ let ids = MLXArray(inputIds.map(Int32.init)).reshaped([1, inputIds.count])
323
+ let mask = MLXArray(attentionMask.map(Int32.init)).reshaped([1, attentionMask.count])
324
+ let extractedStates = model.model.forwardWithHiddenStates(
325
+ ids,
326
+ layerIndices: Self.hiddenStateLayers,
327
+ attentionMask: mask
328
+ )
329
+
330
+ let states = try Self.hiddenStateLayers.map { layerIndex -> MLXArray in
331
+ guard let state = extractedStates[layerIndex] else {
332
+ throw Flux2Error.invalidConfiguration("Qwen3 text encoder did not return hidden state layer \(layerIndex)")
333
+ }
334
+ return state
335
+ }
336
+
337
+ let embeddings = concatenated(states, axis: -1)
338
+ eval(embeddings)
339
+
340
+ // The text encoder is only needed to materialize prompt embeddings.
341
+ // Drop its 4B weights immediately so Flux image generation can load
342
+ // the transformer without keeping both models resident.
343
+ self.model = nil
344
+ MLX.Memory.clearCache()
345
+ return embeddings
346
+ }
347
+
348
+ private func loadModel() throws -> Qwen3TextEncoderModel {
349
+ if let model {
350
+ return model
351
+ }
352
+
353
+ let configURL = modelPath.appendingPathComponent("config.json")
354
+ let config = try Qwen3TextConfig.load(from: configURL)
355
+ let loaded = Qwen3TextEncoderModel(config: config)
356
+
357
+ let configData = try Data(contentsOf: configURL)
358
+ if let quantization = try? JSONDecoder().decode(Qwen3ModelConfigWithQuantization.self, from: configData).quantization {
359
+ quantize(model: loaded, groupSize: quantization.groupSize, bits: quantization.bits)
360
+ }
361
+
362
+ var weights: [String: MLXArray] = [:]
363
+ for file in try FileManager.default.contentsOfDirectory(atPath: modelPath.path).filter({ $0.hasSuffix(".safetensors") }).sorted() {
364
+ let tensors = try loadArrays(url: modelPath.appendingPathComponent(file))
365
+ for (key, value) in tensors {
366
+ weights[key] = value
367
+ }
368
+ }
369
+
370
+ guard !weights.isEmpty else {
371
+ throw Flux2Error.modelNotLoaded("Qwen3 text encoder weights not found")
372
+ }
373
+
374
+ try loaded.update(parameters: ModuleParameters.unflattened(weights), verify: .none)
375
+ eval(loaded)
376
+ MLX.Memory.clearCache()
377
+ model = loaded
378
+ return loaded
379
+ }
380
+ }