bleam 0.0.8 → 0.0.10

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 (96) hide show
  1. package/dist/cli.cjs +269 -22
  2. package/dist/cli.d.cts +32 -2
  3. package/dist/cli.d.ts +32 -2
  4. package/dist/cli.js +270 -23
  5. package/dist/platform.d.cts +2 -0
  6. package/dist/platform.d.ts +2 -0
  7. package/dist/{ui-1WepaMS4.d.cts → ui-Bg11tvlc.d.ts} +1 -1
  8. package/dist/{ui-Ce-pnUUA.d.ts → ui-Dd7SXdbg.d.cts} +7 -7
  9. package/dist/ui.d.cts +1 -1
  10. package/dist/ui.d.ts +1 -1
  11. package/dist/window.d.cts +1 -1
  12. package/dist/window.d.ts +1 -1
  13. package/package.json +5 -1
  14. package/templates/image-generation/app/index.tsx +1 -1
  15. package/templates/native/.gitattributes +1 -0
  16. package/templates/native/App.tsx +26 -0
  17. package/templates/native/app.json +12 -0
  18. package/templates/native/index.ts +8 -0
  19. package/templates/native/ios/.xcode.env +11 -0
  20. package/templates/native/ios/Bleam/AI/Flux2/Configuration/Flux2Config.swift +288 -0
  21. package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryConfig.swift +233 -0
  22. package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryOptimizationConfig.swift +157 -0
  23. package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +425 -0
  24. package/templates/native/ios/Bleam/AI/Flux2/Configuration/QuantizationConfig.swift +138 -0
  25. package/templates/native/ios/Bleam/AI/Flux2/Configuration/TransformerRepoProfile.swift +59 -0
  26. package/templates/native/ios/Bleam/AI/Flux2/Configuration/VAEConfig.swift +134 -0
  27. package/templates/native/ios/Bleam/AI/Flux2/Flux2Core.swift +55 -0
  28. package/templates/native/ios/Bleam/AI/Flux2/Loading/WeightLoader.swift +1192 -0
  29. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +1282 -0
  30. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/LatentUtils.swift +191 -0
  31. package/templates/native/ios/Bleam/AI/Flux2/Scheduler/FlowMatchEulerScheduler.swift +279 -0
  32. package/templates/native/ios/Bleam/AI/Flux2/Text/KleinTextEncoder.swift +380 -0
  33. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Attention.swift +379 -0
  34. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Embeddings.swift +159 -0
  35. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FeedForward.swift +154 -0
  36. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FusedKernels.swift +198 -0
  37. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2KVCache.swift +36 -0
  38. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Modulation.swift +193 -0
  39. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2ParallelAttention.swift +389 -0
  40. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2RoPE.swift +304 -0
  41. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2SingleBlock.swift +290 -0
  42. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Transformer.swift +530 -0
  43. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2TransformerBlock.swift +362 -0
  44. package/templates/native/ios/Bleam/AI/Flux2/Utils/Flux2Debug.swift +101 -0
  45. package/templates/native/ios/Bleam/AI/Flux2/Utils/MLXCheckpoint.swift +118 -0
  46. package/templates/native/ios/Bleam/AI/Flux2/Utils/MemoryManager.swift +201 -0
  47. package/templates/native/ios/Bleam/AI/Flux2/VAE/AutoencoderKL.swift +476 -0
  48. package/templates/native/ios/Bleam/AI/Flux2/VAE/ResnetBlock.swift +316 -0
  49. package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEDecoder.swift +120 -0
  50. package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEEncoder.swift +136 -0
  51. package/templates/native/ios/Bleam/AppDelegate.swift +482 -0
  52. package/templates/native/ios/Bleam/Appearance.swift +62 -0
  53. package/templates/native/ios/Bleam/Bridging-Header.h +3 -0
  54. package/templates/native/ios/Bleam/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
  55. package/templates/native/ios/Bleam/Images.xcassets/Contents.json +6 -0
  56. package/templates/native/ios/Bleam/Info.plist +76 -0
  57. package/templates/native/ios/Bleam/PrivacyInfo.xcprivacy +48 -0
  58. package/templates/native/ios/Bleam/SceneDelegate.swift +58 -0
  59. package/templates/native/ios/Bleam/SplashScreen.storyboard +47 -0
  60. package/templates/native/ios/Bleam/Supporting/Expo.plist +6 -0
  61. package/templates/native/ios/Bleam/bleam.entitlements +10 -0
  62. package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +1147 -0
  63. package/templates/native/ios/Bleam.xcodeproj/xcshareddata/xcschemes/Bleam.xcscheme +88 -0
  64. package/templates/native/ios/GenerationService/GenerationService.entitlements +10 -0
  65. package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +591 -0
  66. package/templates/native/ios/GenerationService/Info.plist +31 -0
  67. package/templates/native/ios/GenerationService/main.swift +165 -0
  68. package/templates/native/ios/PlatformHelper/main.swift +726 -0
  69. package/templates/native/ios/Podfile +198 -0
  70. package/templates/native/ios/Podfile.lock +2284 -0
  71. package/templates/native/ios/Podfile.properties.json +5 -0
  72. package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +12 -0
  73. package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +124 -0
  74. package/templates/native/metro.config.js +6 -0
  75. package/templates/native/modules/bleam-runtime/BleamRuntime.podspec +14 -0
  76. package/templates/native/modules/bleam-runtime/expo-module.config.json +8 -0
  77. package/templates/native/modules/bleam-runtime/ios/AIModule.swift +1258 -0
  78. package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +135 -0
  79. package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +329 -0
  80. package/templates/native/modules/bleam-runtime/package.json +5 -0
  81. package/templates/native/package.json +39 -0
  82. package/templates/native/scripts/build/mlx-frameworks.ts +421 -0
  83. package/templates/native/scripts/start/index.ts +474 -0
  84. package/templates/native/scripts/start/known-failures.ts +20 -0
  85. package/templates/native/scripts/start/loader.ts +76 -0
  86. package/templates/native/scripts/start/native-state.ts +242 -0
  87. package/templates/native/scripts/start/package.json +4 -0
  88. package/templates/native/scripts/start/swift-packages.ts +414 -0
  89. package/templates/native/scripts/start/xcode-formatter.ts +106 -0
  90. package/templates/native/tsconfig.json +14 -0
  91. package/templates/native/yarn.lock +3749 -0
  92. package/templates/updates/README.md +272 -0
  93. package/templates/updates/src/index.ts +385 -0
  94. package/templates/updates/src/schema.ts +385 -0
  95. package/templates/updates/tsconfig.json +12 -0
  96. package/templates/updates/wrangler.jsonc +19 -0
@@ -0,0 +1,379 @@
1
+ // Flux2Attention.swift - Joint Attention for Flux.2 Double-Stream Blocks
2
+ // Copyright 2025 Vincent Gourbin
3
+
4
+ import Foundation
5
+ import MLX
6
+ import MLXNN
7
+
8
+ /// RMS Normalization (Root Mean Square Layer Normalization)
9
+ /// Optimized using MLXFast.rmsNorm for better performance
10
+ public class FluxRMSNorm: Module, @unchecked Sendable {
11
+ let dim: Int
12
+ let eps: Float
13
+ let weight: MLXArray
14
+
15
+ public init(dim: Int, eps: Float = 1e-6) {
16
+ self.dim = dim
17
+ self.eps = eps
18
+ self.weight = MLXArray.ones([dim])
19
+ }
20
+
21
+ public func callAsFunction(_ x: MLXArray) -> MLXArray {
22
+ // Use optimized MLXFast implementation
23
+ return MLXFast.rmsNorm(x, weight: weight, eps: eps)
24
+ }
25
+ }
26
+
27
+ /// Joint Attention for Double-Stream Blocks
28
+ ///
29
+ /// Processes both image hidden states and text encoder hidden states together.
30
+ /// Uses separate Q/K/V projections for each modality, then performs joint attention.
31
+ public class Flux2Attention: Module, @unchecked Sendable {
32
+ let dim: Int
33
+ let numHeads: Int
34
+ let headDim: Int
35
+
36
+ // Image hidden states projections (@ModuleInfo for LoRA injection via update)
37
+ @ModuleInfo var toQ: Linear
38
+ @ModuleInfo var toK: Linear
39
+ @ModuleInfo var toV: Linear
40
+
41
+ // Text encoder hidden states projections
42
+ @ModuleInfo var addQProj: Linear
43
+ @ModuleInfo var addKProj: Linear
44
+ @ModuleInfo var addVProj: Linear
45
+
46
+ // QK normalization (FluxRMSNorm)
47
+ let normQ: FluxRMSNorm
48
+ let normK: FluxRMSNorm
49
+ let normAddedQ: FluxRMSNorm
50
+ let normAddedK: FluxRMSNorm
51
+
52
+ // Output projections
53
+ @ModuleInfo var toOut: Linear
54
+ @ModuleInfo var toAddOut: Linear
55
+
56
+ /// Initialize Joint Attention
57
+ /// - Parameters:
58
+ /// - dim: Model dimension (6144 for Flux.2)
59
+ /// - numHeads: Number of attention heads (48)
60
+ /// - headDim: Dimension per head (128)
61
+ /// - contextDim: Dimension of text encoder hidden states (same as dim after projection)
62
+ public init(
63
+ dim: Int,
64
+ numHeads: Int,
65
+ headDim: Int,
66
+ contextDim: Int? = nil
67
+ ) {
68
+ self.dim = dim
69
+ self.numHeads = numHeads
70
+ self.headDim = headDim
71
+
72
+ let innerDim = numHeads * headDim
73
+ let ctxDim = contextDim ?? dim
74
+
75
+ // Image projections (no bias to match checkpoint)
76
+ self._toQ.wrappedValue = Linear(dim, innerDim, bias: false)
77
+ self._toK.wrappedValue = Linear(dim, innerDim, bias: false)
78
+ self._toV.wrappedValue = Linear(dim, innerDim, bias: false)
79
+
80
+ // Text projections (no bias to match checkpoint)
81
+ self._addQProj.wrappedValue = Linear(ctxDim, innerDim, bias: false)
82
+ self._addKProj.wrappedValue = Linear(ctxDim, innerDim, bias: false)
83
+ self._addVProj.wrappedValue = Linear(ctxDim, innerDim, bias: false)
84
+
85
+ // QK normalization
86
+ self.normQ = FluxRMSNorm(dim: headDim)
87
+ self.normK = FluxRMSNorm(dim: headDim)
88
+ self.normAddedQ = FluxRMSNorm(dim: headDim)
89
+ self.normAddedK = FluxRMSNorm(dim: headDim)
90
+
91
+ // Output projections (no bias to match checkpoint)
92
+ self._toOut.wrappedValue = Linear(innerDim, dim, bias: false)
93
+ self._toAddOut.wrappedValue = Linear(innerDim, ctxDim, bias: false)
94
+ }
95
+
96
+ /// Forward pass for joint attention
97
+ /// - Parameters:
98
+ /// - hiddenStates: Image hidden states [B, S_img, dim]
99
+ /// - encoderHiddenStates: Text encoder hidden states [B, S_txt, dim]
100
+ /// - rotaryEmb: Optional RoPE embeddings (cos, sin)
101
+ /// - Returns: Tuple of updated (image hidden states, text hidden states)
102
+ public func callAsFunction(
103
+ hiddenStates: MLXArray,
104
+ encoderHiddenStates: MLXArray,
105
+ rotaryEmb: (cos: MLXArray, sin: MLXArray)? = nil
106
+ ) -> (hiddenStates: MLXArray, encoderHiddenStates: MLXArray) {
107
+ Flux2Debug.verbose("Flux2Attention input - img: \(hiddenStates.shape), txt: \(encoderHiddenStates.shape)")
108
+
109
+ let batchSize = hiddenStates.shape[0]
110
+ let seqLenImg = hiddenStates.shape[1]
111
+ let seqLenTxt = encoderHiddenStates.shape[1]
112
+
113
+ // Project image to Q, K, V
114
+ var q = toQ(hiddenStates)
115
+ var k = toK(hiddenStates)
116
+ let v = toV(hiddenStates)
117
+ Flux2Debug.verbose("Image Q/K/V projected: q=\(q.shape), k=\(k.shape), v=\(v.shape)")
118
+
119
+ // Project text to Q, K, V
120
+ var addedQ = addQProj(encoderHiddenStates)
121
+ var addedK = addKProj(encoderHiddenStates)
122
+ let addedV = addVProj(encoderHiddenStates)
123
+ Flux2Debug.verbose("Text Q/K/V projected: addedQ=\(addedQ.shape), addedK=\(addedK.shape), addedV=\(addedV.shape)")
124
+
125
+ // Reshape for multi-head attention: [B, S, H*D] -> [B, H, S, D]
126
+ q = reshapeForAttention(q, batchSize: batchSize, seqLen: seqLenImg)
127
+ k = reshapeForAttention(k, batchSize: batchSize, seqLen: seqLenImg)
128
+ let vReshaped = reshapeForAttention(v, batchSize: batchSize, seqLen: seqLenImg)
129
+ Flux2Debug.verbose("Image after reshape: q=\(q.shape), k=\(k.shape), v=\(vReshaped.shape)")
130
+
131
+ addedQ = reshapeForAttention(addedQ, batchSize: batchSize, seqLen: seqLenTxt)
132
+ addedK = reshapeForAttention(addedK, batchSize: batchSize, seqLen: seqLenTxt)
133
+ let addedVReshaped = reshapeForAttention(addedV, batchSize: batchSize, seqLen: seqLenTxt)
134
+ Flux2Debug.verbose("Text after reshape: addedQ=\(addedQ.shape), addedK=\(addedK.shape), addedV=\(addedVReshaped.shape)")
135
+
136
+ // Apply QK normalization
137
+ q = normQ(q)
138
+ k = normK(k)
139
+ addedQ = normAddedQ(addedQ)
140
+ addedK = normAddedK(addedK)
141
+ Flux2Debug.verbose("After QK norm: q=\(q.shape), addedQ=\(addedQ.shape)")
142
+
143
+ // Apply RoPE if provided
144
+ if let rope = rotaryEmb {
145
+ // Split RoPE for text and image portions
146
+ // Note: combined IDs are [txtIds, imgIds], so text comes first
147
+ let txtCos = rope.cos[0..<seqLenTxt]
148
+ let txtSin = rope.sin[0..<seqLenTxt]
149
+ let imgCos = rope.cos[seqLenTxt..<(seqLenTxt + seqLenImg)]
150
+ let imgSin = rope.sin[seqLenTxt..<(seqLenTxt + seqLenImg)]
151
+
152
+ Flux2Debug.verbose("RoPE split - txtCos: \(txtCos.shape), imgCos: \(imgCos.shape)")
153
+
154
+ (q, k) = applyRoPE(q: q, k: k, cos: imgCos, sin: imgSin)
155
+ (addedQ, addedK) = applyRoPE(q: addedQ, k: addedK, cos: txtCos, sin: txtSin)
156
+ Flux2Debug.verbose("After RoPE: q=\(q.shape), addedQ=\(addedQ.shape)")
157
+ }
158
+
159
+ // Concatenate image and text for joint attention
160
+ let concatQ = concatenated([addedQ, q], axis: 2) // [B, H, S_txt+S_img, D]
161
+ let concatK = concatenated([addedK, k], axis: 2)
162
+ let concatV = concatenated([addedVReshaped, vReshaped], axis: 2)
163
+ Flux2Debug.verbose("Concatenated for attention: Q=\(concatQ.shape), K=\(concatK.shape), V=\(concatV.shape)")
164
+
165
+ // Compute attention using MLXFast scaled dot product attention
166
+ Flux2Debug.verbose("Calling scaledDotProductAttention...")
167
+ let attnOutput = MLXFast.scaledDotProductAttention(
168
+ queries: concatQ,
169
+ keys: concatK,
170
+ values: concatV,
171
+ scale: Float(1.0 / sqrt(Float(headDim))),
172
+ mask: nil
173
+ )
174
+ Flux2Debug.verbose("Attention output: \(attnOutput.shape)")
175
+
176
+ // Split back into text and image portions
177
+ let txtAttnOutput = attnOutput[0..., 0..., 0..<seqLenTxt, 0...]
178
+ let imgAttnOutput = attnOutput[0..., 0..., seqLenTxt..., 0...]
179
+ Flux2Debug.verbose("Split attention: txt=\(txtAttnOutput.shape), img=\(imgAttnOutput.shape)")
180
+
181
+ // Reshape back: [B, H, S, D] -> [B, S, H*D]
182
+ let imgOut = reshapeFromAttention(imgAttnOutput, batchSize: batchSize, seqLen: seqLenImg)
183
+ let txtOut = reshapeFromAttention(txtAttnOutput, batchSize: batchSize, seqLen: seqLenTxt)
184
+ Flux2Debug.verbose("After reshape from attn: img=\(imgOut.shape), txt=\(txtOut.shape)")
185
+
186
+ // Project outputs
187
+ let hiddenStatesOut = toOut(imgOut)
188
+ let encoderHiddenStatesOut = toAddOut(txtOut)
189
+ Flux2Debug.verbose("Final outputs: img=\(hiddenStatesOut.shape), txt=\(encoderHiddenStatesOut.shape)")
190
+
191
+ return (hiddenStates: hiddenStatesOut, encoderHiddenStates: encoderHiddenStatesOut)
192
+ }
193
+
194
+ // MARK: - Helper Functions
195
+
196
+ /// Forward pass with cached text K/V (for KV cache acceleration)
197
+ /// - Parameters:
198
+ /// - hiddenStates: Image hidden states [B, S_img, dim]
199
+ /// - encoderHiddenStates: Text encoder hidden states [B, S_txt, dim] (used only for Q projection)
200
+ /// - cachedTextKV: Pre-computed text K and V [B, H, S_txt, D]
201
+ /// - rotaryEmb: Optional RoPE embeddings (cos, sin) - only image portion needed
202
+ /// - Returns: Tuple of updated (image hidden states, text hidden states)
203
+ public func callAsFunction(
204
+ hiddenStates: MLXArray,
205
+ encoderHiddenStates: MLXArray,
206
+ cachedTextKV: (k: MLXArray, v: MLXArray),
207
+ rotaryEmb: (cos: MLXArray, sin: MLXArray)? = nil
208
+ ) -> (hiddenStates: MLXArray, encoderHiddenStates: MLXArray) {
209
+ let batchSize = hiddenStates.shape[0]
210
+ let seqLenImg = hiddenStates.shape[1]
211
+ let seqLenTxt = cachedTextKV.k.shape[2]
212
+
213
+ // Project image to Q, K, V
214
+ var k = toK(hiddenStates)
215
+ let v = toV(hiddenStates)
216
+
217
+ // Project text to Q only (K/V are cached)
218
+ var addedQ = addQProj(encoderHiddenStates)
219
+
220
+ // Reshape for multi-head attention
221
+ var qImg = toQ(hiddenStates)
222
+ qImg = reshapeForAttention(qImg, batchSize: batchSize, seqLen: seqLenImg)
223
+ k = reshapeForAttention(k, batchSize: batchSize, seqLen: seqLenImg)
224
+ let vReshaped = reshapeForAttention(v, batchSize: batchSize, seqLen: seqLenImg)
225
+
226
+ addedQ = reshapeForAttention(addedQ, batchSize: batchSize, seqLen: seqLenTxt)
227
+
228
+ // Apply QK normalization
229
+ qImg = normQ(qImg)
230
+ k = normK(k)
231
+ addedQ = normAddedQ(addedQ)
232
+
233
+ // Apply RoPE to image Q/K only (text doesn't need it since we're not recomputing its K)
234
+ if let rope = rotaryEmb {
235
+ let imgCos = rope.cos[seqLenTxt..<(seqLenTxt + seqLenImg)]
236
+ let imgSin = rope.sin[seqLenTxt..<(seqLenTxt + seqLenImg)]
237
+ (qImg, k) = applyRoPE(q: qImg, k: k, cos: imgCos, sin: imgSin)
238
+ }
239
+
240
+ // Concatenate cached text K/V with image K/V for joint attention
241
+ let concatK = concatenated([cachedTextKV.k, k], axis: 2)
242
+ let concatV = concatenated([cachedTextKV.v, vReshaped], axis: 2)
243
+
244
+ // Compute attention
245
+ let attnOutput = MLXFast.scaledDotProductAttention(
246
+ queries: concatenated([addedQ, qImg], axis: 2),
247
+ keys: concatK,
248
+ values: concatV,
249
+ scale: Float(1.0 / sqrt(Float(headDim))),
250
+ mask: nil
251
+ )
252
+
253
+ // Split back into text and image portions
254
+ let txtAttnOutput = attnOutput[0..., 0..., 0..<seqLenTxt, 0...]
255
+ let imgAttnOutput = attnOutput[0..., 0..., seqLenTxt..., 0...]
256
+
257
+ // Reshape back
258
+ let imgOut = reshapeFromAttention(imgAttnOutput, batchSize: batchSize, seqLen: seqLenImg)
259
+ let txtOut = reshapeFromAttention(txtAttnOutput, batchSize: batchSize, seqLen: seqLenTxt)
260
+
261
+ // Project outputs
262
+ let hiddenStatesOut = toOut(imgOut)
263
+ let encoderHiddenStatesOut = toAddOut(txtOut)
264
+
265
+ return (hiddenStates: hiddenStatesOut, encoderHiddenStates: encoderHiddenStatesOut)
266
+ }
267
+
268
+ /// Compute K and V for text tokens only (for KV cache building)
269
+ /// - Parameters:
270
+ /// - encoderHiddenStates: Normalized text hidden states [B, S_txt, dim]
271
+ /// - batchSize: Batch size
272
+ /// - Returns: (k, v) each [B, H, S_txt, D]
273
+ public func computeTextKV(
274
+ encoderHiddenStates: MLXArray,
275
+ batchSize: Int
276
+ ) -> (k: MLXArray, v: MLXArray) {
277
+ let seqLenTxt = encoderHiddenStates.shape[1]
278
+
279
+ var addedK = addKProj(encoderHiddenStates)
280
+ let addedV = addVProj(encoderHiddenStates)
281
+
282
+ addedK = reshapeForAttention(addedK, batchSize: batchSize, seqLen: seqLenTxt)
283
+ let addedVReshaped = reshapeForAttention(addedV, batchSize: batchSize, seqLen: seqLenTxt)
284
+
285
+ addedK = normAddedK(addedK)
286
+
287
+ return (k: addedK, v: addedVReshaped)
288
+ }
289
+
290
+ /// Self-attention on text tokens only (for KV cache building)
291
+ /// - Parameter encoderHiddenStates: Normalized text hidden states [B, S_txt, dim]
292
+ /// - Returns: Text attention output [B, S_txt, dim]
293
+ public func forwardTextSelfAttention(
294
+ encoderHiddenStates: MLXArray
295
+ ) -> MLXArray {
296
+ let batchSize = encoderHiddenStates.shape[0]
297
+ let seqLenTxt = encoderHiddenStates.shape[1]
298
+
299
+ var addedQ = addQProj(encoderHiddenStates)
300
+ var addedK = addKProj(encoderHiddenStates)
301
+ let addedV = addVProj(encoderHiddenStates)
302
+
303
+ addedQ = reshapeForAttention(addedQ, batchSize: batchSize, seqLen: seqLenTxt)
304
+ addedK = reshapeForAttention(addedK, batchSize: batchSize, seqLen: seqLenTxt)
305
+ let addedVReshaped = reshapeForAttention(addedV, batchSize: batchSize, seqLen: seqLenTxt)
306
+
307
+ addedQ = normAddedQ(addedQ)
308
+ addedK = normAddedK(addedK)
309
+
310
+ let attnOutput = MLXFast.scaledDotProductAttention(
311
+ queries: addedQ,
312
+ keys: addedK,
313
+ values: addedVReshaped,
314
+ scale: Float(1.0 / sqrt(Float(headDim))),
315
+ mask: nil
316
+ )
317
+
318
+ let txtOut = reshapeFromAttention(attnOutput, batchSize: batchSize, seqLen: seqLenTxt)
319
+ return toAddOut(txtOut)
320
+ }
321
+
322
+ // MARK: - Helper Functions
323
+
324
+ /// Reshape tensor for multi-head attention
325
+ /// [B, S, H*D] -> [B, H, S, D]
326
+ private func reshapeForAttention(_ x: MLXArray, batchSize: Int, seqLen: Int) -> MLXArray {
327
+ x.reshaped([batchSize, seqLen, numHeads, headDim])
328
+ .transposed(0, 2, 1, 3)
329
+ }
330
+
331
+ /// Reshape tensor from multi-head attention
332
+ /// [B, H, S, D] -> [B, S, H*D]
333
+ private func reshapeFromAttention(_ x: MLXArray, batchSize: Int, seqLen: Int) -> MLXArray {
334
+ x.transposed(0, 2, 1, 3)
335
+ .reshaped([batchSize, seqLen, numHeads * headDim])
336
+ }
337
+
338
+ /// Apply rotary position embeddings
339
+ private func applyRoPE(
340
+ q: MLXArray,
341
+ k: MLXArray,
342
+ cos: MLXArray,
343
+ sin: MLXArray
344
+ ) -> (MLXArray, MLXArray) {
345
+ // cos/sin: [S, D] -> [1, 1, S, D]
346
+ let cosExpanded = cos.expandedDimensions(axes: [0, 1])
347
+ let sinExpanded = sin.expandedDimensions(axes: [0, 1])
348
+
349
+ let qRotated = rotateHalf(q)
350
+ let kRotated = rotateHalf(k)
351
+
352
+ let qOut = q * cosExpanded + qRotated * sinExpanded
353
+ let kOut = k * cosExpanded + kRotated * sinExpanded
354
+
355
+ return (qOut, kOut)
356
+ }
357
+
358
+ /// Rotate features for RoPE (diffusers-style: treat consecutive pairs as real/imag)
359
+ private func rotateHalf(_ x: MLXArray) -> MLXArray {
360
+ // x shape: [B, H, S, D]
361
+ // Diffusers approach: reshape to [B, H, S, D/2, 2], then [-imag, real]
362
+ let batchSize = x.shape[0]
363
+ let numHeads = x.shape[1]
364
+ let seqLen = x.shape[2]
365
+ let dim = x.shape[3]
366
+ let halfDim = dim / 2
367
+
368
+ // Reshape to [B, H, S, D/2, 2]
369
+ let xReshaped = x.reshaped([batchSize, numHeads, seqLen, halfDim, 2])
370
+
371
+ // Get real and imag parts (consecutive pairs)
372
+ let xReal = xReshaped[0..., 0..., 0..., 0..., 0] // [B, H, S, D/2]
373
+ let xImag = xReshaped[0..., 0..., 0..., 0..., 1] // [B, H, S, D/2]
374
+
375
+ // Create rotated: stack [-imag, real] and flatten
376
+ let xRotatedStacked = stacked([-xImag, xReal], axis: -1) // [B, H, S, D/2, 2]
377
+ return xRotatedStacked.reshaped([batchSize, numHeads, seqLen, dim]) // [B, H, S, D]
378
+ }
379
+ }
@@ -0,0 +1,159 @@
1
+ // Flux2Embeddings.swift - Timestep and guidance embeddings
2
+ // Copyright 2025 Vincent Gourbin
3
+
4
+ import Foundation
5
+ import MLX
6
+ import MLXNN
7
+
8
+ /// Sinusoidal timestep embedding projection
9
+ public class Timesteps: Module, @unchecked Sendable {
10
+ let numChannels: Int
11
+ let flipSinToCos: Bool
12
+ let downscaleFreqShift: Float
13
+ let scale: Float
14
+
15
+ public init(
16
+ numChannels: Int,
17
+ flipSinToCos: Bool = true,
18
+ downscaleFreqShift: Float = 0.0,
19
+ scale: Float = 1.0
20
+ ) {
21
+ self.numChannels = numChannels
22
+ self.flipSinToCos = flipSinToCos
23
+ self.downscaleFreqShift = downscaleFreqShift
24
+ self.scale = scale
25
+ }
26
+
27
+ public func callAsFunction(_ timesteps: MLXArray) -> MLXArray {
28
+ let halfDim = numChannels / 2
29
+ let exponent = -log(Float(10000)) * MLXArray(0..<halfDim).asType(.float32)
30
+ let exponentScaled = exponent / (Float(halfDim) - downscaleFreqShift)
31
+
32
+ let emb = exp(exponentScaled)
33
+ let timestepsExpanded = timesteps.expandedDimensions(axis: -1).asType(.float32)
34
+ let embExpanded = emb.expandedDimensions(axis: 0)
35
+
36
+ let sinEmb = sin(timestepsExpanded * embExpanded * scale)
37
+ let cosEmb = cos(timestepsExpanded * embExpanded * scale)
38
+
39
+ if flipSinToCos {
40
+ return concatenated([cosEmb, sinEmb], axis: -1)
41
+ } else {
42
+ return concatenated([sinEmb, cosEmb], axis: -1)
43
+ }
44
+ }
45
+ }
46
+
47
+ /// MLP for embedding timesteps
48
+ public class TimestepEmbedding: Module, @unchecked Sendable {
49
+ @ModuleInfo var linear1: Linear
50
+ @ModuleInfo var linear2: Linear
51
+ let activation: @Sendable (MLXArray) -> MLXArray
52
+
53
+ public init(
54
+ inChannels: Int,
55
+ timeEmbedDim: Int,
56
+ activationFn: String = "silu"
57
+ ) {
58
+ // No bias to match checkpoint
59
+ self.linear1 = Linear(inChannels, timeEmbedDim, bias: false)
60
+ self.linear2 = Linear(timeEmbedDim, timeEmbedDim, bias: false)
61
+
62
+ switch activationFn {
63
+ case "silu", "swish":
64
+ self.activation = { silu($0) }
65
+ case "gelu":
66
+ self.activation = { gelu($0) }
67
+ case "relu":
68
+ self.activation = { relu($0) }
69
+ default:
70
+ self.activation = { silu($0) }
71
+ }
72
+ }
73
+
74
+ public func callAsFunction(_ sample: MLXArray) -> MLXArray {
75
+ var x = linear1(sample)
76
+ x = activation(x)
77
+ x = linear2(x)
78
+ return x
79
+ }
80
+ }
81
+
82
+ /// Combined timestep and guidance embeddings for Flux.2
83
+ ///
84
+ /// Generates embeddings from timestep and guidance scale values.
85
+ /// Output dimension is the transformer's inner dimension (6144 for Flux.2).
86
+ public class Flux2TimestepGuidanceEmbeddings: Module, @unchecked Sendable {
87
+ let timeProj: Timesteps
88
+ let timestepEmbedder: TimestepEmbedding
89
+ let guidanceEmbedder: TimestepEmbedding?
90
+ let useGuidanceEmbeds: Bool
91
+
92
+ /// Initialize timestep and guidance embeddings
93
+ /// - Parameters:
94
+ /// - embeddingDim: Dimension of sinusoidal projection (256)
95
+ /// - timeEmbedDim: Output dimension for timestep MLP (6144)
96
+ /// - useGuidanceEmbeds: Whether to include guidance embedding
97
+ public init(
98
+ embeddingDim: Int = 256,
99
+ timeEmbedDim: Int = 6144,
100
+ useGuidanceEmbeds: Bool = true
101
+ ) {
102
+ self.timeProj = Timesteps(numChannels: embeddingDim)
103
+ self.timestepEmbedder = TimestepEmbedding(
104
+ inChannels: embeddingDim,
105
+ timeEmbedDim: timeEmbedDim
106
+ )
107
+
108
+ self.useGuidanceEmbeds = useGuidanceEmbeds
109
+ if useGuidanceEmbeds {
110
+ self.guidanceEmbedder = TimestepEmbedding(
111
+ inChannels: embeddingDim,
112
+ timeEmbedDim: timeEmbedDim
113
+ )
114
+ } else {
115
+ self.guidanceEmbedder = nil
116
+ }
117
+ }
118
+
119
+ /// Generate timestep and optional guidance embeddings
120
+ /// - Parameters:
121
+ /// - timestep: Current diffusion timestep [B]
122
+ /// - guidance: Guidance scale [B] (optional)
123
+ /// - Returns: Combined embedding [B, timeEmbedDim]
124
+ public func callAsFunction(
125
+ timestep: MLXArray,
126
+ guidance: MLXArray? = nil
127
+ ) -> MLXArray {
128
+ // Project timestep to sinusoidal embedding
129
+ let tEmb = timeProj(timestep)
130
+ // Pass through MLP
131
+ var temb = timestepEmbedder(tEmb)
132
+
133
+ // Add guidance embedding if enabled
134
+ if useGuidanceEmbeds, let guidanceEmbedder = guidanceEmbedder, let g = guidance {
135
+ let gEmb = timeProj(g)
136
+ let gemb = guidanceEmbedder(gEmb)
137
+ temb = temb + gemb
138
+ }
139
+
140
+ return temb
141
+ }
142
+ }
143
+
144
+ /// Pooled text embedding projection for conditioning
145
+ public class PooledTextProjection: Module, @unchecked Sendable {
146
+ @ModuleInfo var linear: Linear
147
+
148
+ /// Initialize pooled text projection
149
+ /// - Parameters:
150
+ /// - inputDim: Input dimension from text encoder
151
+ /// - outputDim: Output dimension (time embed dim)
152
+ public init(inputDim: Int = 768, outputDim: Int = 6144) {
153
+ self._linear = ModuleInfo(wrappedValue: Linear(inputDim, outputDim))
154
+ }
155
+
156
+ public func callAsFunction(_ pooledEmbedding: MLXArray) -> MLXArray {
157
+ linear(pooledEmbedding)
158
+ }
159
+ }
@@ -0,0 +1,154 @@
1
+ // Flux2FeedForward.swift - SwiGLU FeedForward Network
2
+ // Copyright 2025 Vincent Gourbin
3
+
4
+ import Foundation
5
+ import MLX
6
+ import MLXNN
7
+
8
+ /// GEGLU activation: GELU-gated Linear Unit
9
+ /// Used in some transformer variants
10
+ /// Optimized with kernel fusion for the gating operation
11
+ public class GEGLU: Module, @unchecked Sendable {
12
+ @ModuleInfo var proj: Linear
13
+ let dim: Int
14
+ let innerDim: Int
15
+
16
+ /// Compiled gating function for kernel fusion
17
+ nonisolated(unsafe) private static let compiledGate: (MLXArray, MLXArray) -> MLXArray = compile { gate, value in
18
+ gelu(gate) * value
19
+ }
20
+
21
+ public init(dim: Int, innerDim: Int, bias: Bool = false) {
22
+ self.dim = dim
23
+ self.innerDim = innerDim
24
+ // Projects to 2x inner_dim for gate and value
25
+ self._proj = ModuleInfo(wrappedValue: Linear(dim, innerDim * 2, bias: bias))
26
+ }
27
+
28
+ public func callAsFunction(_ x: MLXArray) -> MLXArray {
29
+ let projected = proj(x)
30
+ // Split into gate and value
31
+ let chunks = split(projected, parts: 2, axis: -1)
32
+ let gate = chunks[0]
33
+ let value = chunks[1]
34
+ // GEGLU: gelu(gate) * value - compiled for kernel fusion
35
+ return Self.compiledGate(gate, value)
36
+ }
37
+ }
38
+
39
+ /// SwiGLU activation: Swish-gated Linear Unit
40
+ /// Used in Flux.2 transformer feedforward blocks
41
+ /// Optimized with kernel fusion for the gating operation
42
+ public class SwiGLU: Module, @unchecked Sendable {
43
+ @ModuleInfo var proj: Linear
44
+ let dim: Int
45
+ let innerDim: Int
46
+
47
+ /// Compiled gating function for kernel fusion
48
+ nonisolated(unsafe) private static let compiledGate: (MLXArray, MLXArray) -> MLXArray = compile { gate, value in
49
+ silu(gate) * value
50
+ }
51
+
52
+ public init(dim: Int, innerDim: Int, bias: Bool = false) {
53
+ self.dim = dim
54
+ self.innerDim = innerDim
55
+ // Projects to 2x inner_dim for gate and value
56
+ self._proj.wrappedValue = Linear(dim, innerDim * 2, bias: bias)
57
+ }
58
+
59
+ public func callAsFunction(_ x: MLXArray) -> MLXArray {
60
+ let projected = proj(x)
61
+ // Split into gate and value
62
+ let chunks = split(projected, parts: 2, axis: -1)
63
+ let gate = chunks[0]
64
+ let value = chunks[1]
65
+ // SwiGLU: silu(gate) * value (silu = swish) - compiled for kernel fusion
66
+ return Self.compiledGate(gate, value)
67
+ }
68
+ }
69
+
70
+ /// FeedForward network for Flux.2 transformer blocks
71
+ ///
72
+ /// Architecture: Linear -> SwiGLU -> Linear
73
+ /// - Input: [B, S, dim]
74
+ /// - Output: [B, S, dim]
75
+ public class Flux2FeedForward: Module, @unchecked Sendable {
76
+ let dim: Int
77
+ let innerDim: Int
78
+ @ModuleInfo var activation: SwiGLU
79
+ @ModuleInfo var linearOut: Linear
80
+
81
+ /// Initialize FeedForward block
82
+ /// - Parameters:
83
+ /// - dim: Model dimension (6144 for Flux.2)
84
+ /// - innerDim: Inner dimension (typically 4 * dim for standard FFN)
85
+ /// - bias: Whether to use bias in linear layers (default false to match checkpoint)
86
+ public init(
87
+ dim: Int,
88
+ innerDim: Int? = nil,
89
+ bias: Bool = false
90
+ ) {
91
+ self.dim = dim
92
+ // Default inner_dim is 4 * dim, but Flux.2 may use different ratio
93
+ self.innerDim = innerDim ?? (dim * 4)
94
+
95
+ // SwiGLU combines input projection and gating
96
+ self._activation.wrappedValue = SwiGLU(dim: dim, innerDim: self.innerDim, bias: bias)
97
+
98
+ // Output projection
99
+ self._linearOut.wrappedValue = Linear(self.innerDim, dim, bias: bias)
100
+ }
101
+
102
+ public func callAsFunction(_ x: MLXArray) -> MLXArray {
103
+ // SwiGLU: projects to 2*innerDim, applies silu gate, outputs innerDim
104
+ var hidden = activation(x)
105
+ // Project back to dim
106
+ hidden = linearOut(hidden)
107
+ return hidden
108
+ }
109
+ }
110
+
111
+ /// FeedForward variant with separate gate and up projections
112
+ /// (Alternative implementation matching some model architectures)
113
+ public class Flux2FeedForwardSplit: Module, @unchecked Sendable {
114
+ let dim: Int
115
+ let innerDim: Int
116
+ @ModuleInfo var linearGate: Linear
117
+ @ModuleInfo var linearUp: Linear
118
+ @ModuleInfo var linearDown: Linear
119
+
120
+ public init(
121
+ dim: Int,
122
+ innerDim: Int? = nil,
123
+ bias: Bool = false
124
+ ) {
125
+ self.dim = dim
126
+ self.innerDim = innerDim ?? (dim * 4)
127
+
128
+ self._linearGate = ModuleInfo(wrappedValue: Linear(dim, self.innerDim, bias: bias))
129
+ self._linearUp = ModuleInfo(wrappedValue: Linear(dim, self.innerDim, bias: bias))
130
+ self._linearDown = ModuleInfo(wrappedValue: Linear(self.innerDim, dim, bias: bias))
131
+ }
132
+
133
+ public func callAsFunction(_ x: MLXArray) -> MLXArray {
134
+ // SwiGLU with separate projections
135
+ let gate = silu(linearGate(x))
136
+ let up = linearUp(x)
137
+ let hidden = gate * up
138
+ return linearDown(hidden)
139
+ }
140
+ }
141
+
142
+ /// Context-specific FeedForward for text encoder hidden states
143
+ /// Used in double-stream blocks for processing text separately
144
+ public class Flux2ContextFeedForward: Module, @unchecked Sendable {
145
+ let feedForward: Flux2FeedForward
146
+
147
+ public init(dim: Int, innerDim: Int? = nil, bias: Bool = false) {
148
+ self.feedForward = Flux2FeedForward(dim: dim, innerDim: innerDim, bias: bias)
149
+ }
150
+
151
+ public func callAsFunction(_ x: MLXArray) -> MLXArray {
152
+ feedForward(x)
153
+ }
154
+ }