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,198 @@
|
|
|
1
|
+
// Flux2FusedKernels.swift - Custom Metal kernels for performance-critical operations
|
|
2
|
+
// Copyright 2025 Vincent Gourbin
|
|
3
|
+
//
|
|
4
|
+
// The RoPE kernel implementation is inspired by mzbac/flux2.swift
|
|
5
|
+
// (https://github.com/mzbac/flux2.swift), licensed under Apache 2.0.
|
|
6
|
+
// This implementation has been adapted for [B, H, S, D] tensor layout
|
|
7
|
+
// (original uses [B, S, H, D]) and integrated with our caching system.
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
import MLX
|
|
11
|
+
|
|
12
|
+
/// Collection of fused Metal kernels for performance-critical transformer operations
|
|
13
|
+
///
|
|
14
|
+
/// These kernels fuse multiple operations into a single GPU dispatch, reducing
|
|
15
|
+
/// kernel launch overhead and improving memory locality.
|
|
16
|
+
public enum Flux2FusedKernels {
|
|
17
|
+
/// Set to true to disable fused kernels (for debugging/comparison)
|
|
18
|
+
/// Can also be set via environment variable FLUX2_DISABLE_FUSED_KERNELS=1
|
|
19
|
+
nonisolated(unsafe) public static var disabled: Bool = {
|
|
20
|
+
ProcessInfo.processInfo.environment["FLUX2_DISABLE_FUSED_KERNELS"] == "1"
|
|
21
|
+
}()
|
|
22
|
+
/// Number of attention heads processed per thread for RoPE kernel
|
|
23
|
+
private static let ropeHeadsPerThread = 4
|
|
24
|
+
|
|
25
|
+
/// Custom Metal kernel for applying rotary position embeddings
|
|
26
|
+
///
|
|
27
|
+
/// This kernel fuses the rotation operation (x * cos + rotated_x * sin) into a single
|
|
28
|
+
/// GPU kernel, processing multiple heads per thread for efficiency.
|
|
29
|
+
///
|
|
30
|
+
/// Input layout: [B, H, S, D] where B=batch, H=heads, S=sequence, D=head_dim
|
|
31
|
+
/// Cos/Sin layout: [S, D] or [B, S, D]
|
|
32
|
+
private static let ropeKernel = MLXFast.metalKernel(
|
|
33
|
+
name: "flux2_apply_rope_bhsd",
|
|
34
|
+
inputNames: ["x", "cos", "sin", "batch", "heads", "seq", "head_dim", "rotary_batch_stride"],
|
|
35
|
+
outputNames: ["out"],
|
|
36
|
+
source: """
|
|
37
|
+
uint dp = thread_position_in_grid.x; // dimension pair index (0..half_dim-1)
|
|
38
|
+
uint seq_idx = thread_position_in_grid.y; // sequence position
|
|
39
|
+
uint z = thread_position_in_grid.z; // batch * head_groups
|
|
40
|
+
|
|
41
|
+
uint half_dim = uint(head_dim / 2);
|
|
42
|
+
if (dp >= half_dim || seq_idx >= uint(seq)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
int head_groups = (heads + (N - 1)) / N;
|
|
47
|
+
int batch_idx = int(z) / head_groups;
|
|
48
|
+
int head_group = int(z) - batch_idx * head_groups;
|
|
49
|
+
if (batch_idx >= batch) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
int head_base = head_group * N;
|
|
54
|
+
|
|
55
|
+
// cos/sin layout: [S, D] if rotary_batch_stride=0, else [B, S, D]
|
|
56
|
+
ulong row_base = (ulong(seq_idx) + ulong(batch_idx) * ulong(rotary_batch_stride)) * ulong(head_dim);
|
|
57
|
+
device const float* cos_row = cos + row_base;
|
|
58
|
+
device const float* sin_row = sin + row_base;
|
|
59
|
+
|
|
60
|
+
uint d0 = dp * 2;
|
|
61
|
+
float c = cos_row[d0];
|
|
62
|
+
float s = sin_row[d0];
|
|
63
|
+
|
|
64
|
+
// For [B, H, S, D] layout:
|
|
65
|
+
// x[batch][head][seq][dim] = x[batch * H * S * D + head * S * D + seq * D + dim]
|
|
66
|
+
ulong stride_S = ulong(head_dim);
|
|
67
|
+
ulong stride_H = ulong(seq) * stride_S;
|
|
68
|
+
ulong stride_B = ulong(heads) * stride_H;
|
|
69
|
+
|
|
70
|
+
for (int i = 0; i < N; ++i) {
|
|
71
|
+
int head_idx = head_base + i;
|
|
72
|
+
if (head_idx >= heads) {
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
ulong base = ulong(batch_idx) * stride_B + ulong(head_idx) * stride_H + ulong(seq_idx) * stride_S + ulong(d0);
|
|
77
|
+
float x0 = float(x[base]);
|
|
78
|
+
float x1 = float(x[base + 1]);
|
|
79
|
+
|
|
80
|
+
// Apply rotation: [x0, x1] * [[c, -s], [s, c]]
|
|
81
|
+
// Result: [x0*c - x1*s, x0*s + x1*c]
|
|
82
|
+
// But diffusers uses: x * cos + rotate_half(x) * sin
|
|
83
|
+
// where rotate_half gives [-x1, x0] for consecutive pairs
|
|
84
|
+
// So: [x0, x1] -> [x0*c - x1*s, x1*c + x0*s]
|
|
85
|
+
out[base] = T(x0 * c - x1 * s);
|
|
86
|
+
out[base + 1] = T(x1 * c + x0 * s);
|
|
87
|
+
}
|
|
88
|
+
"""
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
/// Apply rotary embeddings using the fused Metal kernel
|
|
92
|
+
///
|
|
93
|
+
/// - Parameters:
|
|
94
|
+
/// - x: Input tensor [B, H, S, D] in bfloat16, float16, or float32
|
|
95
|
+
/// - cos: Cosine embeddings [S, D] or [B, S, D] in float32
|
|
96
|
+
/// - sin: Sine embeddings [S, D] or [B, S, D] in float32
|
|
97
|
+
/// - Returns: Rotated tensor [B, H, S, D] in same dtype as input, or nil if kernel can't be used
|
|
98
|
+
public static func applyRotaryEmb(
|
|
99
|
+
_ x: MLXArray,
|
|
100
|
+
cos: MLXArray,
|
|
101
|
+
sin: MLXArray
|
|
102
|
+
) -> MLXArray? {
|
|
103
|
+
// Allow disabling for debugging
|
|
104
|
+
guard !disabled else { return nil }
|
|
105
|
+
|
|
106
|
+
// Only use kernel on GPU
|
|
107
|
+
guard Device.defaultDevice().deviceType == .gpu else {
|
|
108
|
+
return nil
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Validate dtypes
|
|
112
|
+
guard x.dtype == .bfloat16 || x.dtype == .float16 || x.dtype == .float32,
|
|
113
|
+
cos.dtype == .float32,
|
|
114
|
+
sin.dtype == .float32
|
|
115
|
+
else {
|
|
116
|
+
return nil
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Validate x shape: [B, H, S, D]
|
|
120
|
+
guard x.ndim == 4 else {
|
|
121
|
+
return nil
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let batch = x.dim(0)
|
|
125
|
+
let heads = x.dim(1)
|
|
126
|
+
let seq = x.dim(2)
|
|
127
|
+
let headDim = x.dim(3)
|
|
128
|
+
|
|
129
|
+
// Validate cos/sin shape: [S, D] or [B, S, D]
|
|
130
|
+
guard (cos.ndim == 2 || cos.ndim == 3), cos.ndim == sin.ndim else { return nil }
|
|
131
|
+
|
|
132
|
+
let batchStride: Int32
|
|
133
|
+
if cos.ndim == 2 {
|
|
134
|
+
// [S, D] - shared across batch
|
|
135
|
+
guard cos.dim(0) == seq, sin.dim(0) == seq else { return nil }
|
|
136
|
+
guard cos.dim(1) == headDim, sin.dim(1) == headDim else { return nil }
|
|
137
|
+
batchStride = 0
|
|
138
|
+
} else if cos.ndim == 3 {
|
|
139
|
+
// [B, S, D] - per-batch embeddings
|
|
140
|
+
guard cos.dim(0) == batch, sin.dim(0) == batch else { return nil }
|
|
141
|
+
guard cos.dim(1) == seq, sin.dim(1) == seq else { return nil }
|
|
142
|
+
guard cos.dim(2) == headDim, sin.dim(2) == headDim else { return nil }
|
|
143
|
+
batchStride = Int32(seq)
|
|
144
|
+
} else {
|
|
145
|
+
return nil
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Head dimension must be even for rotation
|
|
149
|
+
guard headDim % 2 == 0 else {
|
|
150
|
+
return nil
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let halfDim = headDim / 2
|
|
154
|
+
let headsPerThread = ropeHeadsPerThread
|
|
155
|
+
let headGroups = (heads + headsPerThread - 1) / headsPerThread
|
|
156
|
+
|
|
157
|
+
// Thread group sizing
|
|
158
|
+
let tgX = max(min(32, halfDim), 1)
|
|
159
|
+
let tgY = max(min(4, seq), 1)
|
|
160
|
+
|
|
161
|
+
let out = ropeKernel(
|
|
162
|
+
[
|
|
163
|
+
x,
|
|
164
|
+
cos,
|
|
165
|
+
sin,
|
|
166
|
+
Int32(batch),
|
|
167
|
+
Int32(heads),
|
|
168
|
+
Int32(seq),
|
|
169
|
+
Int32(headDim),
|
|
170
|
+
batchStride,
|
|
171
|
+
],
|
|
172
|
+
template: [
|
|
173
|
+
("T", x.dtype),
|
|
174
|
+
("N", headsPerThread),
|
|
175
|
+
],
|
|
176
|
+
grid: (halfDim, seq, batch * headGroups),
|
|
177
|
+
threadGroup: (tgX, tgY, 1),
|
|
178
|
+
outputShapes: [x.shape],
|
|
179
|
+
outputDTypes: [x.dtype],
|
|
180
|
+
stream: .gpu
|
|
181
|
+
)
|
|
182
|
+
return out[0]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// Check if the fused RoPE kernel is available for the given configuration
|
|
186
|
+
public static func isRopeKernelAvailable(
|
|
187
|
+
xDtype: DType,
|
|
188
|
+
xNdim: Int,
|
|
189
|
+
cosNdim: Int,
|
|
190
|
+
sinNdim: Int
|
|
191
|
+
) -> Bool {
|
|
192
|
+
guard Device.defaultDevice().deviceType == .gpu else { return false }
|
|
193
|
+
guard xDtype == .bfloat16 || xDtype == .float16 || xDtype == .float32 else { return false }
|
|
194
|
+
guard xNdim == 4 else { return false }
|
|
195
|
+
guard (cosNdim == 2 || cosNdim == 3), cosNdim == sinNdim else { return false }
|
|
196
|
+
return true
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Flux2KVCache.swift - KV Cache for Klein 9B acceleration
|
|
2
|
+
// Copyright 2025 Vincent Gourbin
|
|
3
|
+
|
|
4
|
+
import Foundation
|
|
5
|
+
import MLX
|
|
6
|
+
|
|
7
|
+
/// KV Cache for a single attention layer
|
|
8
|
+
/// Stores the key and value projections for reference tokens
|
|
9
|
+
public struct Flux2KVCacheLayer {
|
|
10
|
+
public let k: MLXArray // [B, numHeads, refSeqLen, headDim]
|
|
11
|
+
public let v: MLXArray // [B, numHeads, refSeqLen, headDim]
|
|
12
|
+
|
|
13
|
+
public init(k: MLXArray, v: MLXArray) {
|
|
14
|
+
self.k = k
|
|
15
|
+
self.v = v
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// Complete KV Cache for all transformer layers
|
|
20
|
+
/// Used to skip reference token re-processing after step 0
|
|
21
|
+
public struct Flux2KVCache {
|
|
22
|
+
public let doubleStreamLayers: [Flux2KVCacheLayer] // 8 layers
|
|
23
|
+
public let singleStreamLayers: [Flux2KVCacheLayer] // 48 layers
|
|
24
|
+
public let layerCount: Int
|
|
25
|
+
|
|
26
|
+
public init(doubleStreamLayers: [Flux2KVCacheLayer], singleStreamLayers: [Flux2KVCacheLayer]) {
|
|
27
|
+
self.doubleStreamLayers = doubleStreamLayers
|
|
28
|
+
self.singleStreamLayers = singleStreamLayers
|
|
29
|
+
self.layerCount = doubleStreamLayers.count + singleStreamLayers.count
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public var arrays: [MLXArray] {
|
|
33
|
+
doubleStreamLayers.flatMap { [$0.k, $0.v] } +
|
|
34
|
+
singleStreamLayers.flatMap { [$0.k, $0.v] }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Flux2Modulation.swift - AdaLN Modulation for Flux.2
|
|
2
|
+
// Copyright 2025 Vincent Gourbin
|
|
3
|
+
|
|
4
|
+
import Foundation
|
|
5
|
+
import MLX
|
|
6
|
+
import MLXNN
|
|
7
|
+
|
|
8
|
+
/// Modulation parameters for a single AdaLN operation
|
|
9
|
+
public struct ModulationParams {
|
|
10
|
+
/// Shift parameter for centering
|
|
11
|
+
public let shift: MLXArray
|
|
12
|
+
/// Scale parameter for variance
|
|
13
|
+
public let scale: MLXArray
|
|
14
|
+
/// Gate parameter for residual connection
|
|
15
|
+
public let gate: MLXArray
|
|
16
|
+
|
|
17
|
+
public init(shift: MLXArray, scale: MLXArray, gate: MLXArray) {
|
|
18
|
+
self.shift = shift
|
|
19
|
+
self.scale = scale
|
|
20
|
+
self.gate = gate
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Adaptive Layer Normalization Modulation
|
|
25
|
+
///
|
|
26
|
+
/// Generates (shift, scale, gate) parameters from timestep embeddings
|
|
27
|
+
/// for adaptive layer normalization in transformer blocks.
|
|
28
|
+
public class Flux2Modulation: Module, @unchecked Sendable {
|
|
29
|
+
let dim: Int
|
|
30
|
+
let numSets: Int
|
|
31
|
+
@ModuleInfo var linear: Linear
|
|
32
|
+
|
|
33
|
+
/// Initialize modulation layer
|
|
34
|
+
/// - Parameters:
|
|
35
|
+
/// - dim: Model dimension (6144 for Flux.2)
|
|
36
|
+
/// - numSets: Number of modulation parameter sets (e.g., 6 for double-stream, 3 for single)
|
|
37
|
+
public init(dim: Int, numSets: Int) {
|
|
38
|
+
self.dim = dim
|
|
39
|
+
self.numSets = numSets
|
|
40
|
+
// Each set has 3 parameters: shift, scale, gate
|
|
41
|
+
// So output dim = dim * 3 * numSets
|
|
42
|
+
// IMPORTANT: bias=false to match diffusers checkpoint
|
|
43
|
+
self.linear = Linear(dim, dim * 3 * numSets, bias: false)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Generate modulation parameters
|
|
47
|
+
/// - Parameter embedding: Timestep/conditioning embedding [B, dim]
|
|
48
|
+
/// - Returns: Array of ModulationParams, one for each set
|
|
49
|
+
public func callAsFunction(_ embedding: MLXArray) -> [ModulationParams] {
|
|
50
|
+
Flux2Debug.verbose("Modulation input shape: \(embedding.shape)")
|
|
51
|
+
|
|
52
|
+
// Apply SiLU activation before projection (common in DiT-style models)
|
|
53
|
+
let activated = silu(embedding)
|
|
54
|
+
Flux2Debug.verbose("After silu: \(activated.shape)")
|
|
55
|
+
|
|
56
|
+
// Project to all modulation parameters
|
|
57
|
+
let allParams = linear(activated) // [B, dim * 3 * numSets]
|
|
58
|
+
Flux2Debug.verbose("After linear (dim=\(dim), numSets=\(numSets)): \(allParams.shape)")
|
|
59
|
+
|
|
60
|
+
let batchSize = allParams.shape[0]
|
|
61
|
+
let reshapedParams = allParams.reshaped([batchSize, numSets, 3, dim])
|
|
62
|
+
Flux2Debug.verbose("After reshape: \(reshapedParams.shape)")
|
|
63
|
+
|
|
64
|
+
// Split into individual parameter sets
|
|
65
|
+
var result: [ModulationParams] = []
|
|
66
|
+
|
|
67
|
+
for i in 0..<numSets {
|
|
68
|
+
let shift = reshapedParams[0..., i, 0, 0...]
|
|
69
|
+
let scale = reshapedParams[0..., i, 1, 0...]
|
|
70
|
+
let gate = reshapedParams[0..., i, 2, 0...]
|
|
71
|
+
|
|
72
|
+
Flux2Debug.verbose("Set \(i) - shift: \(shift.shape), scale: \(scale.shape), gate: \(gate.shape)")
|
|
73
|
+
|
|
74
|
+
result.append(ModulationParams(shift: shift, scale: scale, gate: gate))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Generate modulation parameters as a flat array for efficient processing
|
|
81
|
+
/// - Parameter embedding: Timestep/conditioning embedding [B, dim]
|
|
82
|
+
/// - Returns: Tensor [B, numSets, 3, dim] containing all shift/scale/gate
|
|
83
|
+
public func forwardFlat(_ embedding: MLXArray) -> MLXArray {
|
|
84
|
+
let activated = silu(embedding)
|
|
85
|
+
let allParams = linear(activated) // [B, dim * 3 * numSets]
|
|
86
|
+
|
|
87
|
+
// Reshape to [B, numSets, 3, dim]
|
|
88
|
+
let batchSize = allParams.shape[0]
|
|
89
|
+
return allParams.reshaped([batchSize, numSets, 3, dim])
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Apply AdaLN modulation to normalized input
|
|
94
|
+
/// - Parameters:
|
|
95
|
+
/// - x: Normalized input [B, S, dim]
|
|
96
|
+
/// - shift: Shift parameter [B, dim]
|
|
97
|
+
/// - scale: Scale parameter [B, dim]
|
|
98
|
+
/// - Returns: Modulated output [B, S, dim]
|
|
99
|
+
public func applyModulation(
|
|
100
|
+
_ x: MLXArray,
|
|
101
|
+
shift: MLXArray,
|
|
102
|
+
scale: MLXArray
|
|
103
|
+
) -> MLXArray {
|
|
104
|
+
Flux2Debug.verbose("applyModulation - x: \(x.shape), shift: \(shift.shape), scale: \(scale.shape)")
|
|
105
|
+
|
|
106
|
+
// Expand shift/scale for sequence dimension
|
|
107
|
+
// x: [B, S, dim], shift/scale: [B, dim]
|
|
108
|
+
let shiftExpanded = shift.expandedDimensions(axis: 1) // [B, 1, dim]
|
|
109
|
+
let scaleExpanded = scale.expandedDimensions(axis: 1) // [B, 1, dim]
|
|
110
|
+
|
|
111
|
+
Flux2Debug.verbose("Expanded - shift: \(shiftExpanded.shape), scale: \(scaleExpanded.shape)")
|
|
112
|
+
|
|
113
|
+
// Apply: x * (1 + scale) + shift
|
|
114
|
+
return x * (1 + scaleExpanded) + shiftExpanded
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Apply gating to residual connection
|
|
118
|
+
/// - Parameters:
|
|
119
|
+
/// - residual: Residual to be gated [B, S, dim]
|
|
120
|
+
/// - gate: Gate parameter [B, dim]
|
|
121
|
+
/// - Returns: Gated residual [B, S, dim]
|
|
122
|
+
public func applyGate(_ residual: MLXArray, gate: MLXArray) -> MLXArray {
|
|
123
|
+
let gateExpanded = gate.expandedDimensions(axis: 1) // [B, 1, dim]
|
|
124
|
+
return residual * gateExpanded
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Adaptive Layer Norm with continuous embedding (for final output)
|
|
128
|
+
public class AdaLayerNormContinuous: Module, @unchecked Sendable {
|
|
129
|
+
let dim: Int
|
|
130
|
+
let norm: LayerNorm
|
|
131
|
+
@ModuleInfo var linear: Linear
|
|
132
|
+
|
|
133
|
+
public init(dim: Int, eps: Float = 1e-6, usesBias: Bool = false) {
|
|
134
|
+
self.dim = dim
|
|
135
|
+
self.norm = LayerNorm(dimensions: dim, eps: eps, affine: false)
|
|
136
|
+
self._linear = ModuleInfo(wrappedValue: Linear(dim, dim * 2, bias: usesBias))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// Apply adaptive layer norm
|
|
140
|
+
/// - Parameters:
|
|
141
|
+
/// - x: Input tensor [B, S, dim]
|
|
142
|
+
/// - conditioning: Conditioning embedding [B, dim]
|
|
143
|
+
/// - Returns: Normalized and modulated output [B, S, dim]
|
|
144
|
+
public func callAsFunction(_ x: MLXArray, conditioning: MLXArray) -> MLXArray {
|
|
145
|
+
// Get scale and shift from conditioning
|
|
146
|
+
// IMPORTANT: diffusers order is linear(silu(x)), NOT silu(linear(x))
|
|
147
|
+
// And the split order is (scale, shift), NOT (shift, scale)
|
|
148
|
+
let params = linear(silu(conditioning)) // [B, dim * 2]
|
|
149
|
+
let scale = params[0..., 0..<dim]
|
|
150
|
+
let shift = params[0..., dim...]
|
|
151
|
+
|
|
152
|
+
// Normalize
|
|
153
|
+
let normalized = norm(x)
|
|
154
|
+
|
|
155
|
+
// Apply modulation
|
|
156
|
+
return applyModulation(normalized, shift: shift, scale: scale)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// Modulation for double-stream blocks
|
|
161
|
+
/// Generates 6 sets of parameters: 2 for image attention, 2 for image FFN, 2 for text
|
|
162
|
+
public class DoubleStreamModulation: Module, @unchecked Sendable {
|
|
163
|
+
let imgModulation: Flux2Modulation
|
|
164
|
+
let txtModulation: Flux2Modulation
|
|
165
|
+
|
|
166
|
+
public init(dim: Int) {
|
|
167
|
+
// Image branch: attn (shift, scale, gate) + ffn (shift, scale, gate) = 2 sets * 3
|
|
168
|
+
self.imgModulation = Flux2Modulation(dim: dim, numSets: 2)
|
|
169
|
+
// Text branch: attn (shift, scale, gate) + ffn (shift, scale, gate) = 2 sets * 3
|
|
170
|
+
self.txtModulation = Flux2Modulation(dim: dim, numSets: 2)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public func callAsFunction(_ embedding: MLXArray) -> (img: [ModulationParams], txt: [ModulationParams]) {
|
|
174
|
+
let imgParams = imgModulation(embedding)
|
|
175
|
+
let txtParams = txtModulation(embedding)
|
|
176
|
+
return (img: imgParams, txt: txtParams)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Modulation for single-stream blocks
|
|
181
|
+
/// Generates 2 sets: attention and FFN (fused in parallel attention)
|
|
182
|
+
public class SingleStreamModulation: Module, @unchecked Sendable {
|
|
183
|
+
let modulation: Flux2Modulation
|
|
184
|
+
|
|
185
|
+
public init(dim: Int) {
|
|
186
|
+
// Single stream: combined attn+ffn modulation = 1 set * 3
|
|
187
|
+
self.modulation = Flux2Modulation(dim: dim, numSets: 1)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
public func callAsFunction(_ embedding: MLXArray) -> [ModulationParams] {
|
|
191
|
+
modulation(embedding)
|
|
192
|
+
}
|
|
193
|
+
}
|