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,157 @@
1
+ // MemoryOptimizationConfig.swift - Memory optimization settings
2
+ // Copyright 2025 Vincent Gourbin
3
+
4
+ import Foundation
5
+
6
+ /// Configuration for memory optimization during transformer inference
7
+ ///
8
+ /// These settings control how aggressively the transformer manages memory
9
+ /// by forcing evaluation of the computation graph at intervals.
10
+ ///
11
+ /// # Background
12
+ /// MLX uses lazy evaluation and builds a computation graph. For large models,
13
+ /// this graph can grow very large and consume significant memory. Periodically
14
+ /// calling `eval()` materializes intermediate results and allows the graph to
15
+ /// be garbage collected.
16
+ ///
17
+ /// # Trade-offs
18
+ /// - Lower `evalFrequency` = more memory savings, but slower inference
19
+ /// - Higher `evalFrequency` = faster inference, but more memory usage
20
+ /// - `clearCacheOnEval` = additional memory savings at cost of recomputation
21
+ ///
22
+ /// # Reference
23
+ /// Based on optimizations from LTX-2-MLX: https://github.com/Acelogic/LTX-2-MLX
24
+ public struct MemoryOptimizationConfig: Sendable, Equatable {
25
+
26
+ /// How often to evaluate the computation graph during transformer forward pass.
27
+ ///
28
+ /// - `0` = disabled (fastest, most memory)
29
+ /// - `4` = evaluate every 4 blocks (aggressive memory savings)
30
+ /// - `8` = evaluate every 8 blocks (moderate, recommended)
31
+ /// - `16` = evaluate every 16 blocks (light memory savings)
32
+ ///
33
+ /// For Flux.2 Dev with 8 double + 48 single blocks:
34
+ /// - `evalFrequency: 8` will eval ~7 times during forward pass
35
+ /// - `evalFrequency: 4` will eval ~14 times during forward pass
36
+ public var evalFrequency: Int
37
+
38
+ /// Whether to clear GPU cache after each evaluation.
39
+ ///
40
+ /// When enabled, calls `MLX.Memory.clearCache()` after each periodic eval.
41
+ /// This provides additional memory savings but may cause recomputation
42
+ /// of cached values.
43
+ ///
44
+ /// Recommended: `false` for most cases, `true` only if running out of memory
45
+ public var clearCacheOnEval: Bool
46
+
47
+ /// Whether to evaluate after all double-stream blocks complete.
48
+ ///
49
+ /// This ensures the transition between double-stream and single-stream
50
+ /// phases has a clean memory state.
51
+ public var evalBetweenPhases: Bool
52
+
53
+ public init(
54
+ evalFrequency: Int = 0,
55
+ clearCacheOnEval: Bool = false,
56
+ evalBetweenPhases: Bool = true
57
+ ) {
58
+ self.evalFrequency = max(0, evalFrequency)
59
+ self.clearCacheOnEval = clearCacheOnEval
60
+ self.evalBetweenPhases = evalBetweenPhases
61
+ }
62
+
63
+ // MARK: - Presets
64
+
65
+ /// Disabled - no periodic evaluation (fastest, most memory)
66
+ ///
67
+ /// Use when you have plenty of RAM and want maximum speed.
68
+ public static let disabled = MemoryOptimizationConfig(
69
+ evalFrequency: 0,
70
+ clearCacheOnEval: false,
71
+ evalBetweenPhases: false
72
+ )
73
+
74
+ /// Light - evaluate every 16 blocks
75
+ ///
76
+ /// Minimal impact on speed with some memory savings.
77
+ public static let light = MemoryOptimizationConfig(
78
+ evalFrequency: 16,
79
+ clearCacheOnEval: false,
80
+ evalBetweenPhases: true
81
+ )
82
+
83
+ /// Moderate - evaluate every 8 blocks (recommended default)
84
+ ///
85
+ /// Good balance between speed and memory usage.
86
+ public static let moderate = MemoryOptimizationConfig(
87
+ evalFrequency: 8,
88
+ clearCacheOnEval: false,
89
+ evalBetweenPhases: true
90
+ )
91
+
92
+ /// Aggressive - evaluate every 4 blocks with cache clearing
93
+ ///
94
+ /// Use when running low on memory. Slower but uses significantly less RAM.
95
+ public static let aggressive = MemoryOptimizationConfig(
96
+ evalFrequency: 4,
97
+ clearCacheOnEval: true,
98
+ evalBetweenPhases: true
99
+ )
100
+
101
+ /// Ultra low memory - evaluate every 2 blocks with cache clearing
102
+ ///
103
+ /// Maximum memory savings at significant speed cost.
104
+ /// Use only if other presets cause out-of-memory errors.
105
+ public static let ultraLowMemory = MemoryOptimizationConfig(
106
+ evalFrequency: 2,
107
+ clearCacheOnEval: true,
108
+ evalBetweenPhases: true
109
+ )
110
+ }
111
+
112
+ // MARK: - Description
113
+
114
+ extension MemoryOptimizationConfig: CustomStringConvertible {
115
+ public var description: String {
116
+ if evalFrequency == 0 && !evalBetweenPhases {
117
+ return "MemoryOptimization(disabled)"
118
+ }
119
+
120
+ var parts: [String] = []
121
+ if evalFrequency > 0 {
122
+ parts.append("evalEvery: \(evalFrequency)")
123
+ }
124
+ if clearCacheOnEval {
125
+ parts.append("clearCache: true")
126
+ }
127
+ if evalBetweenPhases {
128
+ parts.append("evalBetweenPhases: true")
129
+ }
130
+
131
+ return "MemoryOptimization(\(parts.joined(separator: ", ")))"
132
+ }
133
+ }
134
+
135
+ // MARK: - Recommendation
136
+
137
+ extension MemoryOptimizationConfig {
138
+
139
+ /// Get recommended configuration based on available RAM
140
+ ///
141
+ /// - Parameter ramGB: System RAM in gigabytes
142
+ /// - Returns: Recommended memory optimization preset
143
+ public static func recommended(forRAMGB ramGB: Int) -> MemoryOptimizationConfig {
144
+ switch ramGB {
145
+ case 0..<32:
146
+ return .ultraLowMemory
147
+ case 32..<64:
148
+ return .aggressive
149
+ case 64..<96:
150
+ return .moderate
151
+ case 96..<128:
152
+ return .light
153
+ default:
154
+ return .disabled
155
+ }
156
+ }
157
+ }
@@ -0,0 +1,425 @@
1
+ import Foundation
2
+
3
+ public enum ModelRegistry {
4
+ public enum TransformerRepo: String, CaseIterable, Sendable {
5
+ case blackForestLabsKlein4B = "black-forest-labs/FLUX.2-klein-4B"
6
+ case moxinOrgKlein4B8bit = "moxin-org/FLUX.2-klein-4B-8bit-mlx"
7
+ case themindstudioKlein4B4bit = "themindstudio/flux2-klein-4b-mlx-4bit"
8
+ // These can be presented as Bonsai models while still using the Flux2 pipeline/runtime.
9
+ case prismMLBonsaiKlein4B2bit = "prism-ml/bonsai-image-ternary-4B-mlx-2bit"
10
+ case themindstudioKlein9B4bit = "themindstudio/flux2-klein-9b-mlx-4bit"
11
+ case aitraderKlein9B8bit = "AITRADER/FLUX2-klein-9B-mlx-8bit"
12
+ case mlxCommunityKlein9B4bit = "mlx-community/FLUX.2-klein-9B"
13
+
14
+ public var profile: Flux2TransformerRepoProfile {
15
+ switch self {
16
+ case .blackForestLabsKlein4B:
17
+ return Flux2TransformerRepoProfile(
18
+ repoId: rawValue,
19
+ model: .klein4B,
20
+ quantization: .bf16,
21
+ relativePathComponents: ["models--black-forest-labs--FLUX.2-klein-4B"],
22
+ requiredSnapshotPatterns: [
23
+ "config.json",
24
+ "model_index.json",
25
+ "flux-2-klein-4b*.safetensors",
26
+ "flux-2-klein-4b*.safetensors.index.json",
27
+ ],
28
+ estimatedSizeGB: 8,
29
+ checkpointFormat: .diffusers
30
+ )
31
+ case .moxinOrgKlein4B8bit:
32
+ return Flux2TransformerRepoProfile(
33
+ repoId: rawValue,
34
+ model: .klein4B,
35
+ quantization: .qint8,
36
+ relativePathComponents: ["models--moxin-org--FLUX.2-klein-4B-8bit-mlx", "transformer"],
37
+ requiredSnapshotPatterns: [
38
+ "transformer/config.json",
39
+ "transformer/quantized_8bit.safetensors",
40
+ ],
41
+ estimatedSizeGB: 4,
42
+ checkpointFormat: .diffusersQuanto
43
+ )
44
+ case .themindstudioKlein4B4bit:
45
+ return Flux2TransformerRepoProfile(
46
+ repoId: rawValue,
47
+ model: .klein4B,
48
+ quantization: .int4,
49
+ relativePathComponents: ["models--themindstudio--flux2-klein-4b-mlx-4bit", "transformer"],
50
+ requiredSnapshotPatterns: [
51
+ "transformer/model.safetensors.index.json",
52
+ "transformer/*.safetensors",
53
+ ],
54
+ estimatedSizeGB: 5,
55
+ checkpointFormat: .packedDiffusers,
56
+ needsNormOutSwap: false,
57
+ usesNormOutBias: true
58
+ )
59
+ case .prismMLBonsaiKlein4B2bit:
60
+ return Flux2TransformerRepoProfile(
61
+ repoId: rawValue,
62
+ model: .klein4B,
63
+ quantization: .int4,
64
+ relativePathComponents: ["models--prism-ml--bonsai-image-ternary-4B-mlx-2bit", "transformer-packed-mflux"],
65
+ requiredSnapshotPatterns: [
66
+ "transformer-packed-mflux/config.json",
67
+ "transformer-packed-mflux/diffusion_pytorch_model.safetensors",
68
+ "transformer-packed-mflux/quantization_config.json",
69
+ ],
70
+ estimatedSizeGB: 3,
71
+ checkpointFormat: .packedDiffusers,
72
+ needsNormOutSwap: false,
73
+ usesNormOutBias: false
74
+ )
75
+ case .themindstudioKlein9B4bit:
76
+ return Flux2TransformerRepoProfile(
77
+ repoId: rawValue,
78
+ model: .klein9B,
79
+ quantization: .int4,
80
+ relativePathComponents: ["models--themindstudio--flux2-klein-9b-mlx-4bit", "transformer"],
81
+ requiredSnapshotPatterns: [
82
+ "transformer/model.safetensors.index.json",
83
+ "transformer/*.safetensors",
84
+ ],
85
+ estimatedSizeGB: 20,
86
+ checkpointFormat: .packedDiffusers,
87
+ needsNormOutSwap: false,
88
+ usesNormOutBias: true
89
+ )
90
+ case .aitraderKlein9B8bit:
91
+ return Flux2TransformerRepoProfile(
92
+ repoId: rawValue,
93
+ model: .klein9B,
94
+ quantization: .qint8,
95
+ relativePathComponents: ["models--AITRADER--FLUX2-klein-9B-mlx-8bit", "transformer"],
96
+ requiredSnapshotPatterns: [
97
+ "transformer/model.safetensors.index.json",
98
+ "transformer/*.safetensors",
99
+ ],
100
+ estimatedSizeGB: 10,
101
+ checkpointFormat: .packedDiffusers,
102
+ needsNormOutSwap: false,
103
+ usesNormOutBias: true
104
+ )
105
+ case .mlxCommunityKlein9B4bit:
106
+ return Flux2TransformerRepoProfile(
107
+ repoId: rawValue,
108
+ model: .klein9B,
109
+ quantization: .int4,
110
+ relativePathComponents: ["models--mlx-community--FLUX.2-klein-9B", "transformer"],
111
+ requiredSnapshotPatterns: [
112
+ "transformer/model.safetensors.index.json",
113
+ "transformer/*.safetensors",
114
+ ],
115
+ estimatedSizeGB: 20,
116
+ checkpointFormat: .packedDiffusers,
117
+ needsNormOutSwap: false,
118
+ usesNormOutBias: true
119
+ )
120
+ }
121
+ }
122
+
123
+ public static func repo(for model: Flux2Model, quantization: TransformerQuantization) -> TransformerRepo {
124
+ switch model {
125
+ case .klein4B:
126
+ switch quantization {
127
+ case .bf16:
128
+ return .blackForestLabsKlein4B
129
+ case .int4:
130
+ return .themindstudioKlein4B4bit
131
+ case .qint8:
132
+ return .moxinOrgKlein4B8bit
133
+ }
134
+ case .klein9B:
135
+ switch quantization {
136
+ case .qint8:
137
+ return .aitraderKlein9B8bit
138
+ case .int4:
139
+ return .themindstudioKlein9B4bit
140
+ case .bf16:
141
+ return .mlxCommunityKlein9B4bit
142
+ }
143
+ }
144
+ }
145
+
146
+ public static func profile(for repoId: String) -> Flux2TransformerRepoProfile? {
147
+ allCases.first { $0.rawValue == repoId }?.profile
148
+ }
149
+ }
150
+
151
+ public enum TransformerVariant: String, CaseIterable, Sendable {
152
+ case klein4B_bf16 = "klein4b-bf16"
153
+ case klein4B_8bit = "klein4b-8bit"
154
+ case klein4B_4bit = "klein4b-4bit"
155
+ case bonsaiKlein4B_2bit = "bonsai-klein4b-2bit"
156
+ case bonsaiKlein4B_1bit = "bonsai-klein4b-1bit"
157
+ case klein9B_8bit = "klein9b-8bit"
158
+ case klein9B_4bit = "klein9b-4bit"
159
+
160
+ public var huggingFaceRepo: String {
161
+ switch self {
162
+ case .klein4B_bf16:
163
+ return "black-forest-labs/FLUX.2-klein-4B"
164
+ case .klein4B_8bit:
165
+ return "moxin-org/FLUX.2-klein-4B-8bit-mlx"
166
+ case .klein4B_4bit:
167
+ return "themindstudio/flux2-klein-4b-mlx-4bit"
168
+ case .bonsaiKlein4B_2bit:
169
+ return "prism-ml/bonsai-image-ternary-4B-mlx-2bit"
170
+ case .bonsaiKlein4B_1bit:
171
+ return "prism-ml/bonsai-image-binary-4B-mlx-1bit"
172
+ case .klein9B_8bit:
173
+ return "AITRADER/FLUX2-klein-9B-mlx-8bit"
174
+ case .klein9B_4bit:
175
+ return "themindstudio/flux2-klein-9b-mlx-4bit"
176
+ }
177
+ }
178
+
179
+ public var huggingFaceSubfolder: String? {
180
+ switch self {
181
+ case .klein4B_bf16:
182
+ return nil
183
+ case .klein4B_8bit, .klein4B_4bit, .bonsaiKlein4B_2bit, .bonsaiKlein4B_1bit, .klein9B_8bit, .klein9B_4bit:
184
+ return "transformer"
185
+ }
186
+ }
187
+
188
+ public var requiredSnapshotPatterns: [String] {
189
+ switch self {
190
+ case .klein4B_bf16:
191
+ return [
192
+ "config.json",
193
+ "model_index.json",
194
+ "flux-2-klein-4b*.safetensors",
195
+ "flux-2-klein-4b*.safetensors.index.json",
196
+ ]
197
+ case .klein4B_8bit:
198
+ return [
199
+ "transformer/config.json",
200
+ "transformer/quantized_8bit.safetensors",
201
+ ]
202
+ case .klein4B_4bit:
203
+ return [
204
+ "transformer/model.safetensors.index.json",
205
+ "transformer/*.safetensors",
206
+ ]
207
+ case .bonsaiKlein4B_2bit:
208
+ return [
209
+ "transformer-packed-mflux/config.json",
210
+ "transformer-packed-mflux/diffusion_pytorch_model.safetensors",
211
+ "transformer-packed-mflux/quantization_config.json",
212
+ ]
213
+ case .bonsaiKlein4B_1bit:
214
+ return [
215
+ "transformer-packed-mflux/config.json",
216
+ "transformer-packed-mflux/diffusion_pytorch_model.safetensors",
217
+ "transformer-packed-mflux/quantization_config.json",
218
+ ]
219
+ case .klein9B_8bit:
220
+ return [
221
+ "transformer/model.safetensors.index.json",
222
+ "transformer/*.safetensors",
223
+ ]
224
+ case .klein9B_4bit:
225
+ return [
226
+ "transformer/model.safetensors.index.json",
227
+ "transformer/*.safetensors",
228
+ ]
229
+ }
230
+ }
231
+
232
+ public var estimatedSizeGB: Int {
233
+ switch self {
234
+ case .klein4B_bf16: return 8
235
+ case .klein4B_8bit: return 4
236
+ case .klein4B_4bit: return 5
237
+ case .bonsaiKlein4B_2bit: return 3
238
+ case .bonsaiKlein4B_1bit: return 2
239
+ case .klein9B_8bit: return 10
240
+ case .klein9B_4bit: return 20
241
+ }
242
+ }
243
+
244
+ public var isGated: Bool { false }
245
+ public var license: String { "Apache 2.0" }
246
+ public var isCommercialUseAllowed: Bool { true }
247
+ public var defaultSteps: Int { 4 }
248
+ public var defaultGuidance: Float { 1.0 }
249
+ public var maxReferenceImages: Int { 4 }
250
+
251
+ public var quantization: TransformerQuantization {
252
+ switch self {
253
+ case .klein4B_bf16: return .bf16
254
+ case .klein4B_8bit: return .qint8
255
+ case .klein4B_4bit: return .int4
256
+ case .bonsaiKlein4B_2bit: return .int4
257
+ case .bonsaiKlein4B_1bit: return .int4
258
+ case .klein9B_8bit: return .qint8
259
+ case .klein9B_4bit: return .int4
260
+ }
261
+ }
262
+
263
+ public var modelType: Flux2Model {
264
+ switch self {
265
+ case .klein4B_bf16, .klein4B_8bit, .klein4B_4bit, .bonsaiKlein4B_2bit, .bonsaiKlein4B_1bit: return .klein4B
266
+ case .klein9B_8bit, .klein9B_4bit: return .klein9B
267
+ }
268
+ }
269
+
270
+ public var isForInference: Bool { true }
271
+ public var isForTraining: Bool { false }
272
+
273
+ public static func variant(for model: Flux2Model, quantization: TransformerQuantization) -> TransformerVariant {
274
+ switch model {
275
+ case .klein4B:
276
+ switch quantization {
277
+ case .bf16: return .klein4B_bf16
278
+ case .qint8: return .klein4B_8bit
279
+ case .int4: return .klein4B_4bit
280
+ }
281
+ case .klein9B:
282
+ switch quantization {
283
+ case .qint8: return .klein9B_8bit
284
+ case .int4: return .klein9B_4bit
285
+ case .bf16: return .klein9B_4bit
286
+ }
287
+ }
288
+ }
289
+ }
290
+
291
+ public enum VAEVariant: String, CaseIterable, Sendable {
292
+ case standard = "standard"
293
+ case smallDecoder = "small-decoder"
294
+ case bonsaiTernary = "bonsai-ternary"
295
+
296
+ public var huggingFaceRepo: String {
297
+ switch self {
298
+ case .standard:
299
+ return "black-forest-labs/FLUX.2-klein-4B"
300
+ case .smallDecoder:
301
+ return "black-forest-labs/FLUX.2-dev"
302
+ case .bonsaiTernary:
303
+ return "prism-ml/bonsai-image-ternary-4B-mlx-2bit"
304
+ }
305
+ }
306
+
307
+ public var huggingFaceSubfolder: String {
308
+ switch self {
309
+ case .standard, .smallDecoder, .bonsaiTernary:
310
+ return "vae"
311
+ }
312
+ }
313
+
314
+ public var requiredSnapshotPatterns: [String] {
315
+ switch self {
316
+ case .standard, .bonsaiTernary:
317
+ return [
318
+ "vae/config.json",
319
+ "vae/diffusion_pytorch_model.safetensors",
320
+ "vae/diffusion_pytorch_model.safetensors.index.json",
321
+ "vae/diffusion_pytorch_model-*.safetensors",
322
+ ]
323
+ case .smallDecoder:
324
+ return [
325
+ "vae/config.json",
326
+ "vae/diffusion_pytorch_model.safetensors",
327
+ "vae/diffusion_pytorch_model.safetensors.index.json",
328
+ "vae/diffusion_pytorch_model-*.safetensors",
329
+ ]
330
+ }
331
+ }
332
+
333
+ public var estimatedSizeGB: Int {
334
+ switch self {
335
+ case .standard, .bonsaiTernary: return 3
336
+ case .smallDecoder: return 1
337
+ }
338
+ }
339
+ public var isGated: Bool { false }
340
+ public var license: String { "Apache 2.0" }
341
+ public var isCommercialUseAllowed: Bool { true }
342
+ }
343
+
344
+ public enum ModelComponent: Hashable, Sendable {
345
+ case transformer(Flux2TransformerRepoProfile)
346
+ case textEncoder
347
+ case vae(VAEVariant)
348
+
349
+ public var displayName: String {
350
+ switch self {
351
+ case .transformer(let profile):
352
+ return "Flux.2 Transformer (\(profile.repoId))"
353
+ case .textEncoder:
354
+ return "Qwen3 Text Encoder"
355
+ case .vae:
356
+ return "Flux.2 VAE"
357
+ }
358
+ }
359
+
360
+ public var estimatedSizeGB: Int {
361
+ switch self {
362
+ case .transformer(let profile): return profile.estimatedSizeGB
363
+ case .textEncoder: return 4
364
+ case .vae(let variant): return variant.estimatedSizeGB
365
+ }
366
+ }
367
+
368
+ public var requiredSnapshotPatterns: [String] {
369
+ switch self {
370
+ case .transformer(let profile):
371
+ return profile.requiredSnapshotPatterns
372
+ case .textEncoder:
373
+ return []
374
+ case .vae(let variant):
375
+ return variant.requiredSnapshotPatterns
376
+ }
377
+ }
378
+ }
379
+
380
+ /// The `models/flux/` subdirectory inside the given container root.
381
+ public static func familyDirectory(containerRoot: URL) -> URL {
382
+ containerRoot
383
+ .appendingPathComponent("models", isDirectory: true)
384
+ .appendingPathComponent("flux", isDirectory: true)
385
+ }
386
+
387
+ /// The `models/flux/LoRAs/` subdirectory inside the given container root.
388
+ public static func loRAsDirectory(containerRoot: URL) -> URL {
389
+ familyDirectory(containerRoot: containerRoot)
390
+ .appendingPathComponent("LoRAs", isDirectory: true)
391
+ }
392
+
393
+ public static func localPath(for component: ModelComponent, containerRoot: URL) -> URL {
394
+ let family = familyDirectory(containerRoot: containerRoot)
395
+ switch component {
396
+ case .transformer(let profile):
397
+ return profile.relativePathComponents.reduce(family) { partial, component in
398
+ partial.appendingPathComponent(component, isDirectory: true)
399
+ }
400
+ case .textEncoder:
401
+ return family
402
+ case .vae(let variant):
403
+ let repoDir = variant.huggingFaceRepo
404
+ .replacingOccurrences(of: "/", with: "--")
405
+ return family
406
+ .appendingPathComponent("models--\(repoDir)")
407
+ .appendingPathComponent("vae")
408
+ }
409
+ }
410
+
411
+ public static func recommendedConfig(forRAMGB ram: Int) -> Flux2QuantizationConfig {
412
+ switch ram {
413
+ case 0..<32:
414
+ return .ultraMinimal
415
+ case 32..<48:
416
+ return .minimal
417
+ case 48..<64:
418
+ return .memoryEfficient
419
+ case 64..<96:
420
+ return .balanced
421
+ default:
422
+ return .highQuality
423
+ }
424
+ }
425
+ }