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,380 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import MLX
|
|
3
|
+
import MLXNN
|
|
4
|
+
|
|
5
|
+
public struct Qwen3TextConfig: Codable, Sendable {
|
|
6
|
+
public let vocabSize: Int
|
|
7
|
+
public let hiddenSize: Int
|
|
8
|
+
public let intermediateSize: Int
|
|
9
|
+
public let numHiddenLayers: Int
|
|
10
|
+
public let numAttentionHeads: Int
|
|
11
|
+
public let numKeyValueHeads: Int
|
|
12
|
+
public let maxPositionEmbeddings: Int
|
|
13
|
+
public let rmsNormEps: Float
|
|
14
|
+
public let ropeTheta: Float
|
|
15
|
+
public let tieWordEmbeddings: Bool
|
|
16
|
+
public let hiddenAct: String
|
|
17
|
+
public let attentionBias: Bool
|
|
18
|
+
public let attentionDropout: Float
|
|
19
|
+
public let headDim: Int
|
|
20
|
+
|
|
21
|
+
enum CodingKeys: String, CodingKey {
|
|
22
|
+
case vocabSize = "vocab_size"
|
|
23
|
+
case hiddenSize = "hidden_size"
|
|
24
|
+
case intermediateSize = "intermediate_size"
|
|
25
|
+
case numHiddenLayers = "num_hidden_layers"
|
|
26
|
+
case numAttentionHeads = "num_attention_heads"
|
|
27
|
+
case numKeyValueHeads = "num_key_value_heads"
|
|
28
|
+
case maxPositionEmbeddings = "max_position_embeddings"
|
|
29
|
+
case rmsNormEps = "rms_norm_eps"
|
|
30
|
+
case ropeTheta = "rope_theta"
|
|
31
|
+
case tieWordEmbeddings = "tie_word_embeddings"
|
|
32
|
+
case hiddenAct = "hidden_act"
|
|
33
|
+
case attentionBias = "attention_bias"
|
|
34
|
+
case attentionDropout = "attention_dropout"
|
|
35
|
+
case headDim = "head_dim"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public init(from decoder: Decoder) throws {
|
|
39
|
+
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
40
|
+
vocabSize = try container.decode(Int.self, forKey: .vocabSize)
|
|
41
|
+
hiddenSize = try container.decode(Int.self, forKey: .hiddenSize)
|
|
42
|
+
intermediateSize = try container.decode(Int.self, forKey: .intermediateSize)
|
|
43
|
+
numHiddenLayers = try container.decode(Int.self, forKey: .numHiddenLayers)
|
|
44
|
+
numAttentionHeads = try container.decode(Int.self, forKey: .numAttentionHeads)
|
|
45
|
+
numKeyValueHeads = try container.decode(Int.self, forKey: .numKeyValueHeads)
|
|
46
|
+
maxPositionEmbeddings = try container.decode(Int.self, forKey: .maxPositionEmbeddings)
|
|
47
|
+
rmsNormEps = try container.decodeIfPresent(Float.self, forKey: .rmsNormEps) ?? 1e-6
|
|
48
|
+
ropeTheta = try container.decodeIfPresent(Float.self, forKey: .ropeTheta) ?? 1_000_000.0
|
|
49
|
+
tieWordEmbeddings = try container.decodeIfPresent(Bool.self, forKey: .tieWordEmbeddings) ?? true
|
|
50
|
+
hiddenAct = try container.decodeIfPresent(String.self, forKey: .hiddenAct) ?? "silu"
|
|
51
|
+
attentionBias = try container.decodeIfPresent(Bool.self, forKey: .attentionBias) ?? false
|
|
52
|
+
attentionDropout = try container.decodeIfPresent(Float.self, forKey: .attentionDropout) ?? 0.0
|
|
53
|
+
headDim = try container.decodeIfPresent(Int.self, forKey: .headDim) ?? (hiddenSize / numAttentionHeads)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public static func load(from url: URL) throws -> Qwen3TextConfig {
|
|
57
|
+
try JSONDecoder().decode(Qwen3TextConfig.self, from: Data(contentsOf: url))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private struct Qwen3QuantizationConfig: Codable {
|
|
62
|
+
let groupSize: Int
|
|
63
|
+
let bits: Int
|
|
64
|
+
|
|
65
|
+
enum CodingKeys: String, CodingKey {
|
|
66
|
+
case groupSize = "group_size"
|
|
67
|
+
case bits
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private struct Qwen3ModelConfigWithQuantization: Codable {
|
|
72
|
+
let quantization: Qwen3QuantizationConfig?
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private struct TextEncoderMemoryConfig: Sendable {
|
|
76
|
+
let evalFrequency: Int
|
|
77
|
+
let clearCacheOnEval: Bool
|
|
78
|
+
|
|
79
|
+
static let aggressive = TextEncoderMemoryConfig(evalFrequency: 4, clearCacheOnEval: true)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public class TextRMSNorm: Module, UnaryLayer {
|
|
83
|
+
var weight: MLXArray
|
|
84
|
+
let eps: Float
|
|
85
|
+
|
|
86
|
+
public init(dimensions: Int, eps: Float = 1e-5) {
|
|
87
|
+
self.weight = MLXArray.ones([dimensions])
|
|
88
|
+
self.eps = eps
|
|
89
|
+
super.init()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public func callAsFunction(_ x: MLXArray) -> MLXArray {
|
|
93
|
+
let variance = mean(x * x, axis: -1, keepDims: true)
|
|
94
|
+
return weight * x * rsqrt(variance + MLXArray([eps]))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private final class Qwen3RoPE: Module {
|
|
99
|
+
let dimensions: Int
|
|
100
|
+
let base: Float
|
|
101
|
+
|
|
102
|
+
init(dimensions: Int, base: Float) {
|
|
103
|
+
self.dimensions = dimensions
|
|
104
|
+
self.base = base
|
|
105
|
+
super.init()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func callAsFunction(_ x: MLXArray) -> MLXArray {
|
|
109
|
+
MLXFast.RoPE(x, dimensions: dimensions, traditional: false, base: base, scale: 1.0, offset: 0)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private final class Qwen3Attention: Module {
|
|
114
|
+
let hiddenSize: Int
|
|
115
|
+
let numHeads: Int
|
|
116
|
+
let numKVHeads: Int
|
|
117
|
+
let headDim: Int
|
|
118
|
+
let scale: Float
|
|
119
|
+
|
|
120
|
+
@ModuleInfo var q_proj: Linear
|
|
121
|
+
@ModuleInfo var k_proj: Linear
|
|
122
|
+
@ModuleInfo var v_proj: Linear
|
|
123
|
+
@ModuleInfo var o_proj: Linear
|
|
124
|
+
@ModuleInfo var q_norm: TextRMSNorm
|
|
125
|
+
@ModuleInfo var k_norm: TextRMSNorm
|
|
126
|
+
let rope: Qwen3RoPE
|
|
127
|
+
|
|
128
|
+
init(config: Qwen3TextConfig) {
|
|
129
|
+
hiddenSize = config.hiddenSize
|
|
130
|
+
numHeads = config.numAttentionHeads
|
|
131
|
+
numKVHeads = config.numKeyValueHeads
|
|
132
|
+
headDim = config.headDim
|
|
133
|
+
scale = 1.0 / sqrt(Float(headDim))
|
|
134
|
+
_q_proj = ModuleInfo(wrappedValue: Linear(hiddenSize, numHeads * headDim, bias: config.attentionBias))
|
|
135
|
+
_k_proj = ModuleInfo(wrappedValue: Linear(hiddenSize, numKVHeads * headDim, bias: config.attentionBias))
|
|
136
|
+
_v_proj = ModuleInfo(wrappedValue: Linear(hiddenSize, numKVHeads * headDim, bias: config.attentionBias))
|
|
137
|
+
_o_proj = ModuleInfo(wrappedValue: Linear(numHeads * headDim, hiddenSize, bias: config.attentionBias))
|
|
138
|
+
_q_norm = ModuleInfo(wrappedValue: TextRMSNorm(dimensions: headDim, eps: config.rmsNormEps))
|
|
139
|
+
_k_norm = ModuleInfo(wrappedValue: TextRMSNorm(dimensions: headDim, eps: config.rmsNormEps))
|
|
140
|
+
rope = Qwen3RoPE(dimensions: headDim, base: config.ropeTheta)
|
|
141
|
+
super.init()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
func callAsFunction(_ hiddenStates: MLXArray, mask: MLXArray?) -> MLXArray {
|
|
145
|
+
let batchSize = hiddenStates.shape[0]
|
|
146
|
+
let seqLen = hiddenStates.shape[1]
|
|
147
|
+
|
|
148
|
+
var queries = q_proj(hiddenStates).reshaped([batchSize, seqLen, numHeads, headDim])
|
|
149
|
+
var keys = k_proj(hiddenStates).reshaped([batchSize, seqLen, numKVHeads, headDim])
|
|
150
|
+
var values = v_proj(hiddenStates).reshaped([batchSize, seqLen, numKVHeads, headDim])
|
|
151
|
+
|
|
152
|
+
queries = q_norm(queries).transposed(0, 2, 1, 3)
|
|
153
|
+
keys = k_norm(keys).transposed(0, 2, 1, 3)
|
|
154
|
+
values = values.transposed(0, 2, 1, 3)
|
|
155
|
+
|
|
156
|
+
queries = rope(queries)
|
|
157
|
+
keys = rope(keys)
|
|
158
|
+
|
|
159
|
+
let repeatFactor = numHeads / numKVHeads
|
|
160
|
+
if repeatFactor > 1 {
|
|
161
|
+
keys = MLX.broadcast(keys.expandedDimensions(axis: 2), to: [batchSize, numKVHeads, repeatFactor, seqLen, headDim])
|
|
162
|
+
.reshaped([batchSize, numHeads, seqLen, headDim])
|
|
163
|
+
values = MLX.broadcast(values.expandedDimensions(axis: 2), to: [batchSize, numKVHeads, repeatFactor, seqLen, headDim])
|
|
164
|
+
.reshaped([batchSize, numHeads, seqLen, headDim])
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let output = MLXFast.scaledDotProductAttention(
|
|
168
|
+
queries: queries,
|
|
169
|
+
keys: keys,
|
|
170
|
+
values: values,
|
|
171
|
+
scale: scale,
|
|
172
|
+
mask: mask
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return o_proj(output.transposed(0, 2, 1, 3).reshaped([batchSize, seqLen, numHeads * headDim]))
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private final class Qwen3MLP: Module, @unchecked Sendable {
|
|
180
|
+
@ModuleInfo var gate_proj: Linear
|
|
181
|
+
@ModuleInfo var up_proj: Linear
|
|
182
|
+
@ModuleInfo var down_proj: Linear
|
|
183
|
+
|
|
184
|
+
nonisolated(unsafe) private static let compiledGate: (MLXArray, MLXArray) -> MLXArray = compile { gate, up in
|
|
185
|
+
silu(gate) * up
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
init(config: Qwen3TextConfig) {
|
|
189
|
+
_gate_proj = ModuleInfo(wrappedValue: Linear(config.hiddenSize, config.intermediateSize, bias: false))
|
|
190
|
+
_up_proj = ModuleInfo(wrappedValue: Linear(config.hiddenSize, config.intermediateSize, bias: false))
|
|
191
|
+
_down_proj = ModuleInfo(wrappedValue: Linear(config.intermediateSize, config.hiddenSize, bias: false))
|
|
192
|
+
super.init()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
func callAsFunction(_ x: MLXArray) -> MLXArray {
|
|
196
|
+
down_proj(Self.compiledGate(gate_proj(x), up_proj(x)))
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private final class Qwen3DecoderLayer: Module {
|
|
201
|
+
let self_attn: Qwen3Attention
|
|
202
|
+
let mlp: Qwen3MLP
|
|
203
|
+
let input_layernorm: TextRMSNorm
|
|
204
|
+
let post_attention_layernorm: TextRMSNorm
|
|
205
|
+
|
|
206
|
+
init(config: Qwen3TextConfig) {
|
|
207
|
+
self_attn = Qwen3Attention(config: config)
|
|
208
|
+
mlp = Qwen3MLP(config: config)
|
|
209
|
+
input_layernorm = TextRMSNorm(dimensions: config.hiddenSize, eps: config.rmsNormEps)
|
|
210
|
+
post_attention_layernorm = TextRMSNorm(dimensions: config.hiddenSize, eps: config.rmsNormEps)
|
|
211
|
+
super.init()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
func callAsFunction(_ hiddenStates: MLXArray, mask: MLXArray?) -> MLXArray {
|
|
215
|
+
var hidden = hiddenStates + self_attn(input_layernorm(hiddenStates), mask: mask)
|
|
216
|
+
hidden = hidden + mlp(post_attention_layernorm(hidden))
|
|
217
|
+
return hidden
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private final class Qwen3Model: Module {
|
|
222
|
+
let config: Qwen3TextConfig
|
|
223
|
+
var memoryConfig: TextEncoderMemoryConfig = .aggressive
|
|
224
|
+
|
|
225
|
+
@ModuleInfo var embed_tokens: Embedding
|
|
226
|
+
var layers: [Qwen3DecoderLayer]
|
|
227
|
+
let norm: TextRMSNorm
|
|
228
|
+
|
|
229
|
+
init(config: Qwen3TextConfig) {
|
|
230
|
+
self.config = config
|
|
231
|
+
_embed_tokens = ModuleInfo(wrappedValue: Embedding(embeddingCount: config.vocabSize, dimensions: config.hiddenSize))
|
|
232
|
+
layers = (0..<config.numHiddenLayers).map { _ in Qwen3DecoderLayer(config: config) }
|
|
233
|
+
norm = TextRMSNorm(dimensions: config.hiddenSize, eps: config.rmsNormEps)
|
|
234
|
+
super.init()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
func forwardWithHiddenStates(
|
|
238
|
+
_ inputIds: MLXArray,
|
|
239
|
+
layerIndices: [Int],
|
|
240
|
+
attentionMask: MLXArray
|
|
241
|
+
) -> [Int: MLXArray] {
|
|
242
|
+
var hiddenStates = embed_tokens(inputIds)
|
|
243
|
+
eval(hiddenStates)
|
|
244
|
+
|
|
245
|
+
let mask = createCausalMask(seqLen: inputIds.shape[1], attentionMask: attentionMask)
|
|
246
|
+
eval(mask)
|
|
247
|
+
|
|
248
|
+
let layerSet = Set(layerIndices)
|
|
249
|
+
var extractedStates: [Int: MLXArray] = [:]
|
|
250
|
+
|
|
251
|
+
for (index, layer) in layers.enumerated() {
|
|
252
|
+
hiddenStates = layer(hiddenStates, mask: mask)
|
|
253
|
+
let layerIndex = index + 1
|
|
254
|
+
let shouldEval = layerSet.contains(layerIndex) || (index + 1) % memoryConfig.evalFrequency == 0
|
|
255
|
+
|
|
256
|
+
if shouldEval {
|
|
257
|
+
eval(hiddenStates)
|
|
258
|
+
if memoryConfig.clearCacheOnEval {
|
|
259
|
+
MLX.Memory.clearCache()
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if layerSet.contains(layerIndex) {
|
|
264
|
+
extractedStates[layerIndex] = hiddenStates
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if layerSet.contains(layers.count) {
|
|
269
|
+
let normalizedStates = norm(hiddenStates)
|
|
270
|
+
eval(normalizedStates)
|
|
271
|
+
extractedStates[layers.count] = normalizedStates
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
MLX.Memory.clearCache()
|
|
275
|
+
return extractedStates
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private func createCausalMask(seqLen: Int, attentionMask: MLXArray) -> MLXArray {
|
|
279
|
+
let rowIndices = MLXArray.arange(seqLen, dtype: .float32).expandedDimensions(axis: 1)
|
|
280
|
+
let colIndices = MLXArray.arange(seqLen, dtype: .float32).expandedDimensions(axis: 0)
|
|
281
|
+
let causalMask = MLX.where(
|
|
282
|
+
colIndices .<= rowIndices,
|
|
283
|
+
MLXArray(Float(0.0)),
|
|
284
|
+
MLXArray(-Float.infinity)
|
|
285
|
+
).reshaped([1, 1, seqLen, seqLen])
|
|
286
|
+
|
|
287
|
+
let paddingMask = MLX.where(
|
|
288
|
+
attentionMask .== Int32(1),
|
|
289
|
+
MLXArray(Float(0.0)),
|
|
290
|
+
MLXArray(Float(-1e9))
|
|
291
|
+
).reshaped([attentionMask.shape[0], 1, 1, attentionMask.shape[1]])
|
|
292
|
+
|
|
293
|
+
return causalMask + paddingMask
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private final class Qwen3TextEncoderModel: Module {
|
|
298
|
+
var model: Qwen3Model
|
|
299
|
+
|
|
300
|
+
init(config: Qwen3TextConfig) {
|
|
301
|
+
model = Qwen3Model(config: config)
|
|
302
|
+
super.init()
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public final class KleinTextEncoder: @unchecked Sendable {
|
|
307
|
+
private static let hiddenStateLayers = [9, 18, 27]
|
|
308
|
+
|
|
309
|
+
private let modelPath: URL
|
|
310
|
+
private var model: Qwen3TextEncoderModel?
|
|
311
|
+
|
|
312
|
+
public init(modelPath: URL) {
|
|
313
|
+
self.modelPath = modelPath
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
public func encode(inputIds: [Int], attentionMask: [Int]) throws -> MLXArray {
|
|
317
|
+
guard inputIds.count == attentionMask.count else {
|
|
318
|
+
throw Flux2Error.invalidConfiguration("inputIds and attentionMask must have the same length")
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let model = try loadModel()
|
|
322
|
+
let ids = MLXArray(inputIds.map(Int32.init)).reshaped([1, inputIds.count])
|
|
323
|
+
let mask = MLXArray(attentionMask.map(Int32.init)).reshaped([1, attentionMask.count])
|
|
324
|
+
let extractedStates = model.model.forwardWithHiddenStates(
|
|
325
|
+
ids,
|
|
326
|
+
layerIndices: Self.hiddenStateLayers,
|
|
327
|
+
attentionMask: mask
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
let states = try Self.hiddenStateLayers.map { layerIndex -> MLXArray in
|
|
331
|
+
guard let state = extractedStates[layerIndex] else {
|
|
332
|
+
throw Flux2Error.invalidConfiguration("Qwen3 text encoder did not return hidden state layer \(layerIndex)")
|
|
333
|
+
}
|
|
334
|
+
return state
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let embeddings = concatenated(states, axis: -1)
|
|
338
|
+
eval(embeddings)
|
|
339
|
+
|
|
340
|
+
// The text encoder is only needed to materialize prompt embeddings.
|
|
341
|
+
// Drop its 4B weights immediately so Flux image generation can load
|
|
342
|
+
// the transformer without keeping both models resident.
|
|
343
|
+
self.model = nil
|
|
344
|
+
MLX.Memory.clearCache()
|
|
345
|
+
return embeddings
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private func loadModel() throws -> Qwen3TextEncoderModel {
|
|
349
|
+
if let model {
|
|
350
|
+
return model
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let configURL = modelPath.appendingPathComponent("config.json")
|
|
354
|
+
let config = try Qwen3TextConfig.load(from: configURL)
|
|
355
|
+
let loaded = Qwen3TextEncoderModel(config: config)
|
|
356
|
+
|
|
357
|
+
let configData = try Data(contentsOf: configURL)
|
|
358
|
+
if let quantization = try? JSONDecoder().decode(Qwen3ModelConfigWithQuantization.self, from: configData).quantization {
|
|
359
|
+
quantize(model: loaded, groupSize: quantization.groupSize, bits: quantization.bits)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
var weights: [String: MLXArray] = [:]
|
|
363
|
+
for file in try FileManager.default.contentsOfDirectory(atPath: modelPath.path).filter({ $0.hasSuffix(".safetensors") }).sorted() {
|
|
364
|
+
let tensors = try loadArrays(url: modelPath.appendingPathComponent(file))
|
|
365
|
+
for (key, value) in tensors {
|
|
366
|
+
weights[key] = value
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
guard !weights.isEmpty else {
|
|
371
|
+
throw Flux2Error.modelNotLoaded("Qwen3 text encoder weights not found")
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
try loaded.update(parameters: ModuleParameters.unflattened(weights), verify: .none)
|
|
375
|
+
eval(loaded)
|
|
376
|
+
MLX.Memory.clearCache()
|
|
377
|
+
model = loaded
|
|
378
|
+
return loaded
|
|
379
|
+
}
|
|
380
|
+
}
|