bleam 0.0.7 → 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.
- package/dist/cli.cjs +23 -4
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +23 -4
- package/dist/{ui-234Plg7Z.d.cts → ui-Bg11tvlc.d.ts} +7 -7
- package/dist/ui.d.ts +1 -1
- package/dist/window.d.ts +1 -1
- package/package.json +4 -1
- package/templates/image-generation/app/index.tsx +1 -1
- package/templates/native/.gitattributes +1 -0
- package/templates/native/App.tsx +26 -0
- package/templates/native/app.json +12 -0
- package/templates/native/index.ts +8 -0
- package/templates/native/ios/.xcode.env +11 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/Flux2Config.swift +288 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryConfig.swift +233 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryOptimizationConfig.swift +157 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +425 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/QuantizationConfig.swift +138 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/TransformerRepoProfile.swift +59 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/VAEConfig.swift +134 -0
- package/templates/native/ios/Bleam/AI/Flux2/Flux2Core.swift +55 -0
- package/templates/native/ios/Bleam/AI/Flux2/Loading/WeightLoader.swift +1192 -0
- package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +1282 -0
- package/templates/native/ios/Bleam/AI/Flux2/Pipeline/LatentUtils.swift +191 -0
- package/templates/native/ios/Bleam/AI/Flux2/Scheduler/FlowMatchEulerScheduler.swift +279 -0
- package/templates/native/ios/Bleam/AI/Flux2/Text/KleinTextEncoder.swift +380 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Attention.swift +379 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Embeddings.swift +159 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FeedForward.swift +154 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FusedKernels.swift +198 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2KVCache.swift +36 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Modulation.swift +193 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2ParallelAttention.swift +389 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2RoPE.swift +304 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2SingleBlock.swift +290 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Transformer.swift +530 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2TransformerBlock.swift +362 -0
- package/templates/native/ios/Bleam/AI/Flux2/Utils/Flux2Debug.swift +101 -0
- package/templates/native/ios/Bleam/AI/Flux2/Utils/MLXCheckpoint.swift +118 -0
- package/templates/native/ios/Bleam/AI/Flux2/Utils/MemoryManager.swift +201 -0
- package/templates/native/ios/Bleam/AI/Flux2/VAE/AutoencoderKL.swift +476 -0
- package/templates/native/ios/Bleam/AI/Flux2/VAE/ResnetBlock.swift +316 -0
- package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEDecoder.swift +120 -0
- package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEEncoder.swift +136 -0
- package/templates/native/ios/Bleam/AppDelegate.swift +373 -0
- package/templates/native/ios/Bleam/Appearance.swift +62 -0
- package/templates/native/ios/Bleam/Bridging-Header.h +3 -0
- package/templates/native/ios/Bleam/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
- package/templates/native/ios/Bleam/Images.xcassets/Contents.json +6 -0
- package/templates/native/ios/Bleam/Info.plist +76 -0
- package/templates/native/ios/Bleam/PrivacyInfo.xcprivacy +48 -0
- package/templates/native/ios/Bleam/SceneDelegate.swift +58 -0
- package/templates/native/ios/Bleam/SplashScreen.storyboard +47 -0
- package/templates/native/ios/Bleam/Supporting/Expo.plist +6 -0
- package/templates/native/ios/Bleam/bleam.entitlements +10 -0
- package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +1146 -0
- package/templates/native/ios/Bleam.xcodeproj/xcshareddata/xcschemes/Bleam.xcscheme +88 -0
- package/templates/native/ios/GenerationService/GenerationService.entitlements +10 -0
- package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +591 -0
- package/templates/native/ios/GenerationService/Info.plist +31 -0
- package/templates/native/ios/GenerationService/main.swift +165 -0
- package/templates/native/ios/PlatformHelper/Info.plist +29 -0
- package/templates/native/ios/PlatformHelper/main.swift +335 -0
- package/templates/native/ios/Podfile +198 -0
- package/templates/native/ios/Podfile.lock +2284 -0
- package/templates/native/ios/Podfile.properties.json +5 -0
- package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +12 -0
- package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +124 -0
- package/templates/native/metro.config.js +6 -0
- package/templates/native/modules/bleam-runtime/BleamRuntime.podspec +14 -0
- package/templates/native/modules/bleam-runtime/expo-module.config.json +8 -0
- package/templates/native/modules/bleam-runtime/ios/AIModule.swift +1258 -0
- package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +135 -0
- package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +313 -0
- package/templates/native/modules/bleam-runtime/package.json +5 -0
- package/templates/native/package.json +39 -0
- package/templates/native/scripts/build/mlx-frameworks.ts +421 -0
- package/templates/native/scripts/start/index.ts +474 -0
- package/templates/native/scripts/start/known-failures.ts +20 -0
- package/templates/native/scripts/start/loader.ts +76 -0
- package/templates/native/scripts/start/native-state.ts +242 -0
- package/templates/native/scripts/start/package.json +4 -0
- package/templates/native/scripts/start/swift-packages.ts +414 -0
- package/templates/native/scripts/start/xcode-formatter.ts +106 -0
- package/templates/native/tsconfig.json +14 -0
- package/templates/native/yarn.lock +3749 -0
- package/dist/ui-BJmXhz9Q.d.ts +0 -92
- package/dist/ui-Dd7SXdbg.d.cts +0 -92
- package/dist/ui-Ds88eETu.d.ts +0 -92
- package/dist/ui-TaqnB5SP.d.ts +0 -92
- package/dist/ui-WQvnXL0W.d.ts +0 -92
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
// Flux2TransformerBlock.swift - Double-Stream Transformer Block
|
|
2
|
+
// Copyright 2025 Vincent Gourbin
|
|
3
|
+
|
|
4
|
+
import Foundation
|
|
5
|
+
import MLX
|
|
6
|
+
import MLXNN
|
|
7
|
+
|
|
8
|
+
/// Double-Stream Transformer Block for Flux.2
|
|
9
|
+
///
|
|
10
|
+
/// Processes image and text hidden states in parallel streams with:
|
|
11
|
+
/// 1. Separate LayerNorm for each modality
|
|
12
|
+
/// 2. Joint attention across both modalities
|
|
13
|
+
/// 3. Separate FeedForward for each modality
|
|
14
|
+
/// 4. AdaLN modulation from timestep embeddings
|
|
15
|
+
///
|
|
16
|
+
/// There are 8 such blocks in Flux.2.
|
|
17
|
+
public class Flux2TransformerBlock: Module, @unchecked Sendable {
|
|
18
|
+
let dim: Int
|
|
19
|
+
let numHeads: Int
|
|
20
|
+
let headDim: Int
|
|
21
|
+
|
|
22
|
+
// Layer norms for image stream
|
|
23
|
+
let norm1: LayerNorm
|
|
24
|
+
let norm2: LayerNorm
|
|
25
|
+
|
|
26
|
+
// Layer norms for text stream
|
|
27
|
+
let norm1Context: LayerNorm
|
|
28
|
+
let norm2Context: LayerNorm
|
|
29
|
+
|
|
30
|
+
// Joint attention
|
|
31
|
+
let attn: Flux2Attention
|
|
32
|
+
|
|
33
|
+
// FeedForward networks
|
|
34
|
+
let ff: Flux2FeedForward
|
|
35
|
+
let ffContext: Flux2FeedForward
|
|
36
|
+
|
|
37
|
+
/// Initialize Double-Stream Block
|
|
38
|
+
/// - Parameters:
|
|
39
|
+
/// - dim: Model dimension (6144)
|
|
40
|
+
/// - numHeads: Number of attention heads (48)
|
|
41
|
+
/// - headDim: Dimension per head (128)
|
|
42
|
+
/// - mlpRatio: MLP expansion ratio
|
|
43
|
+
public init(
|
|
44
|
+
dim: Int,
|
|
45
|
+
numHeads: Int,
|
|
46
|
+
headDim: Int,
|
|
47
|
+
mlpRatio: Float = 3.0
|
|
48
|
+
) {
|
|
49
|
+
self.dim = dim
|
|
50
|
+
self.numHeads = numHeads
|
|
51
|
+
self.headDim = headDim
|
|
52
|
+
|
|
53
|
+
let mlpHidden = Int(Float(dim) * mlpRatio)
|
|
54
|
+
|
|
55
|
+
// Image stream norms
|
|
56
|
+
self.norm1 = LayerNorm(dimensions: dim, eps: 1e-6, affine: false)
|
|
57
|
+
self.norm2 = LayerNorm(dimensions: dim, eps: 1e-6, affine: false)
|
|
58
|
+
|
|
59
|
+
// Text stream norms
|
|
60
|
+
self.norm1Context = LayerNorm(dimensions: dim, eps: 1e-6, affine: false)
|
|
61
|
+
self.norm2Context = LayerNorm(dimensions: dim, eps: 1e-6, affine: false)
|
|
62
|
+
|
|
63
|
+
// Joint attention
|
|
64
|
+
self.attn = Flux2Attention(dim: dim, numHeads: numHeads, headDim: headDim)
|
|
65
|
+
|
|
66
|
+
// FeedForward
|
|
67
|
+
self.ff = Flux2FeedForward(dim: dim, innerDim: mlpHidden)
|
|
68
|
+
self.ffContext = Flux2FeedForward(dim: dim, innerDim: mlpHidden)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Forward pass
|
|
72
|
+
/// - Parameters:
|
|
73
|
+
/// - hiddenStates: Image hidden states [B, S_img, dim]
|
|
74
|
+
/// - encoderHiddenStates: Text hidden states [B, S_txt, dim]
|
|
75
|
+
/// - temb: Timestep embedding [B, dim]
|
|
76
|
+
/// - rotaryEmb: Optional RoPE embeddings
|
|
77
|
+
/// - imgModParams: Modulation params for image stream (2 sets: attn, ffn)
|
|
78
|
+
/// - txtModParams: Modulation params for text stream (2 sets: attn, ffn)
|
|
79
|
+
/// - Returns: Updated (text hidden states, image hidden states)
|
|
80
|
+
public func callAsFunction(
|
|
81
|
+
hiddenStates: MLXArray,
|
|
82
|
+
encoderHiddenStates: MLXArray,
|
|
83
|
+
temb: MLXArray,
|
|
84
|
+
rotaryEmb: (cos: MLXArray, sin: MLXArray)? = nil,
|
|
85
|
+
imgModParams: [ModulationParams]? = nil,
|
|
86
|
+
txtModParams: [ModulationParams]? = nil
|
|
87
|
+
) -> (encoderHiddenStates: MLXArray, hiddenStates: MLXArray) {
|
|
88
|
+
// Store residuals
|
|
89
|
+
let residualImg = hiddenStates
|
|
90
|
+
let residualTxt = encoderHiddenStates
|
|
91
|
+
|
|
92
|
+
// --- Attention Block ---
|
|
93
|
+
|
|
94
|
+
// Normalize image hidden states
|
|
95
|
+
var imgNorm = norm1(hiddenStates)
|
|
96
|
+
|
|
97
|
+
// Normalize text hidden states
|
|
98
|
+
var txtNorm = norm1Context(encoderHiddenStates)
|
|
99
|
+
|
|
100
|
+
// Apply modulation if provided
|
|
101
|
+
if let imgMod = imgModParams, imgMod.count >= 1 {
|
|
102
|
+
imgNorm = applyModulation(imgNorm, shift: imgMod[0].shift, scale: imgMod[0].scale)
|
|
103
|
+
}
|
|
104
|
+
if let txtMod = txtModParams, txtMod.count >= 1 {
|
|
105
|
+
txtNorm = applyModulation(txtNorm, shift: txtMod[0].shift, scale: txtMod[0].scale)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Joint attention
|
|
109
|
+
let (imgAttnOut, txtAttnOut) = attn(
|
|
110
|
+
hiddenStates: imgNorm,
|
|
111
|
+
encoderHiddenStates: txtNorm,
|
|
112
|
+
rotaryEmb: rotaryEmb
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
// Apply gate and add residual
|
|
116
|
+
var imgOut: MLXArray
|
|
117
|
+
var txtOut: MLXArray
|
|
118
|
+
|
|
119
|
+
if let imgMod = imgModParams, imgMod.count >= 1 {
|
|
120
|
+
imgOut = residualImg + applyGate(imgAttnOut, gate: imgMod[0].gate)
|
|
121
|
+
} else {
|
|
122
|
+
imgOut = residualImg + imgAttnOut
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if let txtMod = txtModParams, txtMod.count >= 1 {
|
|
126
|
+
txtOut = residualTxt + applyGate(txtAttnOut, gate: txtMod[0].gate)
|
|
127
|
+
} else {
|
|
128
|
+
txtOut = residualTxt + txtAttnOut
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --- FeedForward Block ---
|
|
132
|
+
|
|
133
|
+
// Store new residuals
|
|
134
|
+
let residualImg2 = imgOut
|
|
135
|
+
let residualTxt2 = txtOut
|
|
136
|
+
|
|
137
|
+
// Normalize
|
|
138
|
+
var imgNorm2 = norm2(imgOut)
|
|
139
|
+
var txtNorm2 = norm2Context(txtOut)
|
|
140
|
+
|
|
141
|
+
// Apply modulation if provided
|
|
142
|
+
if let imgMod = imgModParams, imgMod.count >= 2 {
|
|
143
|
+
imgNorm2 = applyModulation(imgNorm2, shift: imgMod[1].shift, scale: imgMod[1].scale)
|
|
144
|
+
}
|
|
145
|
+
if let txtMod = txtModParams, txtMod.count >= 2 {
|
|
146
|
+
txtNorm2 = applyModulation(txtNorm2, shift: txtMod[1].shift, scale: txtMod[1].scale)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// FeedForward
|
|
150
|
+
let imgFFOut = ff(imgNorm2)
|
|
151
|
+
let txtFFOut = ffContext(txtNorm2)
|
|
152
|
+
|
|
153
|
+
// Apply gate and add residual
|
|
154
|
+
if let imgMod = imgModParams, imgMod.count >= 2 {
|
|
155
|
+
imgOut = residualImg2 + applyGate(imgFFOut, gate: imgMod[1].gate)
|
|
156
|
+
} else {
|
|
157
|
+
imgOut = residualImg2 + imgFFOut
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if let txtMod = txtModParams, txtMod.count >= 2 {
|
|
161
|
+
txtOut = residualTxt2 + applyGate(txtFFOut, gate: txtMod[1].gate)
|
|
162
|
+
} else {
|
|
163
|
+
txtOut = residualTxt2 + txtFFOut
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Return order matches diffusers: (encoder_hidden_states, hidden_states)
|
|
167
|
+
return (encoderHiddenStates: txtOut, hiddenStates: imgOut)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/// Forward pass with cached text K/V (for KV cache acceleration)
|
|
171
|
+
/// - Parameters:
|
|
172
|
+
/// - hiddenStates: Image hidden states [B, S_img, dim]
|
|
173
|
+
/// - encoderHiddenStates: Text hidden states [B, S_txt, dim] (used only for Q projection)
|
|
174
|
+
/// - temb: Timestep embedding [B, dim]
|
|
175
|
+
/// - cachedTextKV: Pre-computed text K and V
|
|
176
|
+
/// - rotaryEmb: Optional RoPE embeddings
|
|
177
|
+
/// - imgModParams: Modulation params for image stream
|
|
178
|
+
/// - txtModParams: Modulation params for text stream
|
|
179
|
+
/// - Returns: Updated (text hidden states, image hidden states)
|
|
180
|
+
public func callAsFunction(
|
|
181
|
+
hiddenStates: MLXArray,
|
|
182
|
+
encoderHiddenStates: MLXArray,
|
|
183
|
+
temb: MLXArray,
|
|
184
|
+
cachedTextKV: (k: MLXArray, v: MLXArray),
|
|
185
|
+
rotaryEmb: (cos: MLXArray, sin: MLXArray)? = nil,
|
|
186
|
+
imgModParams: [ModulationParams]? = nil,
|
|
187
|
+
txtModParams: [ModulationParams]? = nil
|
|
188
|
+
) -> (encoderHiddenStates: MLXArray, hiddenStates: MLXArray) {
|
|
189
|
+
let residualImg = hiddenStates
|
|
190
|
+
let residualTxt = encoderHiddenStates
|
|
191
|
+
|
|
192
|
+
// --- Attention Block ---
|
|
193
|
+
|
|
194
|
+
var imgNorm = norm1(hiddenStates)
|
|
195
|
+
var txtNorm = norm1Context(encoderHiddenStates)
|
|
196
|
+
|
|
197
|
+
if let imgMod = imgModParams, imgMod.count >= 1 {
|
|
198
|
+
imgNorm = applyModulation(imgNorm, shift: imgMod[0].shift, scale: imgMod[0].scale)
|
|
199
|
+
}
|
|
200
|
+
if let txtMod = txtModParams, txtMod.count >= 1 {
|
|
201
|
+
txtNorm = applyModulation(txtNorm, shift: txtMod[0].shift, scale: txtMod[0].scale)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Use cached text K/V
|
|
205
|
+
let (imgAttnOut, txtAttnOut) = attn(
|
|
206
|
+
hiddenStates: imgNorm,
|
|
207
|
+
encoderHiddenStates: txtNorm,
|
|
208
|
+
cachedTextKV: cachedTextKV,
|
|
209
|
+
rotaryEmb: rotaryEmb
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
var imgOut: MLXArray
|
|
213
|
+
var txtOut: MLXArray
|
|
214
|
+
|
|
215
|
+
if let imgMod = imgModParams, imgMod.count >= 1 {
|
|
216
|
+
imgOut = residualImg + applyGate(imgAttnOut, gate: imgMod[0].gate)
|
|
217
|
+
} else {
|
|
218
|
+
imgOut = residualImg + imgAttnOut
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if let txtMod = txtModParams, txtMod.count >= 1 {
|
|
222
|
+
txtOut = residualTxt + applyGate(txtAttnOut, gate: txtMod[0].gate)
|
|
223
|
+
} else {
|
|
224
|
+
txtOut = residualTxt + txtAttnOut
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// --- FeedForward Block ---
|
|
228
|
+
|
|
229
|
+
let residualImg2 = imgOut
|
|
230
|
+
let residualTxt2 = txtOut
|
|
231
|
+
|
|
232
|
+
var imgNorm2 = norm2(imgOut)
|
|
233
|
+
var txtNorm2 = norm2Context(txtOut)
|
|
234
|
+
|
|
235
|
+
if let imgMod = imgModParams, imgMod.count >= 2 {
|
|
236
|
+
imgNorm2 = applyModulation(imgNorm2, shift: imgMod[1].shift, scale: imgMod[1].scale)
|
|
237
|
+
}
|
|
238
|
+
if let txtMod = txtModParams, txtMod.count >= 2 {
|
|
239
|
+
txtNorm2 = applyModulation(txtNorm2, shift: txtMod[1].shift, scale: txtMod[1].scale)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let imgFFOut = ff(imgNorm2)
|
|
243
|
+
let txtFFOut = ffContext(txtNorm2)
|
|
244
|
+
|
|
245
|
+
if let imgMod = imgModParams, imgMod.count >= 2 {
|
|
246
|
+
imgOut = residualImg2 + applyGate(imgFFOut, gate: imgMod[1].gate)
|
|
247
|
+
} else {
|
|
248
|
+
imgOut = residualImg2 + imgFFOut
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if let txtMod = txtModParams, txtMod.count >= 2 {
|
|
252
|
+
txtOut = residualTxt2 + applyGate(txtFFOut, gate: txtMod[1].gate)
|
|
253
|
+
} else {
|
|
254
|
+
txtOut = residualTxt2 + txtFFOut
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return (encoderHiddenStates: txtOut, hiddenStates: imgOut)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// Forward pass for text tokens only (for KV cache building)
|
|
261
|
+
/// - Parameters:
|
|
262
|
+
/// - encoderHiddenStates: Text hidden states [B, S_txt, dim]
|
|
263
|
+
/// - temb: Timestep embedding [B, dim]
|
|
264
|
+
/// - txtModParams: Modulation params for text stream
|
|
265
|
+
/// - Returns: Updated text hidden states
|
|
266
|
+
public func forwardTextOnly(
|
|
267
|
+
encoderHiddenStates: MLXArray,
|
|
268
|
+
temb: MLXArray,
|
|
269
|
+
txtModParams: [ModulationParams]? = nil
|
|
270
|
+
) -> MLXArray {
|
|
271
|
+
let residualTxt = encoderHiddenStates
|
|
272
|
+
|
|
273
|
+
// Normalize
|
|
274
|
+
var txtNorm = norm1Context(encoderHiddenStates)
|
|
275
|
+
if let txtMod = txtModParams, txtMod.count >= 1 {
|
|
276
|
+
txtNorm = applyModulation(txtNorm, shift: txtMod[0].shift, scale: txtMod[0].scale)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Text self-attention (using only text K/V, no image interaction)
|
|
280
|
+
// For KV cache building, we approximate by doing a self-attention on text only
|
|
281
|
+
let txtAttnOut = attn.forwardTextSelfAttention(
|
|
282
|
+
encoderHiddenStates: txtNorm
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
var txtOut: MLXArray
|
|
286
|
+
if let txtMod = txtModParams, txtMod.count >= 1 {
|
|
287
|
+
txtOut = residualTxt + applyGate(txtAttnOut, gate: txtMod[0].gate)
|
|
288
|
+
} else {
|
|
289
|
+
txtOut = residualTxt + txtAttnOut
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// FeedForward
|
|
293
|
+
let residualTxt2 = txtOut
|
|
294
|
+
var txtNorm2 = norm2Context(txtOut)
|
|
295
|
+
if let txtMod = txtModParams, txtMod.count >= 2 {
|
|
296
|
+
txtNorm2 = applyModulation(txtNorm2, shift: txtMod[1].shift, scale: txtMod[1].scale)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let txtFFOut = ffContext(txtNorm2)
|
|
300
|
+
if let txtMod = txtModParams, txtMod.count >= 2 {
|
|
301
|
+
txtOut = residualTxt2 + applyGate(txtFFOut, gate: txtMod[1].gate)
|
|
302
|
+
} else {
|
|
303
|
+
txtOut = residualTxt2 + txtFFOut
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return txtOut
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// Stack of Double-Stream Transformer Blocks
|
|
311
|
+
public class Flux2TransformerBlocks: Module, @unchecked Sendable {
|
|
312
|
+
let blocks: [Flux2TransformerBlock]
|
|
313
|
+
|
|
314
|
+
public init(
|
|
315
|
+
numBlocks: Int,
|
|
316
|
+
dim: Int,
|
|
317
|
+
numHeads: Int,
|
|
318
|
+
headDim: Int,
|
|
319
|
+
mlpRatio: Float = 3.0
|
|
320
|
+
) {
|
|
321
|
+
self.blocks = (0..<numBlocks).map { _ in
|
|
322
|
+
Flux2TransformerBlock(
|
|
323
|
+
dim: dim,
|
|
324
|
+
numHeads: numHeads,
|
|
325
|
+
headDim: headDim,
|
|
326
|
+
mlpRatio: mlpRatio
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
public func callAsFunction(
|
|
332
|
+
hiddenStates: MLXArray,
|
|
333
|
+
encoderHiddenStates: MLXArray,
|
|
334
|
+
temb: MLXArray,
|
|
335
|
+
rotaryEmb: (cos: MLXArray, sin: MLXArray)? = nil,
|
|
336
|
+
imgModulation: Flux2Modulation? = nil,
|
|
337
|
+
txtModulation: Flux2Modulation? = nil
|
|
338
|
+
) -> (encoderHiddenStates: MLXArray, hiddenStates: MLXArray) {
|
|
339
|
+
var imgHS = hiddenStates
|
|
340
|
+
var txtHS = encoderHiddenStates
|
|
341
|
+
|
|
342
|
+
for block in blocks {
|
|
343
|
+
// Get modulation params if modulators provided
|
|
344
|
+
let imgParams = imgModulation?(temb)
|
|
345
|
+
let txtParams = txtModulation?(temb)
|
|
346
|
+
|
|
347
|
+
let (newTxt, newImg) = block(
|
|
348
|
+
hiddenStates: imgHS,
|
|
349
|
+
encoderHiddenStates: txtHS,
|
|
350
|
+
temb: temb,
|
|
351
|
+
rotaryEmb: rotaryEmb,
|
|
352
|
+
imgModParams: imgParams,
|
|
353
|
+
txtModParams: txtParams
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
imgHS = newImg
|
|
357
|
+
txtHS = newTxt
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return (encoderHiddenStates: txtHS, hiddenStates: imgHS)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Flux2Debug.swift - Debug and logging utilities
|
|
2
|
+
// Copyright 2025 Vincent Gourbin
|
|
3
|
+
|
|
4
|
+
import Foundation
|
|
5
|
+
|
|
6
|
+
/// Debug utilities for Flux.2
|
|
7
|
+
public enum Flux2Debug {
|
|
8
|
+
public typealias LogSink = @Sendable (Level, String) -> Void
|
|
9
|
+
|
|
10
|
+
/// Enable/disable debug logging
|
|
11
|
+
nonisolated(unsafe) public static var enabled: Bool = true
|
|
12
|
+
|
|
13
|
+
nonisolated(unsafe) private static var logSink: LogSink?
|
|
14
|
+
|
|
15
|
+
/// Log level
|
|
16
|
+
public enum Level: Int, Comparable, Sendable {
|
|
17
|
+
case verbose = 0 // Detailed debug info
|
|
18
|
+
case info = 1 // General progress
|
|
19
|
+
case warning = 2 // Warnings
|
|
20
|
+
case error = 3 // Errors only
|
|
21
|
+
|
|
22
|
+
public static func < (lhs: Level, rhs: Level) -> Bool {
|
|
23
|
+
lhs.rawValue < rhs.rawValue
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Minimum log level to display (default: info - show progress and above)
|
|
28
|
+
nonisolated(unsafe) public static var minLevel: Level = .info
|
|
29
|
+
|
|
30
|
+
/// Enable debug mode (shows all logs including verbose)
|
|
31
|
+
public static func enableDebugMode() {
|
|
32
|
+
enabled = true
|
|
33
|
+
minLevel = .verbose
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Set to normal mode (only show warnings and errors)
|
|
37
|
+
public static func setNormalMode() {
|
|
38
|
+
enabled = true
|
|
39
|
+
minLevel = .warning
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static func setLogSink(_ sink: LogSink?) {
|
|
43
|
+
logSink = sink
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Log a debug message
|
|
47
|
+
public static func log(_ message: String, level: Level = .info) {
|
|
48
|
+
guard enabled, level >= minLevel else { return }
|
|
49
|
+
|
|
50
|
+
let prefix: String
|
|
51
|
+
switch level {
|
|
52
|
+
case .verbose:
|
|
53
|
+
prefix = "[Flux2:V]"
|
|
54
|
+
case .info:
|
|
55
|
+
prefix = "[Flux2]"
|
|
56
|
+
case .warning:
|
|
57
|
+
prefix = "[Flux2:W]"
|
|
58
|
+
case .error:
|
|
59
|
+
prefix = "[Flux2:E]"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
print("\(prefix) \(message)")
|
|
63
|
+
fflush(stdout)
|
|
64
|
+
logSink?(level, message)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Convenience methods
|
|
68
|
+
public static func verbose(_ message: String) {
|
|
69
|
+
log(message, level: .verbose)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public static func info(_ message: String) {
|
|
73
|
+
log(message, level: .info)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public static func warning(_ message: String) {
|
|
77
|
+
log(message, level: .warning)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public static func error(_ message: String) {
|
|
81
|
+
log(message, level: .error)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Log with timing
|
|
85
|
+
public static func timed<T>(_ label: String, _ block: () throws -> T) rethrows -> T {
|
|
86
|
+
let start = CFAbsoluteTimeGetCurrent()
|
|
87
|
+
let result = try block()
|
|
88
|
+
let elapsed = CFAbsoluteTimeGetCurrent() - start
|
|
89
|
+
log("\(label): \(String(format: "%.3f", elapsed))s")
|
|
90
|
+
return result
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Async version of timed
|
|
94
|
+
public static func timedAsync<T>(_ label: String, _ block: () async throws -> T) async rethrows -> T {
|
|
95
|
+
let start = CFAbsoluteTimeGetCurrent()
|
|
96
|
+
let result = try await block()
|
|
97
|
+
let elapsed = CFAbsoluteTimeGetCurrent() - start
|
|
98
|
+
log("\(label): \(String(format: "%.3f", elapsed))s")
|
|
99
|
+
return result
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// MLXCheckpoint.swift - Wrapper for mlx_checkpoint C function
|
|
2
|
+
// Enables gradient checkpointing to reduce memory usage during training
|
|
3
|
+
|
|
4
|
+
import Cmlx
|
|
5
|
+
import MLX
|
|
6
|
+
|
|
7
|
+
/// Wraps a function with gradient checkpointing.
|
|
8
|
+
///
|
|
9
|
+
/// During the forward pass, intermediate activations are not stored.
|
|
10
|
+
/// During the backward pass, they are recomputed as needed.
|
|
11
|
+
/// This trades compute time for memory savings (~30-50% less memory).
|
|
12
|
+
///
|
|
13
|
+
/// Usage:
|
|
14
|
+
/// ```swift
|
|
15
|
+
/// let checkpointedBlock = checkpoint { inputs in
|
|
16
|
+
/// return myTransformerBlock(inputs)
|
|
17
|
+
/// }
|
|
18
|
+
/// let output = checkpointedBlock(inputs)
|
|
19
|
+
/// ```
|
|
20
|
+
///
|
|
21
|
+
/// - Parameter f: The function to wrap with checkpointing
|
|
22
|
+
/// - Returns: A checkpointed version of the function
|
|
23
|
+
public func checkpoint(_ f: @escaping ([MLXArray]) -> [MLXArray]) -> ([MLXArray]) -> [MLXArray] {
|
|
24
|
+
// Create C closure from Swift function
|
|
25
|
+
let inputClosure = new_mlx_closure(f)
|
|
26
|
+
|
|
27
|
+
// Create checkpointed closure
|
|
28
|
+
var checkpointedClosure = mlx_closure_new()
|
|
29
|
+
let status = mlx_checkpoint(&checkpointedClosure, inputClosure)
|
|
30
|
+
|
|
31
|
+
// Free the input closure (checkpointed version has its own reference)
|
|
32
|
+
mlx_closure_free(inputClosure)
|
|
33
|
+
|
|
34
|
+
guard status == 0 else {
|
|
35
|
+
fatalError("mlx_checkpoint failed with status \(status)")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Return a Swift function that invokes the checkpointed closure
|
|
39
|
+
return { (inputs: [MLXArray]) -> [MLXArray] in
|
|
40
|
+
// Convert inputs to C vector
|
|
41
|
+
let inputVec = new_mlx_vector_array(inputs)
|
|
42
|
+
defer { mlx_vector_array_free(inputVec) }
|
|
43
|
+
|
|
44
|
+
// Call the checkpointed closure
|
|
45
|
+
var outputVec = mlx_vector_array_new()
|
|
46
|
+
let applyStatus = mlx_closure_apply(&outputVec, checkpointedClosure, inputVec)
|
|
47
|
+
defer { mlx_vector_array_free(outputVec) }
|
|
48
|
+
|
|
49
|
+
guard applyStatus == 0 else {
|
|
50
|
+
fatalError("mlx_closure_apply failed with status \(applyStatus)")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Convert output back to Swift arrays
|
|
54
|
+
return mlx_vector_array_values(outputVec)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Convenience overload for single input/output
|
|
59
|
+
public func checkpoint(_ f: @escaping (MLXArray) -> MLXArray) -> (MLXArray) -> MLXArray {
|
|
60
|
+
let wrapped = checkpoint { (arrays: [MLXArray]) -> [MLXArray] in
|
|
61
|
+
return [f(arrays[0])]
|
|
62
|
+
}
|
|
63
|
+
return { input in wrapped([input])[0] }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// MARK: - Helper functions (copied from MLX internal, needed for C interop)
|
|
67
|
+
|
|
68
|
+
/// Create a new mlx_vector_array from Swift arrays
|
|
69
|
+
private func new_mlx_vector_array(_ arrays: [MLXArray]) -> mlx_vector_array {
|
|
70
|
+
withExtendedLifetime(arrays) {
|
|
71
|
+
mlx_vector_array_new_data(arrays.map { $0.ctx }, arrays.count)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Convert mlx_vector_array to Swift array
|
|
76
|
+
private func mlx_vector_array_values(_ vector_array: mlx_vector_array) -> [MLXArray] {
|
|
77
|
+
(0 ..< mlx_vector_array_size(vector_array))
|
|
78
|
+
.map { index in
|
|
79
|
+
var ctx = mlx_array_new()
|
|
80
|
+
mlx_vector_array_get(&ctx, vector_array, index)
|
|
81
|
+
return MLXArray(ctx)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Create a mlx_closure from a Swift function
|
|
86
|
+
private func new_mlx_closure(_ f: @escaping ([MLXArray]) -> [MLXArray]) -> mlx_closure {
|
|
87
|
+
class ClosureCaptureState {
|
|
88
|
+
let f: ([MLXArray]) -> [MLXArray]
|
|
89
|
+
init(_ f: @escaping ([MLXArray]) -> [MLXArray]) {
|
|
90
|
+
self.f = f
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func free(ptr: UnsafeMutableRawPointer?) {
|
|
95
|
+
Unmanaged<ClosureCaptureState>.fromOpaque(ptr!).release()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let payload = Unmanaged.passRetained(ClosureCaptureState(f)).toOpaque()
|
|
99
|
+
|
|
100
|
+
func trampoline(
|
|
101
|
+
resultOut: UnsafeMutablePointer<mlx_vector_array>?,
|
|
102
|
+
vector_array: mlx_vector_array,
|
|
103
|
+
payload: UnsafeMutableRawPointer?
|
|
104
|
+
) -> Int32 {
|
|
105
|
+
let state = Unmanaged<ClosureCaptureState>.fromOpaque(payload!).takeUnretainedValue()
|
|
106
|
+
let arrays = mlx_vector_array_values(vector_array)
|
|
107
|
+
let result = state.f(arrays)
|
|
108
|
+
|
|
109
|
+
if let resultOut {
|
|
110
|
+
resultOut.pointee = new_mlx_vector_array(result)
|
|
111
|
+
} else {
|
|
112
|
+
fatalError("no resultOut pointer")
|
|
113
|
+
}
|
|
114
|
+
return 0
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return mlx_closure_new_func_payload(trampoline, payload, free)
|
|
118
|
+
}
|