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,530 @@
1
+ // Flux2Transformer.swift - Complete Flux.2 Diffusion Transformer
2
+ // Copyright 2025 Vincent Gourbin
3
+
4
+ import Foundation
5
+ import MLX
6
+ import MLXNN
7
+
8
+ /// Flux.2 Diffusion Transformer (DiT) Model
9
+ ///
10
+ /// Architecture:
11
+ /// - 8 double-stream transformer blocks (joint text-image attention)
12
+ /// - 48 single-stream transformer blocks (concatenated self-attention)
13
+ /// - ~32B parameters total
14
+ ///
15
+ /// Flow:
16
+ /// 1. Project latents to inner dim: [B, H*W, 128] -> [B, H*W, 6144]
17
+ /// 2. Project text embeddings: [B, S, 15360] -> [B, S, 6144]
18
+ /// 3. Generate timestep/guidance embeddings
19
+ /// 4. Double-stream blocks: process text and image separately with joint attention
20
+ /// 5. Single-stream blocks: concatenate and process together
21
+ /// 6. Final norm and projection: [B, H*W, 6144] -> [B, H*W, 128]
22
+ public class Flux2Transformer2DModel: Module, @unchecked Sendable {
23
+ let config: Flux2TransformerConfig
24
+
25
+ /// Memory optimization settings for periodic graph evaluation
26
+ public var memoryOptimization: MemoryOptimizationConfig
27
+
28
+ /// Enable gradient checkpointing for training (reduces memory, increases compute ~2x)
29
+ /// When enabled, each transformer block's forward pass is wrapped with checkpoint()
30
+ /// so intermediate activations are recomputed during backward instead of stored.
31
+ public var gradientCheckpointing: Bool = false
32
+
33
+ // Input embeddings (var for LoRA injection)
34
+ @ModuleInfo var xEmbedder: Linear // Latent projection: 128 -> 6144
35
+ @ModuleInfo var contextEmbedder: Linear // Text projection: 15360 -> 6144
36
+
37
+ // Timestep/guidance embeddings (var for LoRA injection)
38
+ @ModuleInfo var timeGuidanceEmbed: Flux2TimestepGuidanceEmbeddings
39
+
40
+ // Positional embeddings (RoPE)
41
+ let posEmbed: Flux2RoPE
42
+
43
+ // Modulation layers (var for LoRA injection)
44
+ @ModuleInfo var doubleStreamModulationImg: Flux2Modulation
45
+ @ModuleInfo var doubleStreamModulationTxt: Flux2Modulation
46
+ @ModuleInfo var singleStreamModulation: Flux2Modulation
47
+
48
+ // Transformer blocks
49
+ let transformerBlocks: [Flux2TransformerBlock]
50
+ let singleTransformerBlocks: [Flux2SingleTransformerBlock]
51
+
52
+ // Output layers
53
+ let normOut: AdaLayerNormContinuous
54
+ @ModuleInfo var projOut: Linear // Output projection: 6144 -> 128
55
+
56
+ /// Initialize Flux.2 Transformer
57
+ /// - Parameters:
58
+ /// - config: Model configuration
59
+ /// - memoryOptimization: Memory optimization settings (default: moderate)
60
+ public init(
61
+ config: Flux2TransformerConfig = .klein4B,
62
+ memoryOptimization: MemoryOptimizationConfig = .moderate,
63
+ usesNormOutBias: Bool = false
64
+ ) {
65
+ self.config = config
66
+ self.memoryOptimization = memoryOptimization
67
+
68
+ let dim = config.innerDim // 6144
69
+
70
+ // Input projections (no bias to match checkpoint)
71
+ self.xEmbedder = Linear(config.inChannels, dim, bias: false)
72
+ self.contextEmbedder = Linear(config.jointAttentionDim, dim, bias: false)
73
+
74
+ // Timestep embeddings
75
+ self.timeGuidanceEmbed = Flux2TimestepGuidanceEmbeddings(
76
+ embeddingDim: 256,
77
+ timeEmbedDim: dim,
78
+ useGuidanceEmbeds: config.guidanceEmbeds
79
+ )
80
+
81
+ // RoPE
82
+ self.posEmbed = Flux2RoPE(
83
+ axesDims: config.axesDimsRope,
84
+ theta: config.ropeTheta
85
+ )
86
+
87
+ // Modulation layers (6 params each for double-stream, 3 for single)
88
+ self.doubleStreamModulationImg = Flux2Modulation(dim: dim, numSets: 2)
89
+ self.doubleStreamModulationTxt = Flux2Modulation(dim: dim, numSets: 2)
90
+ self.singleStreamModulation = Flux2Modulation(dim: dim, numSets: 1)
91
+
92
+ // Double-stream blocks (8)
93
+ self.transformerBlocks = (0..<config.numLayers).map { _ in
94
+ Flux2TransformerBlock(
95
+ dim: dim,
96
+ numHeads: config.numAttentionHeads,
97
+ headDim: config.attentionHeadDim
98
+ )
99
+ }
100
+
101
+ // Single-stream blocks (48)
102
+ self.singleTransformerBlocks = (0..<config.numSingleLayers).map { _ in
103
+ Flux2SingleTransformerBlock(
104
+ dim: dim,
105
+ numHeads: config.numAttentionHeads,
106
+ headDim: config.attentionHeadDim
107
+ )
108
+ }
109
+
110
+ // Output (no bias to match checkpoint)
111
+ self.normOut = AdaLayerNormContinuous(dim: dim, usesBias: usesNormOutBias)
112
+ self.projOut = Linear(dim, config.outChannels, bias: false)
113
+ }
114
+
115
+ /// Forward pass
116
+ /// - Parameters:
117
+ /// - hiddenStates: Packed latent image [B, S_img, 128]
118
+ /// - encoderHiddenStates: Text embeddings from Mistral [B, S_txt, 15360]
119
+ /// - timestep: Diffusion timestep [B]
120
+ /// - guidance: Guidance scale [B] (optional)
121
+ /// - imgIds: Image position IDs [S_img, 4]
122
+ /// - txtIds: Text position IDs [S_txt, 4]
123
+ /// - Returns: Predicted noise [B, S_img, 128]
124
+ public func callAsFunction(
125
+ hiddenStates: MLXArray,
126
+ encoderHiddenStates: MLXArray,
127
+ timestep: MLXArray,
128
+ guidance: MLXArray? = nil,
129
+ imgIds: MLXArray,
130
+ txtIds: MLXArray
131
+ ) -> MLXArray {
132
+ Flux2Debug.verbose("=== Transformer Forward ===")
133
+ Flux2Debug.verbose("hiddenStates: \(hiddenStates.shape)")
134
+ Flux2Debug.verbose("encoderHiddenStates: \(encoderHiddenStates.shape)")
135
+ Flux2Debug.verbose("timestep: \(timestep.shape)")
136
+
137
+ // Project inputs
138
+ var imgHS = xEmbedder(hiddenStates)
139
+ var txtHS = contextEmbedder(encoderHiddenStates)
140
+ Flux2Debug.verbose("After projection - imgHS: \(imgHS.shape), txtHS: \(txtHS.shape)")
141
+
142
+ // Scale timestep and guidance by 1000 (diffusers pattern)
143
+ // Pipeline passes timestep in [0, 1] range (sigma), but the sinusoidal
144
+ // embedding in Flux2TimestepGuidanceEmbeddings expects values in [0, 1000] range.
145
+ // Similarly, guidance (e.g., 4.0) is scaled to 4000.
146
+ let scaledTimestep = timestep * 1000
147
+ let scaledGuidance = guidance.map { $0 * 1000 }
148
+
149
+ // Generate timestep + guidance embedding
150
+ let temb = timeGuidanceEmbed(timestep: scaledTimestep, guidance: scaledGuidance)
151
+ Flux2Debug.verbose("temb shape: \(temb.shape)")
152
+
153
+ // Generate RoPE embeddings
154
+ let combinedIds = concatenated([txtIds, imgIds], axis: 0)
155
+ let ropeEmb = posEmbed(combinedIds)
156
+ Flux2Debug.verbose("RoPE shapes - cos: \(ropeEmb.cos.shape), sin: \(ropeEmb.sin.shape)")
157
+
158
+ // --- Double-Stream Blocks ---
159
+ // OPTIMIZATION: Compute modulation parameters ONCE before the loop
160
+ // (they only depend on temb which doesn't change between blocks)
161
+ let imgMod = doubleStreamModulationImg(temb)
162
+ let txtMod = doubleStreamModulationTxt(temb)
163
+ Flux2Debug.verbose("imgMod count: \(imgMod.count), first shift: \(imgMod.first?.shift.shape ?? [])")
164
+
165
+ // Pre-compute flattened modulation arrays once (avoids re-creating at each iteration)
166
+ let imgModFlat = imgMod.flatMap { [$0.shift, $0.scale, $0.gate] }
167
+ let txtModFlat = txtMod.flatMap { [$0.shift, $0.scale, $0.gate] }
168
+
169
+ for (blockIdx, block) in transformerBlocks.enumerated() {
170
+ Flux2Debug.verbose("Double-stream block \(blockIdx)")
171
+
172
+ if gradientCheckpointing {
173
+ // Gradient checkpointing: wrap block forward pass with checkpoint()
174
+ // Pack all inputs (activations + trainable params) as explicit arguments
175
+ // so checkpoint() can track gradients through them during backward pass.
176
+ let paramTemplate = block.trainableParameters()
177
+ let paramValues = paramTemplate.flattenedValues()
178
+ let numActivations = 17 // imgHS, txtHS, temb, ropeCos, ropeSin, 6×imgMod, 6×txtMod
179
+
180
+ var inputs: [MLXArray] = [imgHS, txtHS, temb, ropeEmb.cos, ropeEmb.sin]
181
+ inputs.append(contentsOf: imgModFlat)
182
+ inputs.append(contentsOf: txtModFlat)
183
+ inputs.append(contentsOf: paramValues)
184
+
185
+ let checkpointedForward = checkpoint { (arrays: [MLXArray]) -> [MLXArray] in
186
+ // Unpack activations
187
+ let hs = arrays[0], ehs = arrays[1], t = arrays[2]
188
+ let rope = (cos: arrays[3], sin: arrays[4])
189
+ let iMod = [ModulationParams(shift: arrays[5], scale: arrays[6], gate: arrays[7]),
190
+ ModulationParams(shift: arrays[8], scale: arrays[9], gate: arrays[10])]
191
+ let tMod = [ModulationParams(shift: arrays[11], scale: arrays[12], gate: arrays[13]),
192
+ ModulationParams(shift: arrays[14], scale: arrays[15], gate: arrays[16])]
193
+
194
+ // Unpack and restore trainable parameters in block
195
+ let blockParams = Array(arrays[numActivations...])
196
+ block.update(parameters: paramTemplate.replacingValues(with: blockParams))
197
+
198
+ let (newTxt, newImg) = block(
199
+ hiddenStates: hs,
200
+ encoderHiddenStates: ehs,
201
+ temb: t,
202
+ rotaryEmb: rope,
203
+ imgModParams: iMod,
204
+ txtModParams: tMod
205
+ )
206
+ return [newTxt, newImg]
207
+ }
208
+
209
+ let out = checkpointedForward(inputs)
210
+ txtHS = out[0]
211
+ imgHS = out[1]
212
+ } else {
213
+ let (newTxt, newImg) = block(
214
+ hiddenStates: imgHS,
215
+ encoderHiddenStates: txtHS,
216
+ temb: temb,
217
+ rotaryEmb: ropeEmb,
218
+ imgModParams: imgMod,
219
+ txtModParams: txtMod
220
+ )
221
+
222
+ imgHS = newImg
223
+ txtHS = newTxt
224
+ }
225
+ Flux2Debug.verbose("After block \(blockIdx) - imgHS: \(imgHS.shape), txtHS: \(txtHS.shape)")
226
+
227
+ // Memory optimization: periodic evaluation to prevent graph accumulation
228
+ // Skip when gradient checkpointing is active (checkpoint boundaries handle segmentation)
229
+ if !gradientCheckpointing &&
230
+ memoryOptimization.evalFrequency > 0 &&
231
+ (blockIdx + 1) % memoryOptimization.evalFrequency == 0 {
232
+ eval(imgHS, txtHS)
233
+ if memoryOptimization.clearCacheOnEval {
234
+ MLX.Memory.clearCache()
235
+ }
236
+ Flux2Debug.verbose("Eval at double-stream block \(blockIdx)")
237
+ }
238
+ }
239
+
240
+ // Memory optimization: evaluate between phases
241
+ if memoryOptimization.evalBetweenPhases {
242
+ eval(imgHS, txtHS)
243
+ if memoryOptimization.clearCacheOnEval {
244
+ MLX.Memory.clearCache()
245
+ }
246
+ Flux2Debug.verbose("Eval between double/single stream phases")
247
+ }
248
+
249
+ // --- Single-Stream Blocks ---
250
+ // Concatenate text and image streams BEFORE entering single-stream blocks
251
+ // (diffusers pattern: single blocks work on concatenated hidden_states)
252
+ let textSeqLen = txtHS.shape[1]
253
+ var combinedHS = concatenated([txtHS, imgHS], axis: 1) // [B, S_txt + S_img, dim]
254
+ Flux2Debug.verbose("Single-stream input (combined): \(combinedHS.shape)")
255
+
256
+ // OPTIMIZATION: Compute single-stream modulation ONCE before the loop
257
+ let singleMod = singleStreamModulation(temb)
258
+ let singleModFlat = singleMod.flatMap { [$0.shift, $0.scale, $0.gate] }
259
+
260
+ for (blockIdx, block) in singleTransformerBlocks.enumerated() {
261
+ if gradientCheckpointing {
262
+ // Gradient checkpointing: same pattern as double-stream blocks
263
+ let paramTemplate = block.trainableParameters()
264
+ let paramValues = paramTemplate.flattenedValues()
265
+ let numActivations = 7 // combinedHS, temb, ropeCos, ropeSin, mod.shift, mod.scale, mod.gate
266
+
267
+ var inputs: [MLXArray] = [combinedHS, temb, ropeEmb.cos, ropeEmb.sin]
268
+ inputs.append(contentsOf: singleModFlat)
269
+ inputs.append(contentsOf: paramValues)
270
+
271
+ let checkpointedForward = checkpoint { (arrays: [MLXArray]) -> [MLXArray] in
272
+ let hs = arrays[0], t = arrays[1]
273
+ let rope = (cos: arrays[2], sin: arrays[3])
274
+ let mod = [ModulationParams(shift: arrays[4], scale: arrays[5], gate: arrays[6])]
275
+
276
+ // Restore trainable parameters in block
277
+ let blockParams = Array(arrays[numActivations...])
278
+ block.update(parameters: paramTemplate.replacingValues(with: blockParams))
279
+
280
+ let result = block(
281
+ hiddenStates: hs,
282
+ encoderHiddenStates: nil,
283
+ temb: t,
284
+ rotaryEmb: rope,
285
+ modParams: mod
286
+ )
287
+ return [result]
288
+ }
289
+
290
+ let out = checkpointedForward(inputs)
291
+ combinedHS = out[0]
292
+ } else {
293
+ // Pass encoder_hidden_states=nil since everything is in combinedHS
294
+ combinedHS = block(
295
+ hiddenStates: combinedHS,
296
+ encoderHiddenStates: nil,
297
+ temb: temb,
298
+ rotaryEmb: ropeEmb,
299
+ modParams: singleMod
300
+ )
301
+ }
302
+
303
+ // Memory optimization: periodic evaluation to prevent graph accumulation
304
+ // Skip when gradient checkpointing is active (checkpoint boundaries handle segmentation)
305
+ if !gradientCheckpointing &&
306
+ memoryOptimization.evalFrequency > 0 &&
307
+ (blockIdx + 1) % memoryOptimization.evalFrequency == 0 {
308
+ eval(combinedHS)
309
+ if memoryOptimization.clearCacheOnEval {
310
+ MLX.Memory.clearCache()
311
+ }
312
+ Flux2Debug.verbose("Eval at single-stream block \(blockIdx)")
313
+ }
314
+ }
315
+
316
+ // Remove text tokens from the concatenated stream
317
+ imgHS = combinedHS[0..., textSeqLen..., 0...]
318
+ Flux2Debug.verbose("After single blocks (image only): \(imgHS.shape)")
319
+
320
+ // --- Output ---
321
+ // Final adaptive layer norm
322
+ imgHS = normOut(imgHS, conditioning: temb)
323
+
324
+ // Project to output channels
325
+ let output = projOut(imgHS)
326
+
327
+ return output
328
+ }
329
+
330
+ /// Convenience method with automatic position ID generation
331
+ public func forward(
332
+ latents: MLXArray,
333
+ encoderHiddenStates: MLXArray,
334
+ timestep: MLXArray,
335
+ guidance: MLXArray? = nil,
336
+ height: Int,
337
+ width: Int
338
+ ) -> MLXArray {
339
+ let textLen = encoderHiddenStates.shape[1]
340
+
341
+ // Generate position IDs
342
+ let imgIds = generateImagePositionIDs(height: height, width: width)
343
+ let txtIds = generateTextPositionIDs(length: textLen)
344
+
345
+ return self.callAsFunction(
346
+ hiddenStates: latents,
347
+ encoderHiddenStates: encoderHiddenStates,
348
+ timestep: timestep,
349
+ guidance: guidance,
350
+ imgIds: imgIds,
351
+ txtIds: txtIds
352
+ )
353
+ }
354
+
355
+ // MARK: - KV Cache Support
356
+
357
+ /// Build KV cache by processing reference tokens at step 0
358
+ /// - Parameters:
359
+ /// - encoderHiddenStates: Text embeddings [B, S_txt, 15360]
360
+ /// - timestep: Diffusion timestep [B]
361
+ /// - guidance: Guidance scale [B]
362
+ /// - imgIds: Image position IDs [S_img, 4]
363
+ /// - txtIds: Text position IDs [S_txt, 4]
364
+ /// - Returns: KV cache for all layers
365
+ public func buildKVCache(
366
+ encoderHiddenStates: MLXArray,
367
+ timestep: MLXArray,
368
+ guidance: MLXArray? = nil,
369
+ imgIds: MLXArray,
370
+ txtIds: MLXArray
371
+ ) -> Flux2KVCache {
372
+ let scaledTimestep = timestep * 1000
373
+ let scaledGuidance = guidance.map { $0 * 1000 }
374
+ let temb = timeGuidanceEmbed(timestep: scaledTimestep, guidance: scaledGuidance)
375
+
376
+ let combinedIds = concatenated([txtIds, imgIds], axis: 0)
377
+ let ropeEmb = posEmbed(combinedIds)
378
+
379
+ let imgMod = doubleStreamModulationImg(temb)
380
+ let txtMod = doubleStreamModulationTxt(temb)
381
+
382
+ // Project text embeddings
383
+ var txtHS = contextEmbedder(encoderHiddenStates)
384
+
385
+ // Build double-stream KV cache
386
+ var doubleStreamCaches: [Flux2KVCacheLayer] = []
387
+ for block in transformerBlocks {
388
+ // Compute text K/V for this layer
389
+ let txtNorm = block.norm1Context(txtHS)
390
+ let (k, v) = block.attn.computeTextKV(
391
+ encoderHiddenStates: txtNorm,
392
+ batchSize: txtHS.shape[0]
393
+ )
394
+ doubleStreamCaches.append(Flux2KVCacheLayer(k: k, v: v))
395
+
396
+ // Process text through block to get updated text HS for next layer
397
+ txtHS = block.forwardTextOnly(
398
+ encoderHiddenStates: txtHS,
399
+ temb: temb,
400
+ txtModParams: txtMod
401
+ )
402
+ }
403
+
404
+ // Build single-stream KV cache
405
+ var singleStreamCaches: [Flux2KVCacheLayer] = []
406
+ let singleMod = singleStreamModulation(temb)
407
+
408
+ // Process text through single-stream blocks
409
+ var combinedHS = txtHS
410
+ for block in singleTransformerBlocks {
411
+ let combinedNorm = block.norm(combinedHS)
412
+ let (k, v) = block.attn.computeRefKV(
413
+ hiddenStates: combinedNorm,
414
+ batchSize: combinedHS.shape[0]
415
+ )
416
+ singleStreamCaches.append(Flux2KVCacheLayer(k: k, v: v))
417
+
418
+ combinedHS = block.forwardRefOnly(
419
+ hiddenStates: combinedHS,
420
+ temb: temb,
421
+ modParams: singleMod
422
+ )
423
+ }
424
+
425
+ return Flux2KVCache(doubleStreamLayers: doubleStreamCaches, singleStreamLayers: singleStreamCaches)
426
+ }
427
+
428
+ /// Forward pass with KV cache (step 1+ only)
429
+ /// - Parameters:
430
+ /// - hiddenStates: Latent image [B, S_img, 128]
431
+ /// - encoderHiddenStates: Text embeddings [B, S_txt, 15360]
432
+ /// - timestep: Diffusion timestep [B]
433
+ /// - guidance: Guidance scale [B]
434
+ /// - kvCache: Pre-built KV cache
435
+ /// - imgIds: Image position IDs [S_img, 4]
436
+ /// - txtIds: Text position IDs [S_txt, 4]
437
+ /// - Returns: Predicted noise [B, S_img, 128]
438
+ public func callAsFunctionWithKVCache(
439
+ hiddenStates: MLXArray,
440
+ encoderHiddenStates: MLXArray,
441
+ timestep: MLXArray,
442
+ guidance: MLXArray? = nil,
443
+ kvCache: Flux2KVCache,
444
+ imgIds: MLXArray,
445
+ txtIds: MLXArray
446
+ ) -> MLXArray {
447
+ let scaledTimestep = timestep * 1000
448
+ let scaledGuidance = guidance.map { $0 * 1000 }
449
+ let temb = timeGuidanceEmbed(timestep: scaledTimestep, guidance: scaledGuidance)
450
+
451
+ // Project inputs
452
+ var imgHS = xEmbedder(hiddenStates)
453
+ let txtHS = contextEmbedder(encoderHiddenStates)
454
+
455
+ // Generate RoPE embeddings for image portion only
456
+ let textSeqLen = txtHS.shape[1]
457
+ let imgSeqLen = imgHS.shape[1]
458
+ let imgRopeEmb = posEmbed(imgIds)
459
+
460
+ let imgMod = doubleStreamModulationImg(temb)
461
+ let txtMod = doubleStreamModulationTxt(temb)
462
+
463
+ // --- Double-Stream Blocks with KV Cache ---
464
+ for (blockIdx, block) in transformerBlocks.enumerated() {
465
+ let cache = kvCache.doubleStreamLayers[blockIdx]
466
+ let (newTxt, newImg) = block(
467
+ hiddenStates: imgHS,
468
+ encoderHiddenStates: txtHS,
469
+ temb: temb,
470
+ cachedTextKV: (k: cache.k, v: cache.v),
471
+ rotaryEmb: (cos: imgRopeEmb.cos, sin: imgRopeEmb.sin),
472
+ imgModParams: imgMod,
473
+ txtModParams: txtMod
474
+ )
475
+ imgHS = newImg
476
+ // txtHS doesn't change in cached mode - we use the original text embeddings
477
+ }
478
+
479
+ // --- Single-Stream Blocks with KV Cache ---
480
+ let singleMod = singleStreamModulation(temb)
481
+ let singleRopeEmb = posEmbed(imgIds) // Image-only RoPE
482
+
483
+ for (blockIdx, block) in singleTransformerBlocks.enumerated() {
484
+ let cache = kvCache.singleStreamLayers[blockIdx]
485
+ imgHS = block(
486
+ hiddenStates: imgHS,
487
+ cachedRefKV: (k: cache.k, v: cache.v),
488
+ temb: temb,
489
+ rotaryEmb: (cos: singleRopeEmb.cos, sin: singleRopeEmb.sin),
490
+ modParams: singleMod
491
+ )
492
+ }
493
+
494
+ // --- Output ---
495
+ imgHS = normOut(imgHS, conditioning: temb)
496
+ return projOut(imgHS)
497
+ }
498
+ }
499
+
500
+ // MARK: - Weight Loading Extension
501
+
502
+ extension Flux2Transformer2DModel {
503
+ /// Load weights from safetensors files
504
+ /// - Parameters:
505
+ /// - url: Directory containing weight files
506
+ /// - quantization: Quantization configuration
507
+ public func loadWeights(from url: URL, quantization: TransformerQuantization) throws {
508
+ // Weight loading implementation will be added in WeightLoader.swift
509
+ // This is a placeholder for the interface
510
+ fatalError("Weight loading not yet implemented - see WeightLoader.swift")
511
+ }
512
+ }
513
+
514
+ // MARK: - Memory Management
515
+
516
+ extension Flux2Transformer2DModel {
517
+ /// Estimated memory requirement for this model configuration
518
+ public var estimatedMemoryGB: Int {
519
+ // Rough estimate based on parameter count and dtype
520
+ // 32B params * 2 bytes (bf16) / 1e9 = ~64GB for bf16
521
+ // Quantized versions are proportionally smaller
522
+ 64
523
+ }
524
+
525
+ /// Clear GPU cache
526
+ public func clearCache() {
527
+ // MLX manages memory automatically, but we can suggest cleanup
528
+ eval([]) // Ensure all operations are complete
529
+ }
530
+ }