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