bleam 0.0.8 → 0.0.10
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 +269 -22
- package/dist/cli.d.cts +32 -2
- package/dist/cli.d.ts +32 -2
- package/dist/cli.js +270 -23
- package/dist/platform.d.cts +2 -0
- package/dist/platform.d.ts +2 -0
- package/dist/{ui-1WepaMS4.d.cts → ui-Bg11tvlc.d.ts} +1 -1
- package/dist/{ui-Ce-pnUUA.d.ts → ui-Dd7SXdbg.d.cts} +7 -7
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/window.d.cts +1 -1
- package/dist/window.d.ts +1 -1
- package/package.json +5 -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 +482 -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 +1147 -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/main.swift +726 -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 +329 -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/templates/updates/README.md +272 -0
- package/templates/updates/src/index.ts +385 -0
- package/templates/updates/src/schema.ts +385 -0
- package/templates/updates/tsconfig.json +12 -0
- package/templates/updates/wrangler.jsonc +19 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import NativeScript from '@nativescript/react-native'
|
|
2
|
+
import { StyleSheet, Text, View } from 'react-native'
|
|
3
|
+
|
|
4
|
+
NativeScript.init()
|
|
5
|
+
|
|
6
|
+
export default function App() {
|
|
7
|
+
return (
|
|
8
|
+
<View style={styles.canvas}>
|
|
9
|
+
<Text style={styles.title}>Bleam</Text>
|
|
10
|
+
<Text style={styles.message}>
|
|
11
|
+
Run a Bleam project with npx bleam new or bleam dev.
|
|
12
|
+
</Text>
|
|
13
|
+
</View>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const styles = StyleSheet.create({
|
|
18
|
+
canvas: {
|
|
19
|
+
flex: 1,
|
|
20
|
+
alignItems: 'center',
|
|
21
|
+
justifyContent: 'center',
|
|
22
|
+
backgroundColor: '#0b0b0b',
|
|
23
|
+
},
|
|
24
|
+
title: { color: '#f5f5f5', fontSize: 28, fontWeight: '700' },
|
|
25
|
+
message: { color: '#888', fontSize: 14, marginTop: 10 },
|
|
26
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { registerRootComponent } from 'expo'
|
|
2
|
+
|
|
3
|
+
import App from './App'
|
|
4
|
+
|
|
5
|
+
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
|
|
6
|
+
// It also ensures that whether you load the app in Expo Go or in a native build,
|
|
7
|
+
// the environment is set up appropriately
|
|
8
|
+
registerRootComponent(App)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# This `.xcode.env` file is versioned and is used to source the environment
|
|
2
|
+
# used when running script phases inside Xcode.
|
|
3
|
+
# To customize your local environment, you can create an `.xcode.env.local`
|
|
4
|
+
# file that is not versioned.
|
|
5
|
+
|
|
6
|
+
# NODE_BINARY variable contains the PATH to the node executable.
|
|
7
|
+
#
|
|
8
|
+
# Customize the NODE_BINARY variable here.
|
|
9
|
+
# For example, to use nvm with brew, add the following line
|
|
10
|
+
# . "$(brew --prefix nvm)/nvm.sh" --no-use
|
|
11
|
+
export NODE_BINARY=$(command -v node)
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// Flux2Config.swift - Flux.2 Transformer Configuration
|
|
2
|
+
// Copyright 2025 Vincent Gourbin
|
|
3
|
+
|
|
4
|
+
import Foundation
|
|
5
|
+
|
|
6
|
+
// MARK: - Model Selection
|
|
7
|
+
|
|
8
|
+
/// Flux.2 model variants
|
|
9
|
+
public enum Flux2Model: String, CaseIterable, Sendable {
|
|
10
|
+
/// Flux.2 Klein 4B - 4B parameters, Qwen3-4B text encoder (Apache 2.0)
|
|
11
|
+
case klein4B = "klein-4b"
|
|
12
|
+
/// Flux.2 Klein 9B - 9B parameters, Qwen3-8B text encoder (Apache 2.0)
|
|
13
|
+
case klein9B = "klein-9b"
|
|
14
|
+
|
|
15
|
+
public var displayName: String {
|
|
16
|
+
switch self {
|
|
17
|
+
case .klein4B: return "Flux.2 Klein 4B"
|
|
18
|
+
case .klein9B: return "Flux.2 Klein 9B"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Whether this is a base (non-distilled) model for training
|
|
23
|
+
public var isBaseModel: Bool {
|
|
24
|
+
false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Whether this model can be used for inference
|
|
28
|
+
public var isForInference: Bool {
|
|
29
|
+
true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Whether this model can be used for LoRA training
|
|
33
|
+
public var isForTraining: Bool {
|
|
34
|
+
false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Get the base (non-distilled) variant for training, if available
|
|
38
|
+
public var trainingVariant: Flux2Model {
|
|
39
|
+
.klein4B
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Get the distilled variant for inference (validation images during training)
|
|
43
|
+
public var inferenceVariant: Flux2Model {
|
|
44
|
+
.klein4B
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Whether this model uses guidance embeddings
|
|
48
|
+
public var usesGuidanceEmbeds: Bool {
|
|
49
|
+
false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Joint attention dimension (text encoder output)
|
|
53
|
+
public var jointAttentionDim: Int {
|
|
54
|
+
switch self {
|
|
55
|
+
case .klein4B: return 7680 // Qwen3-4B: 3 × 2560
|
|
56
|
+
case .klein9B: return 12288 // Qwen3-8B: 3 × 4096
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Get the transformer configuration for this model
|
|
61
|
+
public var transformerConfig: Flux2TransformerConfig {
|
|
62
|
+
switch self {
|
|
63
|
+
case .klein4B: return .klein4B
|
|
64
|
+
case .klein9B: return .klein9B
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Estimated VRAM usage in GB
|
|
69
|
+
public var estimatedVRAM: Int {
|
|
70
|
+
switch self {
|
|
71
|
+
case .klein4B: return 13
|
|
72
|
+
case .klein9B: return 20
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// License information
|
|
77
|
+
public var license: String {
|
|
78
|
+
"Apache 2.0"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Whether this model can be used commercially
|
|
82
|
+
public var isCommercialUseAllowed: Bool {
|
|
83
|
+
true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Recommended number of inference steps for optimal quality
|
|
87
|
+
public var defaultSteps: Int {
|
|
88
|
+
4
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Recommended guidance scale for optimal quality
|
|
92
|
+
public var defaultGuidance: Float {
|
|
93
|
+
1.0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Estimated generation time in seconds (1024x1024 on M2 Max)
|
|
97
|
+
public var estimatedTimeSeconds: Int {
|
|
98
|
+
switch self {
|
|
99
|
+
case .klein4B: return 26
|
|
100
|
+
case .klein9B: return 45
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Maximum number of reference images for I2I generation
|
|
105
|
+
/// Source: https://docs.bfl.ai/flux_2/flux2_image_editing
|
|
106
|
+
public var maxReferenceImages: Int {
|
|
107
|
+
4
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// MARK: - Transformer Configuration
|
|
112
|
+
|
|
113
|
+
/// Configuration for the Flux.2 diffusion transformer
|
|
114
|
+
public struct Flux2TransformerConfig: Codable, Sendable {
|
|
115
|
+
/// Patch size for input embedding (1 for Flux.2)
|
|
116
|
+
public var patchSize: Int
|
|
117
|
+
|
|
118
|
+
/// Number of input channels (128 for Flux.2 latents)
|
|
119
|
+
public var inChannels: Int
|
|
120
|
+
|
|
121
|
+
/// Number of output channels (same as input)
|
|
122
|
+
public var outChannels: Int
|
|
123
|
+
|
|
124
|
+
/// Number of double-stream transformer blocks
|
|
125
|
+
public var numLayers: Int
|
|
126
|
+
|
|
127
|
+
/// Number of single-stream transformer blocks
|
|
128
|
+
public var numSingleLayers: Int
|
|
129
|
+
|
|
130
|
+
/// Dimension of each attention head
|
|
131
|
+
public var attentionHeadDim: Int
|
|
132
|
+
|
|
133
|
+
/// Number of attention heads
|
|
134
|
+
public var numAttentionHeads: Int
|
|
135
|
+
|
|
136
|
+
/// Inner dimension for transformer (numAttentionHeads * attentionHeadDim)
|
|
137
|
+
public var innerDim: Int {
|
|
138
|
+
numAttentionHeads * attentionHeadDim
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Dimension of joint attention (from Mistral embeddings: 15360)
|
|
142
|
+
public var jointAttentionDim: Int
|
|
143
|
+
|
|
144
|
+
/// Dimension of pooled projection (time + guidance embeddings)
|
|
145
|
+
public var pooledProjectionDim: Int
|
|
146
|
+
|
|
147
|
+
/// Whether to use guidance embedding
|
|
148
|
+
public var guidanceEmbeds: Bool
|
|
149
|
+
|
|
150
|
+
/// Axes dimensions for RoPE [T, H, W, L]
|
|
151
|
+
public var axesDimsRope: [Int]
|
|
152
|
+
|
|
153
|
+
/// Base theta for RoPE
|
|
154
|
+
public var ropeTheta: Float
|
|
155
|
+
|
|
156
|
+
/// MLP expansion ratio (3.0 for Flux.2, determines FFN hidden dimension)
|
|
157
|
+
public var mlpRatio: Float
|
|
158
|
+
|
|
159
|
+
/// Activation function for feedforward
|
|
160
|
+
public var activationFunction: String
|
|
161
|
+
|
|
162
|
+
public init(
|
|
163
|
+
patchSize: Int = 1,
|
|
164
|
+
inChannels: Int = 128,
|
|
165
|
+
outChannels: Int = 128,
|
|
166
|
+
numLayers: Int = 8,
|
|
167
|
+
numSingleLayers: Int = 48,
|
|
168
|
+
attentionHeadDim: Int = 128,
|
|
169
|
+
numAttentionHeads: Int = 48,
|
|
170
|
+
jointAttentionDim: Int = 15360,
|
|
171
|
+
pooledProjectionDim: Int = 768,
|
|
172
|
+
guidanceEmbeds: Bool = true,
|
|
173
|
+
axesDimsRope: [Int] = [32, 32, 32, 32],
|
|
174
|
+
ropeTheta: Float = 2000.0,
|
|
175
|
+
mlpRatio: Float = 3.0,
|
|
176
|
+
activationFunction: String = "silu"
|
|
177
|
+
) {
|
|
178
|
+
self.patchSize = patchSize
|
|
179
|
+
self.inChannels = inChannels
|
|
180
|
+
self.outChannels = outChannels
|
|
181
|
+
self.numLayers = numLayers
|
|
182
|
+
self.numSingleLayers = numSingleLayers
|
|
183
|
+
self.attentionHeadDim = attentionHeadDim
|
|
184
|
+
self.numAttentionHeads = numAttentionHeads
|
|
185
|
+
self.jointAttentionDim = jointAttentionDim
|
|
186
|
+
self.pooledProjectionDim = pooledProjectionDim
|
|
187
|
+
self.guidanceEmbeds = guidanceEmbeds
|
|
188
|
+
self.axesDimsRope = axesDimsRope
|
|
189
|
+
self.ropeTheta = ropeTheta
|
|
190
|
+
self.mlpRatio = mlpRatio
|
|
191
|
+
self.activationFunction = activationFunction
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Flux.2 Klein 4B configuration
|
|
195
|
+
/// 4B parameters, 5 double + 20 single blocks
|
|
196
|
+
public static let klein4B = Flux2TransformerConfig(
|
|
197
|
+
patchSize: 1,
|
|
198
|
+
inChannels: 128,
|
|
199
|
+
outChannels: 128,
|
|
200
|
+
numLayers: 5,
|
|
201
|
+
numSingleLayers: 20,
|
|
202
|
+
attentionHeadDim: 128,
|
|
203
|
+
numAttentionHeads: 24, // 24 × 128 = 3072
|
|
204
|
+
jointAttentionDim: 7680, // Qwen3-4B: 3 × 2560
|
|
205
|
+
pooledProjectionDim: 768,
|
|
206
|
+
guidanceEmbeds: false,
|
|
207
|
+
axesDimsRope: [32, 32, 32, 32],
|
|
208
|
+
ropeTheta: 2000.0,
|
|
209
|
+
mlpRatio: 3.0,
|
|
210
|
+
activationFunction: "silu"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
/// Flux.2 Klein 9B configuration
|
|
214
|
+
/// 9B parameters, 8 double + 24 single blocks
|
|
215
|
+
public static let klein9B = Flux2TransformerConfig(
|
|
216
|
+
patchSize: 1,
|
|
217
|
+
inChannels: 128,
|
|
218
|
+
outChannels: 128,
|
|
219
|
+
numLayers: 8,
|
|
220
|
+
numSingleLayers: 24,
|
|
221
|
+
attentionHeadDim: 128,
|
|
222
|
+
numAttentionHeads: 32, // 32 × 128 = 4096
|
|
223
|
+
jointAttentionDim: 12288, // Qwen3-8B: 3 × 4096
|
|
224
|
+
pooledProjectionDim: 768,
|
|
225
|
+
guidanceEmbeds: false,
|
|
226
|
+
axesDimsRope: [32, 32, 32, 32],
|
|
227
|
+
ropeTheta: 2000.0,
|
|
228
|
+
mlpRatio: 3.0,
|
|
229
|
+
activationFunction: "silu"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
// MARK: - Codable
|
|
233
|
+
|
|
234
|
+
enum CodingKeys: String, CodingKey {
|
|
235
|
+
case patchSize = "patch_size"
|
|
236
|
+
case inChannels = "in_channels"
|
|
237
|
+
case outChannels = "out_channels"
|
|
238
|
+
case numLayers = "num_layers"
|
|
239
|
+
case numSingleLayers = "num_single_layers"
|
|
240
|
+
case attentionHeadDim = "attention_head_dim"
|
|
241
|
+
case numAttentionHeads = "num_attention_heads"
|
|
242
|
+
case jointAttentionDim = "joint_attention_dim"
|
|
243
|
+
case pooledProjectionDim = "pooled_projection_dim"
|
|
244
|
+
case guidanceEmbeds = "guidance_embeds"
|
|
245
|
+
case axesDimsRope = "axes_dims_rope"
|
|
246
|
+
case ropeTheta = "rope_theta"
|
|
247
|
+
case mlpRatio = "mlp_ratio"
|
|
248
|
+
case activationFunction = "activation_function"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public init(from decoder: Decoder) throws {
|
|
252
|
+
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
253
|
+
|
|
254
|
+
patchSize = try container.decodeIfPresent(Int.self, forKey: .patchSize) ?? 1
|
|
255
|
+
inChannels = try container.decodeIfPresent(Int.self, forKey: .inChannels) ?? 128
|
|
256
|
+
outChannels = try container.decodeIfPresent(Int.self, forKey: .outChannels) ?? 128
|
|
257
|
+
numLayers = try container.decodeIfPresent(Int.self, forKey: .numLayers) ?? 8
|
|
258
|
+
numSingleLayers = try container.decodeIfPresent(Int.self, forKey: .numSingleLayers) ?? 48
|
|
259
|
+
attentionHeadDim = try container.decodeIfPresent(Int.self, forKey: .attentionHeadDim) ?? 128
|
|
260
|
+
numAttentionHeads = try container.decodeIfPresent(Int.self, forKey: .numAttentionHeads) ?? 48
|
|
261
|
+
jointAttentionDim = try container.decodeIfPresent(Int.self, forKey: .jointAttentionDim) ?? 15360
|
|
262
|
+
pooledProjectionDim = try container.decodeIfPresent(Int.self, forKey: .pooledProjectionDim) ?? 768
|
|
263
|
+
guidanceEmbeds = try container.decodeIfPresent(Bool.self, forKey: .guidanceEmbeds) ?? true
|
|
264
|
+
axesDimsRope = try container.decodeIfPresent([Int].self, forKey: .axesDimsRope) ?? [32, 32, 32, 32]
|
|
265
|
+
ropeTheta = try container.decodeIfPresent(Float.self, forKey: .ropeTheta) ?? 2000.0
|
|
266
|
+
mlpRatio = try container.decodeIfPresent(Float.self, forKey: .mlpRatio) ?? 3.0
|
|
267
|
+
activationFunction = try container.decodeIfPresent(String.self, forKey: .activationFunction) ?? "silu"
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Load configuration from a JSON file
|
|
271
|
+
public static func load(from url: URL) throws -> Flux2TransformerConfig {
|
|
272
|
+
let data = try Data(contentsOf: url)
|
|
273
|
+
return try JSONDecoder().decode(Flux2TransformerConfig.self, from: data)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
extension Flux2TransformerConfig: CustomStringConvertible {
|
|
278
|
+
public var description: String {
|
|
279
|
+
"""
|
|
280
|
+
Flux2TransformerConfig(
|
|
281
|
+
layers: \(numLayers) double + \(numSingleLayers) single,
|
|
282
|
+
heads: \(numAttentionHeads) × \(attentionHeadDim) = \(innerDim),
|
|
283
|
+
jointDim: \(jointAttentionDim),
|
|
284
|
+
rope: \(axesDimsRope) θ=\(ropeTheta)
|
|
285
|
+
)
|
|
286
|
+
"""
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// MemoryConfig.swift - Centralized memory management configuration
|
|
2
|
+
// Copyright 2025 Vincent Gourbin
|
|
3
|
+
|
|
4
|
+
import Foundation
|
|
5
|
+
import MLX
|
|
6
|
+
|
|
7
|
+
/// Centralized memory configuration for GPU cache management
|
|
8
|
+
///
|
|
9
|
+
/// Provides dynamic memory management that adapts to:
|
|
10
|
+
/// - System RAM availability
|
|
11
|
+
/// - Image resolution being generated
|
|
12
|
+
/// - Model size (currently Klein 4B only in image-studio)
|
|
13
|
+
///
|
|
14
|
+
/// On high-RAM systems (64GB+), allows more cache for better performance.
|
|
15
|
+
/// On lower-RAM systems, uses conservative limits to prevent OOM.
|
|
16
|
+
public struct MemoryConfig {
|
|
17
|
+
|
|
18
|
+
// MARK: - Constants
|
|
19
|
+
|
|
20
|
+
private static let MB = 1024 * 1024
|
|
21
|
+
private static let GB = 1024 * 1024 * 1024
|
|
22
|
+
|
|
23
|
+
// MARK: - Cache Profiles
|
|
24
|
+
|
|
25
|
+
/// Memory profile presets - used as hints for dynamic calculation
|
|
26
|
+
public enum CacheProfile: String, CaseIterable, Sendable {
|
|
27
|
+
case conservative // Minimize memory usage (slower)
|
|
28
|
+
case balanced // Balance speed and memory
|
|
29
|
+
case performance // Maximize speed (more memory)
|
|
30
|
+
case auto // Dynamic based on available RAM
|
|
31
|
+
|
|
32
|
+
/// Human-readable description
|
|
33
|
+
public var description: String {
|
|
34
|
+
switch self {
|
|
35
|
+
case .conservative: return "Conservative - Minimize memory (may be slower)"
|
|
36
|
+
case .balanced: return "Balanced - Good tradeoff"
|
|
37
|
+
case .performance: return "Performance - Maximize speed (uses more memory)"
|
|
38
|
+
case .auto: return "Auto - Dynamic based on available RAM"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// MARK: - System Information
|
|
44
|
+
|
|
45
|
+
/// Get system RAM in GB
|
|
46
|
+
public static var systemRAMGB: Int {
|
|
47
|
+
Int(ProcessInfo.processInfo.physicalMemory / UInt64(GB))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Estimate available RAM in GB (rough heuristic)
|
|
51
|
+
/// Reserves ~8GB for system + other apps
|
|
52
|
+
public static var estimatedAvailableRAMGB: Int {
|
|
53
|
+
max(8, systemRAMGB - 8)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Calculate what percentage of RAM we can safely use for GPU cache
|
|
57
|
+
/// Higher RAM systems can afford to use more cache
|
|
58
|
+
public static var safeCachePercentage: Double {
|
|
59
|
+
let ram = systemRAMGB
|
|
60
|
+
switch ram {
|
|
61
|
+
case ..<24: return 0.03 // 3% - Very conservative for 16-24GB
|
|
62
|
+
case 24..<48: return 0.05 // 5% - Conservative for 24-48GB
|
|
63
|
+
case 48..<96: return 0.08 // 8% - Moderate for 48-96GB
|
|
64
|
+
case 96..<192: return 0.12 // 12% - Performance for 96-192GB
|
|
65
|
+
default: return 0.15 // 15% - High performance for 192GB+
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// MARK: - Dynamic Cache Calculation
|
|
70
|
+
|
|
71
|
+
/// Calculate recommended cache limit based on system RAM
|
|
72
|
+
/// Returns bytes, or nil for unlimited
|
|
73
|
+
public static func recommendedCacheLimit() -> Int? {
|
|
74
|
+
let ram = systemRAMGB
|
|
75
|
+
|
|
76
|
+
// Very high RAM systems (128GB+) can use unlimited cache
|
|
77
|
+
if ram >= 128 {
|
|
78
|
+
return nil
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Calculate based on percentage of RAM
|
|
82
|
+
let cacheBytes = Int(Double(ram * GB) * safeCachePercentage)
|
|
83
|
+
|
|
84
|
+
// Clamp to reasonable bounds
|
|
85
|
+
let minCache = 256 * MB // At least 256 MB
|
|
86
|
+
let maxCache = 8 * GB // At most 8 GB
|
|
87
|
+
|
|
88
|
+
return min(max(cacheBytes, minCache), maxCache)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Calculate cache limit for a specific resolution
|
|
92
|
+
/// Larger images need more cache for intermediate computations
|
|
93
|
+
public static func cacheLimitForResolution(width: Int, height: Int, model: Flux2Model) -> Int {
|
|
94
|
+
let pixels = width * height
|
|
95
|
+
let baseLimit = recommendedCacheLimit() ?? (4 * GB)
|
|
96
|
+
|
|
97
|
+
// Scale factor based on resolution
|
|
98
|
+
// 512x512 = 262144 pixels (base)
|
|
99
|
+
// 1024x1024 = 1048576 pixels (4x)
|
|
100
|
+
// 2048x2048 = 4194304 pixels (16x)
|
|
101
|
+
let basePixels = 512 * 512
|
|
102
|
+
let scaleFactor = max(1.0, sqrt(Double(pixels) / Double(basePixels)))
|
|
103
|
+
|
|
104
|
+
// Model size factor
|
|
105
|
+
let modelFactor: Double = 1.0
|
|
106
|
+
|
|
107
|
+
let adjustedLimit = Int(Double(baseLimit) * scaleFactor * modelFactor)
|
|
108
|
+
|
|
109
|
+
// Clamp to available RAM (leave at least 8GB for system)
|
|
110
|
+
let maxAllowed = (systemRAMGB - 8) * GB
|
|
111
|
+
return min(adjustedLimit, maxAllowed)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// MARK: - Profile-based Limits (for manual override)
|
|
115
|
+
|
|
116
|
+
/// Get cache limit for a specific profile
|
|
117
|
+
public static func cacheLimitForProfile(_ profile: CacheProfile) -> Int? {
|
|
118
|
+
switch profile {
|
|
119
|
+
case .auto:
|
|
120
|
+
return recommendedCacheLimit()
|
|
121
|
+
case .conservative:
|
|
122
|
+
return 512 * MB
|
|
123
|
+
case .balanced:
|
|
124
|
+
return min(2 * GB, (systemRAMGB / 32) * GB)
|
|
125
|
+
case .performance:
|
|
126
|
+
return min(4 * GB, (systemRAMGB / 16) * GB)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// MARK: - Cache Limit Application
|
|
131
|
+
|
|
132
|
+
/// Apply cache limit dynamically based on system RAM
|
|
133
|
+
public static func applyDynamicCacheLimit() {
|
|
134
|
+
if let limit = recommendedCacheLimit() {
|
|
135
|
+
Memory.cacheLimit = limit
|
|
136
|
+
Flux2Debug.log("GPU cache limit: \(limit / MB) MB (dynamic, \(systemRAMGB) GB RAM)")
|
|
137
|
+
} else {
|
|
138
|
+
Flux2Debug.log("GPU cache: unlimited (\(systemRAMGB) GB RAM)")
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Apply cache limit for a specific profile
|
|
143
|
+
public static func applyCacheLimit(_ profile: CacheProfile) {
|
|
144
|
+
if let limit = cacheLimitForProfile(profile) {
|
|
145
|
+
Memory.cacheLimit = limit
|
|
146
|
+
Flux2Debug.log("GPU cache limit: \(limit / MB) MB (\(profile.rawValue) profile)")
|
|
147
|
+
} else {
|
|
148
|
+
Flux2Debug.log("GPU cache: unlimited (\(profile.rawValue) profile)")
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Apply cache limit with specific byte value
|
|
153
|
+
public static func applyCacheLimit(bytes: Int) {
|
|
154
|
+
Memory.cacheLimit = bytes
|
|
155
|
+
Flux2Debug.log("GPU cache limit: \(bytes / MB) MB")
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Clear GPU cache
|
|
159
|
+
public static func clearCache() {
|
|
160
|
+
Memory.clearCache()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// MARK: - Phase-Specific Limits
|
|
164
|
+
|
|
165
|
+
/// Per-phase cache limits for granular control during generation
|
|
166
|
+
public struct PhaseLimits: Sendable {
|
|
167
|
+
public let textEncoding: Int // Text encoder phase
|
|
168
|
+
public let denoising: Int // Transformer denoising loop
|
|
169
|
+
public let vaeDecoding: Int // VAE decode phase
|
|
170
|
+
|
|
171
|
+
public init(textEncoding: Int, denoising: Int, vaeDecoding: Int) {
|
|
172
|
+
self.textEncoding = textEncoding
|
|
173
|
+
self.denoising = denoising
|
|
174
|
+
self.vaeDecoding = vaeDecoding
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Get dynamic phase limits based on model and system RAM
|
|
178
|
+
/// Automatically scales based on available memory
|
|
179
|
+
public static func forModel(_ model: Flux2Model, profile: CacheProfile) -> PhaseLimits {
|
|
180
|
+
// Use dynamic calculation for auto profile
|
|
181
|
+
if profile == .auto {
|
|
182
|
+
return dynamicLimitsForModel(model)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
switch profile {
|
|
186
|
+
case .auto:
|
|
187
|
+
return dynamicLimitsForModel(model)
|
|
188
|
+
case .conservative:
|
|
189
|
+
return PhaseLimits(textEncoding: 256 * MB, denoising: 512 * MB, vaeDecoding: 256 * MB)
|
|
190
|
+
case .balanced:
|
|
191
|
+
return PhaseLimits(textEncoding: 512 * MB, denoising: 1 * GB, vaeDecoding: 512 * MB)
|
|
192
|
+
case .performance:
|
|
193
|
+
return PhaseLimits(textEncoding: 1 * GB, denoising: 2 * GB, vaeDecoding: 1 * GB)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/// Calculate dynamic limits based on available RAM
|
|
198
|
+
private static func dynamicLimitsForModel(_ model: Flux2Model) -> PhaseLimits {
|
|
199
|
+
let baseLimit = recommendedCacheLimit() ?? (4 * GB)
|
|
200
|
+
|
|
201
|
+
_ = model
|
|
202
|
+
let (textRatio, denoiseRatio, vaeRatio): (Double, Double, Double) = (0.25, 0.6, 0.3)
|
|
203
|
+
|
|
204
|
+
return PhaseLimits(
|
|
205
|
+
textEncoding: Int(Double(baseLimit) * textRatio),
|
|
206
|
+
denoising: Int(Double(baseLimit) * denoiseRatio),
|
|
207
|
+
vaeDecoding: Int(Double(baseLimit) * vaeRatio)
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// MARK: - Memory Monitoring
|
|
213
|
+
|
|
214
|
+
/// Log current memory state
|
|
215
|
+
public static func logMemoryState(context: String = "") {
|
|
216
|
+
let prefix = context.isEmpty ? "" : "[\(context)] "
|
|
217
|
+
let recommended = recommendedCacheLimit()
|
|
218
|
+
let limitStr = recommended.map { "\($0 / MB) MB" } ?? "unlimited"
|
|
219
|
+
Flux2Debug.log("\(prefix)System: \(systemRAMGB) GB RAM, recommended cache: \(limitStr)")
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// Get a summary of current memory configuration
|
|
223
|
+
public static var configurationSummary: String {
|
|
224
|
+
let recommended = recommendedCacheLimit()
|
|
225
|
+
let limitStr = recommended.map { "\($0 / MB) MB" } ?? "unlimited"
|
|
226
|
+
return """
|
|
227
|
+
Memory Configuration:
|
|
228
|
+
System RAM: \(systemRAMGB) GB
|
|
229
|
+
Safe cache %: \(Int(safeCachePercentage * 100))%
|
|
230
|
+
Recommended cache: \(limitStr)
|
|
231
|
+
"""
|
|
232
|
+
}
|
|
233
|
+
}
|