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