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.
Files changed (96) hide show
  1. package/dist/cli.cjs +269 -22
  2. package/dist/cli.d.cts +32 -2
  3. package/dist/cli.d.ts +32 -2
  4. package/dist/cli.js +270 -23
  5. package/dist/platform.d.cts +2 -0
  6. package/dist/platform.d.ts +2 -0
  7. package/dist/{ui-1WepaMS4.d.cts → ui-Bg11tvlc.d.ts} +1 -1
  8. package/dist/{ui-Ce-pnUUA.d.ts → ui-Dd7SXdbg.d.cts} +7 -7
  9. package/dist/ui.d.cts +1 -1
  10. package/dist/ui.d.ts +1 -1
  11. package/dist/window.d.cts +1 -1
  12. package/dist/window.d.ts +1 -1
  13. package/package.json +5 -1
  14. package/templates/image-generation/app/index.tsx +1 -1
  15. package/templates/native/.gitattributes +1 -0
  16. package/templates/native/App.tsx +26 -0
  17. package/templates/native/app.json +12 -0
  18. package/templates/native/index.ts +8 -0
  19. package/templates/native/ios/.xcode.env +11 -0
  20. package/templates/native/ios/Bleam/AI/Flux2/Configuration/Flux2Config.swift +288 -0
  21. package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryConfig.swift +233 -0
  22. package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryOptimizationConfig.swift +157 -0
  23. package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +425 -0
  24. package/templates/native/ios/Bleam/AI/Flux2/Configuration/QuantizationConfig.swift +138 -0
  25. package/templates/native/ios/Bleam/AI/Flux2/Configuration/TransformerRepoProfile.swift +59 -0
  26. package/templates/native/ios/Bleam/AI/Flux2/Configuration/VAEConfig.swift +134 -0
  27. package/templates/native/ios/Bleam/AI/Flux2/Flux2Core.swift +55 -0
  28. package/templates/native/ios/Bleam/AI/Flux2/Loading/WeightLoader.swift +1192 -0
  29. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +1282 -0
  30. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/LatentUtils.swift +191 -0
  31. package/templates/native/ios/Bleam/AI/Flux2/Scheduler/FlowMatchEulerScheduler.swift +279 -0
  32. package/templates/native/ios/Bleam/AI/Flux2/Text/KleinTextEncoder.swift +380 -0
  33. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Attention.swift +379 -0
  34. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Embeddings.swift +159 -0
  35. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FeedForward.swift +154 -0
  36. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FusedKernels.swift +198 -0
  37. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2KVCache.swift +36 -0
  38. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Modulation.swift +193 -0
  39. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2ParallelAttention.swift +389 -0
  40. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2RoPE.swift +304 -0
  41. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2SingleBlock.swift +290 -0
  42. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Transformer.swift +530 -0
  43. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2TransformerBlock.swift +362 -0
  44. package/templates/native/ios/Bleam/AI/Flux2/Utils/Flux2Debug.swift +101 -0
  45. package/templates/native/ios/Bleam/AI/Flux2/Utils/MLXCheckpoint.swift +118 -0
  46. package/templates/native/ios/Bleam/AI/Flux2/Utils/MemoryManager.swift +201 -0
  47. package/templates/native/ios/Bleam/AI/Flux2/VAE/AutoencoderKL.swift +476 -0
  48. package/templates/native/ios/Bleam/AI/Flux2/VAE/ResnetBlock.swift +316 -0
  49. package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEDecoder.swift +120 -0
  50. package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEEncoder.swift +136 -0
  51. package/templates/native/ios/Bleam/AppDelegate.swift +482 -0
  52. package/templates/native/ios/Bleam/Appearance.swift +62 -0
  53. package/templates/native/ios/Bleam/Bridging-Header.h +3 -0
  54. package/templates/native/ios/Bleam/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
  55. package/templates/native/ios/Bleam/Images.xcassets/Contents.json +6 -0
  56. package/templates/native/ios/Bleam/Info.plist +76 -0
  57. package/templates/native/ios/Bleam/PrivacyInfo.xcprivacy +48 -0
  58. package/templates/native/ios/Bleam/SceneDelegate.swift +58 -0
  59. package/templates/native/ios/Bleam/SplashScreen.storyboard +47 -0
  60. package/templates/native/ios/Bleam/Supporting/Expo.plist +6 -0
  61. package/templates/native/ios/Bleam/bleam.entitlements +10 -0
  62. package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +1147 -0
  63. package/templates/native/ios/Bleam.xcodeproj/xcshareddata/xcschemes/Bleam.xcscheme +88 -0
  64. package/templates/native/ios/GenerationService/GenerationService.entitlements +10 -0
  65. package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +591 -0
  66. package/templates/native/ios/GenerationService/Info.plist +31 -0
  67. package/templates/native/ios/GenerationService/main.swift +165 -0
  68. package/templates/native/ios/PlatformHelper/main.swift +726 -0
  69. package/templates/native/ios/Podfile +198 -0
  70. package/templates/native/ios/Podfile.lock +2284 -0
  71. package/templates/native/ios/Podfile.properties.json +5 -0
  72. package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +12 -0
  73. package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +124 -0
  74. package/templates/native/metro.config.js +6 -0
  75. package/templates/native/modules/bleam-runtime/BleamRuntime.podspec +14 -0
  76. package/templates/native/modules/bleam-runtime/expo-module.config.json +8 -0
  77. package/templates/native/modules/bleam-runtime/ios/AIModule.swift +1258 -0
  78. package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +135 -0
  79. package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +329 -0
  80. package/templates/native/modules/bleam-runtime/package.json +5 -0
  81. package/templates/native/package.json +39 -0
  82. package/templates/native/scripts/build/mlx-frameworks.ts +421 -0
  83. package/templates/native/scripts/start/index.ts +474 -0
  84. package/templates/native/scripts/start/known-failures.ts +20 -0
  85. package/templates/native/scripts/start/loader.ts +76 -0
  86. package/templates/native/scripts/start/native-state.ts +242 -0
  87. package/templates/native/scripts/start/package.json +4 -0
  88. package/templates/native/scripts/start/swift-packages.ts +414 -0
  89. package/templates/native/scripts/start/xcode-formatter.ts +106 -0
  90. package/templates/native/tsconfig.json +14 -0
  91. package/templates/native/yarn.lock +3749 -0
  92. package/templates/updates/README.md +272 -0
  93. package/templates/updates/src/index.ts +385 -0
  94. package/templates/updates/src/schema.ts +385 -0
  95. package/templates/updates/tsconfig.json +12 -0
  96. 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,12 @@
1
+ {
2
+ "expo": {
3
+ "name": "Bleam",
4
+ "slug": "bleam",
5
+ "version": "1.0.0",
6
+ "platforms": ["ios"],
7
+ "ios": {
8
+ "appleTeamId": "5SZ3H5P69M",
9
+ "bundleIdentifier": "dev.bleam.app"
10
+ }
11
+ }
12
+ }
@@ -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
+ }