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,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
+ }