bleam 0.0.9 → 0.0.11

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 (68) hide show
  1. package/dist/ai.cjs +608 -59
  2. package/dist/ai.d.cts +217 -89
  3. package/dist/ai.d.ts +217 -89
  4. package/dist/ai.js +601 -59
  5. package/dist/cli.cjs +142 -94
  6. package/dist/cli.d.cts +11 -27
  7. package/dist/cli.d.ts +11 -27
  8. package/dist/cli.js +143 -95
  9. package/dist/files.d.cts +1 -1
  10. package/dist/files.d.ts +1 -1
  11. package/dist/platform.cjs +1 -13
  12. package/dist/platform.d.cts +1 -26
  13. package/dist/platform.d.ts +1 -26
  14. package/dist/platform.js +1 -10
  15. package/dist/schema.d.cts +1 -1
  16. package/dist/schema.d.ts +1 -1
  17. package/dist/state.d.cts +1 -1
  18. package/dist/state.d.ts +1 -1
  19. package/dist/{ui-Bg11tvlc.d.ts → ui-CHc4xEs_.d.ts} +7 -7
  20. package/dist/ui.d.ts +1 -1
  21. package/dist/window.d.ts +1 -1
  22. package/package.json +2 -1
  23. package/templates/image-generation/app/index.tsx +3 -1
  24. package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +1 -1
  25. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +7 -2
  26. package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextGenerator.swift +46 -0
  27. package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextModel.swift +107 -0
  28. package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextSnapshotValidator.swift +201 -0
  29. package/templates/native/ios/Bleam/AI/TextGeneration/Core/GenerationTypes.swift +85 -0
  30. package/templates/native/ios/Bleam/AI/TextGeneration/Core/ModelCapabilities.swift +21 -0
  31. package/templates/native/ios/Bleam/AI/TextGeneration/Core/TextKVCache.swift +23 -0
  32. package/templates/native/ios/Bleam/AI/TextGeneration/Core/TokenSampler.swift +79 -0
  33. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3CheckpointLoader.swift +161 -0
  34. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Configuration.swift +119 -0
  35. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3InferenceSession.swift +84 -0
  36. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Layers.swift +197 -0
  37. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Model.swift +130 -0
  38. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Checkpoint.swift +102 -0
  39. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Configuration.swift +140 -0
  40. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Model.swift +112 -0
  41. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Session.swift +121 -0
  42. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridCache.swift +42 -0
  43. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridFullAttention.swift +57 -0
  44. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLayers.swift +58 -0
  45. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLinearAttention.swift +111 -0
  46. package/templates/native/ios/Bleam/AppDelegate.swift +0 -97
  47. package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +128 -121
  48. package/templates/native/ios/GenerationService/BonsaiTextGenerationRunner.swift +103 -0
  49. package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +19 -7
  50. package/templates/native/ios/GenerationService/main.swift +174 -48
  51. package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +3 -0
  52. package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +44 -0
  53. package/templates/native/modules/bleam-runtime/ios/AIModule.swift +225 -5
  54. package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +47 -0
  55. package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +0 -175
  56. package/templates/text-generation/app/index.tsx +93 -0
  57. package/templates/text-generation/app.config.ts +5 -0
  58. package/templates/updates/README.md +116 -0
  59. package/templates/updates/src/index.ts +292 -0
  60. package/templates/updates/src/schema.ts +230 -0
  61. package/templates/updates/tsconfig.json +12 -0
  62. package/templates/updates/wrangler.jsonc +19 -0
  63. package/templates/native/ios/PlatformHelper/Info.plist +0 -29
  64. package/templates/native/ios/PlatformHelper/main.swift +0 -335
  65. /package/dist/{files-DwA7pzr3.d.cts → files-4ZEoAWiv.d.ts} +0 -0
  66. /package/dist/{files-VrkQlKIT.d.ts → files-Dt5mbzLq.d.cts} +0 -0
  67. /package/dist/{schema-DOOjfXvs.d.ts → schema-CYh6n8GS.d.ts} +0 -0
  68. /package/dist/{schema-ENSMX_1t.d.cts → schema-oeOrd3i1.d.cts} +0 -0
@@ -0,0 +1,161 @@
1
+ import Foundation
2
+ import MLX
3
+ import MLXNN
4
+
5
+ public enum Qwen3CheckpointError: LocalizedError {
6
+ case missingFile(String)
7
+ case unsupportedArchitecture(String)
8
+ case unsupportedQuantization(bits: Int?, groupSize: Int?)
9
+ case invalidConfiguration(String)
10
+ case invalidCheckpoint(String)
11
+
12
+ public var errorDescription: String? {
13
+ switch self {
14
+ case .missingFile(let file): "Qwen3 checkpoint is missing \(file)"
15
+ case .unsupportedArchitecture(let architecture): "Unsupported Qwen3 architecture \(architecture)"
16
+ case .unsupportedQuantization(let bits, let groupSize):
17
+ "Bonsai Qwen3 requires 2-bit/group-128 affine weights, got bits=\(bits.map(String.init) ?? "nil") group=\(groupSize.map(String.init) ?? "nil")"
18
+ case .invalidConfiguration(let message): "Invalid Qwen3 configuration: \(message)"
19
+ case .invalidCheckpoint(let message): "Invalid Qwen3 checkpoint: \(message)"
20
+ }
21
+ }
22
+ }
23
+
24
+ public enum Qwen3CheckpointLoader {
25
+ private struct SafetensorsIndex: Decodable {
26
+ let weightMap: [String: String]
27
+
28
+ enum CodingKeys: String, CodingKey {
29
+ case weightMap = "weight_map"
30
+ }
31
+ }
32
+
33
+ public static func load(from modelURL: URL) throws -> Qwen3CausalLM {
34
+ let (configuration, quantization, tensors) = try loadSnapshot(from: modelURL, requireLanguageHead: true)
35
+
36
+ let model = Qwen3CausalLM(configuration: configuration)
37
+ MLXNN.quantize(
38
+ model: model,
39
+ groupSize: quantization.groupSize,
40
+ bits: quantization.bits,
41
+ mode: .affine
42
+ )
43
+ do {
44
+ try model.update(
45
+ parameters: ModuleParameters.unflattened(tensors),
46
+ verify: [.noUnusedKeys, .shapeMismatch]
47
+ )
48
+ } catch {
49
+ throw Qwen3CheckpointError.invalidCheckpoint(error.localizedDescription)
50
+ }
51
+ model.usesTiedOutputHead = configuration.tieWordEmbeddings && tensors["lm_head.weight"] == nil
52
+ MLX.eval(model)
53
+ MLX.Memory.clearCache()
54
+ return model
55
+ }
56
+
57
+ public static func loadTransformer(from modelURL: URL) throws -> Qwen3TransformerModel {
58
+ let (configuration, quantization, tensors) = try loadSnapshot(from: modelURL, requireLanguageHead: false)
59
+ let model = Qwen3TransformerModel(configuration: configuration)
60
+ MLXNN.quantize(
61
+ model: model,
62
+ groupSize: quantization.groupSize,
63
+ bits: quantization.bits,
64
+ mode: .affine
65
+ )
66
+ let transformerTensors: [String: MLXArray] = Dictionary(uniqueKeysWithValues: tensors.compactMap { name, value in
67
+ guard name.hasPrefix("model.") else { return nil }
68
+ return (String(name.dropFirst("model.".count)), value)
69
+ })
70
+ do {
71
+ try model.update(
72
+ parameters: ModuleParameters.unflattened(transformerTensors),
73
+ verify: [.noUnusedKeys, .shapeMismatch]
74
+ )
75
+ } catch {
76
+ throw Qwen3CheckpointError.invalidCheckpoint(error.localizedDescription)
77
+ }
78
+ MLX.eval(model)
79
+ MLX.Memory.clearCache()
80
+ return model
81
+ }
82
+
83
+ private static func loadSnapshot(
84
+ from modelURL: URL,
85
+ requireLanguageHead: Bool
86
+ ) throws -> (Qwen3ModelConfiguration, Qwen3ModelConfiguration.Quantization, [String: MLXArray]) {
87
+ let configuration = try Qwen3ModelConfiguration.load(from: modelURL)
88
+ try configuration.validate()
89
+ guard let quantization = configuration.quantization,
90
+ quantization.bits == 2, quantization.groupSize == 128 else {
91
+ throw Qwen3CheckpointError.unsupportedQuantization(
92
+ bits: configuration.quantization?.bits,
93
+ groupSize: configuration.quantization?.groupSize
94
+ )
95
+ }
96
+ let files = try checkpointFiles(at: modelURL)
97
+ var tensors: [String: MLXArray] = [:]
98
+ for file in files {
99
+ for (name, value) in try loadArrays(url: modelURL.appendingPathComponent(file)) {
100
+ tensors[name] = value
101
+ }
102
+ }
103
+ try validateTensors(
104
+ tensors,
105
+ configuration: configuration,
106
+ requireLanguageHead: requireLanguageHead
107
+ )
108
+ return (configuration, quantization, tensors)
109
+ }
110
+
111
+ private static func checkpointFiles(at modelURL: URL) throws -> [String] {
112
+ let fileManager = FileManager.default
113
+ let indexURL = modelURL.appendingPathComponent("model.safetensors.index.json")
114
+ if fileManager.fileExists(atPath: indexURL.path) {
115
+ let index = try JSONDecoder().decode(SafetensorsIndex.self, from: Data(contentsOf: indexURL))
116
+ let files = Set(index.weightMap.values).sorted()
117
+ for file in files where !fileManager.fileExists(atPath: modelURL.appendingPathComponent(file).path) {
118
+ throw Qwen3CheckpointError.missingFile(file)
119
+ }
120
+ return files
121
+ }
122
+ let file = "model.safetensors"
123
+ guard fileManager.fileExists(atPath: modelURL.appendingPathComponent(file).path) else {
124
+ throw Qwen3CheckpointError.missingFile(file)
125
+ }
126
+ return [file]
127
+ }
128
+
129
+ private static func validateTensors(
130
+ _ tensors: [String: MLXArray],
131
+ configuration: Qwen3ModelConfiguration,
132
+ requireLanguageHead: Bool
133
+ ) throws {
134
+ var requiredModules = ["model.embed_tokens"]
135
+ for index in 0..<configuration.hiddenLayerCount {
136
+ let prefix = "model.layers.\(index)"
137
+ requiredModules += [
138
+ "\(prefix).self_attn.q_proj", "\(prefix).self_attn.k_proj",
139
+ "\(prefix).self_attn.v_proj", "\(prefix).self_attn.o_proj",
140
+ "\(prefix).mlp.gate_proj", "\(prefix).mlp.up_proj", "\(prefix).mlp.down_proj",
141
+ ]
142
+ for norm in ["input_layernorm", "post_attention_layernorm", "self_attn.q_norm", "self_attn.k_norm"] {
143
+ let key = "\(prefix).\(norm).weight"
144
+ guard tensors[key] != nil else { throw Qwen3CheckpointError.invalidCheckpoint("missing tensor \(key)") }
145
+ }
146
+ }
147
+ if requireLanguageHead, !configuration.tieWordEmbeddings { requiredModules.append("lm_head") }
148
+ for module in requiredModules {
149
+ for suffix in ["weight", "scales", "biases"] {
150
+ let key = "\(module).\(suffix)"
151
+ guard tensors[key] != nil else { throw Qwen3CheckpointError.invalidCheckpoint("missing tensor \(key)") }
152
+ }
153
+ guard tensors["\(module).weight"]?.dtype == .uint32 else {
154
+ throw Qwen3CheckpointError.invalidCheckpoint("\(module).weight is not packed uint32")
155
+ }
156
+ }
157
+ guard tensors["model.norm.weight"] != nil else {
158
+ throw Qwen3CheckpointError.invalidCheckpoint("missing tensor model.norm.weight")
159
+ }
160
+ }
161
+ }
@@ -0,0 +1,119 @@
1
+ import Foundation
2
+
3
+ public struct Qwen3ModelConfiguration: Decodable, Sendable {
4
+ public struct RopeScaling: Decodable, Sendable {
5
+ public let ropeType: String
6
+ public let factor: Float
7
+ public let originalMaximumPositions: Int
8
+ public let betaFast: Float
9
+ public let betaSlow: Float
10
+
11
+ enum CodingKeys: String, CodingKey {
12
+ case ropeType = "rope_type"
13
+ case type
14
+ case factor
15
+ case originalMaximumPositions = "original_max_position_embeddings"
16
+ case betaFast = "beta_fast"
17
+ case betaSlow = "beta_slow"
18
+ }
19
+
20
+ public init(from decoder: Decoder) throws {
21
+ let values = try decoder.container(keyedBy: CodingKeys.self)
22
+ ropeType = try values.decodeIfPresent(String.self, forKey: .ropeType)
23
+ ?? values.decodeIfPresent(String.self, forKey: .type)
24
+ ?? "yarn"
25
+ factor = try values.decode(Float.self, forKey: .factor)
26
+ originalMaximumPositions = try values.decode(Int.self, forKey: .originalMaximumPositions)
27
+ betaFast = try values.decodeIfPresent(Float.self, forKey: .betaFast) ?? 32
28
+ betaSlow = try values.decodeIfPresent(Float.self, forKey: .betaSlow) ?? 1
29
+ }
30
+ }
31
+
32
+ public struct Quantization: Decodable, Sendable {
33
+ public let bits: Int
34
+ public let groupSize: Int
35
+
36
+ enum CodingKeys: String, CodingKey {
37
+ case bits
38
+ case groupSize = "group_size"
39
+ }
40
+ }
41
+
42
+ public let architectures: [String]
43
+ public let modelType: String
44
+ public let vocabularySize: Int
45
+ public let hiddenSize: Int
46
+ public let intermediateSize: Int
47
+ public let hiddenLayerCount: Int
48
+ public let attentionHeadCount: Int
49
+ public let keyValueHeadCount: Int
50
+ public let maximumPositionCount: Int
51
+ public let rmsNormEpsilon: Float
52
+ public let ropeTheta: Float
53
+ public let tieWordEmbeddings: Bool
54
+ public let attentionBias: Bool
55
+ public let headDimension: Int
56
+ public let ropeScaling: RopeScaling?
57
+ public let quantization: Quantization?
58
+
59
+ enum CodingKeys: String, CodingKey {
60
+ case architectures
61
+ case modelType = "model_type"
62
+ case vocabularySize = "vocab_size"
63
+ case hiddenSize = "hidden_size"
64
+ case intermediateSize = "intermediate_size"
65
+ case hiddenLayerCount = "num_hidden_layers"
66
+ case attentionHeadCount = "num_attention_heads"
67
+ case keyValueHeadCount = "num_key_value_heads"
68
+ case maximumPositionCount = "max_position_embeddings"
69
+ case rmsNormEpsilon = "rms_norm_eps"
70
+ case ropeTheta = "rope_theta"
71
+ case tieWordEmbeddings = "tie_word_embeddings"
72
+ case attentionBias = "attention_bias"
73
+ case headDimension = "head_dim"
74
+ case ropeScaling = "rope_scaling"
75
+ case quantization
76
+ }
77
+
78
+ public init(from decoder: Decoder) throws {
79
+ let values = try decoder.container(keyedBy: CodingKeys.self)
80
+ architectures = try values.decodeIfPresent([String].self, forKey: .architectures) ?? []
81
+ modelType = try values.decodeIfPresent(String.self, forKey: .modelType) ?? "qwen3"
82
+ vocabularySize = try values.decode(Int.self, forKey: .vocabularySize)
83
+ hiddenSize = try values.decode(Int.self, forKey: .hiddenSize)
84
+ intermediateSize = try values.decode(Int.self, forKey: .intermediateSize)
85
+ hiddenLayerCount = try values.decode(Int.self, forKey: .hiddenLayerCount)
86
+ attentionHeadCount = try values.decode(Int.self, forKey: .attentionHeadCount)
87
+ keyValueHeadCount = try values.decode(Int.self, forKey: .keyValueHeadCount)
88
+ maximumPositionCount = try values.decode(Int.self, forKey: .maximumPositionCount)
89
+ rmsNormEpsilon = try values.decodeIfPresent(Float.self, forKey: .rmsNormEpsilon) ?? 1e-6
90
+ ropeTheta = try values.decodeIfPresent(Float.self, forKey: .ropeTheta) ?? 1_000_000
91
+ tieWordEmbeddings = try values.decodeIfPresent(Bool.self, forKey: .tieWordEmbeddings) ?? true
92
+ attentionBias = try values.decodeIfPresent(Bool.self, forKey: .attentionBias) ?? false
93
+ headDimension = try values.decodeIfPresent(Int.self, forKey: .headDimension)
94
+ ?? hiddenSize / attentionHeadCount
95
+ ropeScaling = try values.decodeIfPresent(RopeScaling.self, forKey: .ropeScaling)
96
+ quantization = try values.decodeIfPresent(Quantization.self, forKey: .quantization)
97
+ }
98
+
99
+ public static func load(from modelURL: URL) throws -> Qwen3ModelConfiguration {
100
+ try JSONDecoder().decode(
101
+ Qwen3ModelConfiguration.self,
102
+ from: Data(contentsOf: modelURL.appendingPathComponent("config.json"))
103
+ )
104
+ }
105
+
106
+ public func validate() throws {
107
+ guard modelType == "qwen3", architectures.isEmpty || architectures.contains("Qwen3ForCausalLM") else {
108
+ throw Qwen3CheckpointError.unsupportedArchitecture(architectures.first ?? modelType)
109
+ }
110
+ guard hiddenSize > 0, intermediateSize > 0, hiddenLayerCount > 0,
111
+ attentionHeadCount > 0, keyValueHeadCount > 0,
112
+ attentionHeadCount.isMultiple(of: keyValueHeadCount), headDimension > 0 else {
113
+ throw Qwen3CheckpointError.invalidConfiguration("invalid model dimensions")
114
+ }
115
+ if let ropeScaling, ropeScaling.ropeType != "yarn" {
116
+ throw Qwen3CheckpointError.invalidConfiguration("unsupported RoPE scaling type \(ropeScaling.ropeType)")
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,84 @@
1
+ import MLX
2
+
3
+ public final class Qwen3InferenceSession: @unchecked Sendable {
4
+ private let model: Qwen3CausalLM
5
+ private let endTokenIDs: Set<Int>
6
+
7
+ public init(model: Qwen3CausalLM, endTokenIDs: Set<Int> = [151643, 151645]) {
8
+ self.model = model
9
+ self.endTokenIDs = endTokenIDs
10
+ }
11
+
12
+ public func generate(
13
+ inputIDs: [Int],
14
+ parameters: GenerationParameters = .balanced,
15
+ isCancelled: (() -> Bool)? = nil,
16
+ onToken: ((Int) -> Bool)? = nil
17
+ ) throws -> TokenGenerationResult {
18
+ try TokenSampler.validate(parameters)
19
+ guard !inputIDs.isEmpty else { throw TextGenerationError.emptyPrompt }
20
+ for token in inputIDs where token < 0 || token >= model.vocabularySize {
21
+ throw TextGenerationError.invalidTokenID(token)
22
+ }
23
+ let requestedLength = inputIDs.count + parameters.maximumNewTokens
24
+ guard requestedLength <= model.maximumContextLength else {
25
+ throw TextGenerationError.contextExceeded(
26
+ requested: requestedLength,
27
+ maximum: model.maximumContextLength
28
+ )
29
+ }
30
+ guard parameters.maximumNewTokens > 0 else {
31
+ return TokenGenerationResult(promptTokenCount: inputIDs.count, tokenIDs: [], finishReason: .length)
32
+ }
33
+ if isCancelled?() == true {
34
+ return TokenGenerationResult(promptTokenCount: inputIDs.count, tokenIDs: [], finishReason: .cancelled)
35
+ }
36
+ if let seed = parameters.seed { MLXRandom.seed(seed) }
37
+
38
+ let cache = model.makeCache()
39
+ defer {
40
+ cache.forEach { $0.clear() }
41
+ MLX.Memory.clearCache()
42
+ }
43
+ var logits = model.logits(
44
+ for: MLXArray(inputIDs.map(Int32.init)).reshaped([1, inputIDs.count]),
45
+ cache: cache
46
+ )
47
+ var generated: [Int] = []
48
+ var history = inputIDs
49
+ var finishReason: GenerationFinishReason = .length
50
+
51
+ for _ in 0..<parameters.maximumNewTokens {
52
+ if isCancelled?() == true {
53
+ finishReason = .cancelled
54
+ break
55
+ }
56
+ let sampled = try TokenSampler.sample(
57
+ logits: logits,
58
+ parameters: parameters,
59
+ tokenHistory: history
60
+ )
61
+ MLX.eval(sampled)
62
+ let token = Int(sampled.item(Int32.self))
63
+ if endTokenIDs.contains(token) {
64
+ finishReason = .endOfSequence
65
+ break
66
+ }
67
+ generated.append(token)
68
+ history.append(token)
69
+ if onToken?(token) == false {
70
+ finishReason = .callbackStopped
71
+ break
72
+ }
73
+ logits = model.logits(
74
+ for: MLXArray([Int32(token)]).reshaped([1, 1]),
75
+ cache: cache
76
+ )
77
+ }
78
+ return TokenGenerationResult(
79
+ promptTokenCount: inputIDs.count,
80
+ tokenIDs: generated,
81
+ finishReason: finishReason
82
+ )
83
+ }
84
+ }
@@ -0,0 +1,197 @@
1
+ import Foundation
2
+ import MLX
3
+ import MLXNN
4
+
5
+ public final class Qwen3RMSNorm: Module, UnaryLayer {
6
+ @ParameterInfo public var weight: MLXArray
7
+ public let epsilon: Float
8
+
9
+ public init(dimensions: Int, epsilon: Float) {
10
+ _weight = ParameterInfo(wrappedValue: MLXArray.ones([dimensions]))
11
+ self.epsilon = epsilon
12
+ super.init()
13
+ }
14
+
15
+ public func callAsFunction(_ x: MLXArray) -> MLXArray {
16
+ MLXFast.rmsNorm(x, weight: weight, eps: epsilon)
17
+ }
18
+ }
19
+
20
+ public final class Qwen3RotaryEmbedding: Module {
21
+ private let dimensions: Int
22
+ private let base: Float
23
+ private let frequencies: MLXArray?
24
+ private let magnitudeScale: Float
25
+
26
+ public init(configuration: Qwen3ModelConfiguration) {
27
+ dimensions = configuration.headDimension
28
+ base = configuration.ropeTheta
29
+ if let scaling = configuration.ropeScaling {
30
+ precondition(configuration.headDimension.isMultiple(of: 2))
31
+ func correctionDimension(_ rotations: Float) -> Float {
32
+ Float(configuration.headDimension)
33
+ * log(Float(scaling.originalMaximumPositions) / (rotations * 2 * .pi))
34
+ / (2 * log(configuration.ropeTheta))
35
+ }
36
+ let low = max(Int(floor(correctionDimension(scaling.betaFast))), 0)
37
+ let high = min(Int(ceil(correctionDimension(scaling.betaSlow))), configuration.headDimension - 1)
38
+ let safeHigh = low == high ? Float(high) + 0.001 : Float(high)
39
+ let ramp = clip(
40
+ (MLXArray(0..<(configuration.headDimension / 2)).asType(.float32) - Float(low))
41
+ / (safeHigh - Float(low)),
42
+ min: 0,
43
+ max: 1
44
+ )
45
+ let exponent = MLXArray(stride(from: 0, to: configuration.headDimension, by: 2))
46
+ .asType(.float32) / Float(configuration.headDimension)
47
+ let unscaled = pow(configuration.ropeTheta, exponent)
48
+ let interpolated = scaling.factor * unscaled
49
+ let mask = 1 - ramp
50
+ frequencies = (interpolated * unscaled) / (interpolated * mask + unscaled * (1 - mask))
51
+ magnitudeScale = scaling.factor <= 1 ? 1 : 0.1 * log(scaling.factor) + 1
52
+ } else {
53
+ frequencies = nil
54
+ magnitudeScale = 1
55
+ }
56
+ super.init()
57
+ }
58
+
59
+ public func callAsFunction(_ x: MLXArray, offset: Int) -> MLXArray {
60
+ MLXFast.RoPE(
61
+ magnitudeScale == 1 ? x : x * magnitudeScale,
62
+ dimensions: dimensions,
63
+ traditional: false,
64
+ base: frequencies == nil ? base : nil,
65
+ scale: 1,
66
+ offset: offset,
67
+ freqs: frequencies
68
+ )
69
+ }
70
+ }
71
+
72
+ public final class Qwen3SelfAttention: Module {
73
+ private let headCount: Int
74
+ private let keyValueHeadCount: Int
75
+ private let headDimension: Int
76
+ private let scale: Float
77
+
78
+ @ModuleInfo public var q_proj: Linear
79
+ @ModuleInfo public var k_proj: Linear
80
+ @ModuleInfo public var v_proj: Linear
81
+ @ModuleInfo public var o_proj: Linear
82
+ @ModuleInfo public var q_norm: Qwen3RMSNorm
83
+ @ModuleInfo public var k_norm: Qwen3RMSNorm
84
+ @ModuleInfo public var rope: Qwen3RotaryEmbedding
85
+
86
+ public init(configuration: Qwen3ModelConfiguration) {
87
+ headCount = configuration.attentionHeadCount
88
+ keyValueHeadCount = configuration.keyValueHeadCount
89
+ headDimension = configuration.headDimension
90
+ scale = 1 / sqrt(Float(configuration.headDimension))
91
+ _q_proj = ModuleInfo(wrappedValue: Linear(
92
+ configuration.hiddenSize,
93
+ configuration.attentionHeadCount * configuration.headDimension,
94
+ bias: configuration.attentionBias
95
+ ))
96
+ _k_proj = ModuleInfo(wrappedValue: Linear(
97
+ configuration.hiddenSize,
98
+ configuration.keyValueHeadCount * configuration.headDimension,
99
+ bias: configuration.attentionBias
100
+ ))
101
+ _v_proj = ModuleInfo(wrappedValue: Linear(
102
+ configuration.hiddenSize,
103
+ configuration.keyValueHeadCount * configuration.headDimension,
104
+ bias: configuration.attentionBias
105
+ ))
106
+ _o_proj = ModuleInfo(wrappedValue: Linear(
107
+ configuration.attentionHeadCount * configuration.headDimension,
108
+ configuration.hiddenSize,
109
+ bias: configuration.attentionBias
110
+ ))
111
+ _q_norm = ModuleInfo(wrappedValue: Qwen3RMSNorm(
112
+ dimensions: configuration.headDimension,
113
+ epsilon: configuration.rmsNormEpsilon
114
+ ))
115
+ _k_norm = ModuleInfo(wrappedValue: Qwen3RMSNorm(
116
+ dimensions: configuration.headDimension,
117
+ epsilon: configuration.rmsNormEpsilon
118
+ ))
119
+ _rope = ModuleInfo(wrappedValue: Qwen3RotaryEmbedding(configuration: configuration))
120
+ super.init()
121
+ }
122
+
123
+ public func callAsFunction(_ hiddenStates: MLXArray, mask: MLXArray?, cache: TextKVCache?) -> MLXArray {
124
+ let batchSize = hiddenStates.dim(0)
125
+ let sequenceLength = hiddenStates.dim(1)
126
+ var queries = q_proj(hiddenStates).reshaped([batchSize, sequenceLength, headCount, headDimension])
127
+ var keys = k_proj(hiddenStates).reshaped([batchSize, sequenceLength, keyValueHeadCount, headDimension])
128
+ var values = v_proj(hiddenStates).reshaped([batchSize, sequenceLength, keyValueHeadCount, headDimension])
129
+ queries = q_norm(queries).transposed(0, 2, 1, 3)
130
+ keys = k_norm(keys).transposed(0, 2, 1, 3)
131
+ values = values.transposed(0, 2, 1, 3)
132
+ let offset = cache?.length ?? 0
133
+ queries = rope(queries, offset: offset)
134
+ keys = rope(keys, offset: offset)
135
+ if let cache {
136
+ (keys, values) = cache.update(keys: keys, values: values)
137
+ }
138
+ let attended = MLXFast.scaledDotProductAttention(
139
+ queries: queries,
140
+ keys: keys,
141
+ values: values,
142
+ scale: scale,
143
+ mask: mask
144
+ )
145
+ return o_proj(attended.transposed(0, 2, 1, 3).reshaped([
146
+ batchSize, sequenceLength, headCount * headDimension,
147
+ ]))
148
+ }
149
+ }
150
+
151
+ public final class Qwen3FeedForward: Module, @unchecked Sendable {
152
+ @ModuleInfo public var gate_proj: Linear
153
+ @ModuleInfo public var up_proj: Linear
154
+ @ModuleInfo public var down_proj: Linear
155
+
156
+ nonisolated(unsafe) private static let activate: (MLXArray, MLXArray) -> MLXArray = compile {
157
+ silu($0) * $1
158
+ }
159
+
160
+ public init(configuration: Qwen3ModelConfiguration) {
161
+ _gate_proj = ModuleInfo(wrappedValue: Linear(configuration.hiddenSize, configuration.intermediateSize, bias: false))
162
+ _up_proj = ModuleInfo(wrappedValue: Linear(configuration.hiddenSize, configuration.intermediateSize, bias: false))
163
+ _down_proj = ModuleInfo(wrappedValue: Linear(configuration.intermediateSize, configuration.hiddenSize, bias: false))
164
+ super.init()
165
+ }
166
+
167
+ public func callAsFunction(_ x: MLXArray) -> MLXArray {
168
+ down_proj(Self.activate(gate_proj(x), up_proj(x)))
169
+ }
170
+ }
171
+
172
+ public final class Qwen3DecoderBlock: Module {
173
+ @ModuleInfo public var self_attn: Qwen3SelfAttention
174
+ @ModuleInfo public var mlp: Qwen3FeedForward
175
+ @ModuleInfo public var input_layernorm: Qwen3RMSNorm
176
+ @ModuleInfo public var post_attention_layernorm: Qwen3RMSNorm
177
+
178
+ public init(configuration: Qwen3ModelConfiguration) {
179
+ _self_attn = ModuleInfo(wrappedValue: Qwen3SelfAttention(configuration: configuration))
180
+ _mlp = ModuleInfo(wrappedValue: Qwen3FeedForward(configuration: configuration))
181
+ _input_layernorm = ModuleInfo(wrappedValue: Qwen3RMSNorm(
182
+ dimensions: configuration.hiddenSize,
183
+ epsilon: configuration.rmsNormEpsilon
184
+ ))
185
+ _post_attention_layernorm = ModuleInfo(wrappedValue: Qwen3RMSNorm(
186
+ dimensions: configuration.hiddenSize,
187
+ epsilon: configuration.rmsNormEpsilon
188
+ ))
189
+ super.init()
190
+ }
191
+
192
+ public func callAsFunction(_ hiddenStates: MLXArray, mask: MLXArray?, cache: TextKVCache?) -> MLXArray {
193
+ var hidden = hiddenStates + self_attn(input_layernorm(hiddenStates), mask: mask, cache: cache)
194
+ hidden = hidden + mlp(post_attention_layernorm(hidden))
195
+ return hidden
196
+ }
197
+ }
@@ -0,0 +1,130 @@
1
+ import MLX
2
+ import MLXNN
3
+
4
+ public final class Qwen3TransformerModel: Module, HiddenStateModel {
5
+ public let configuration: Qwen3ModelConfiguration
6
+
7
+ @ModuleInfo public var embed_tokens: Embedding
8
+ public var layers: [Qwen3DecoderBlock]
9
+ @ModuleInfo public var norm: Qwen3RMSNorm
10
+
11
+ public var vocabularySize: Int { configuration.vocabularySize }
12
+ public var maximumContextLength: Int { configuration.maximumPositionCount }
13
+
14
+ public init(configuration: Qwen3ModelConfiguration) {
15
+ self.configuration = configuration
16
+ _embed_tokens = ModuleInfo(wrappedValue: Embedding(
17
+ embeddingCount: configuration.vocabularySize,
18
+ dimensions: configuration.hiddenSize
19
+ ))
20
+ layers = (0..<configuration.hiddenLayerCount).map { _ in
21
+ Qwen3DecoderBlock(configuration: configuration)
22
+ }
23
+ _norm = ModuleInfo(wrappedValue: Qwen3RMSNorm(
24
+ dimensions: configuration.hiddenSize,
25
+ epsilon: configuration.rmsNormEpsilon
26
+ ))
27
+ super.init()
28
+ }
29
+
30
+ public func callAsFunction(_ inputIDs: MLXArray, cache: [TextKVCache]? = nil) -> MLXArray {
31
+ var hidden = embed_tokens(inputIDs)
32
+ let mask = causalMask(sequenceLength: inputIDs.dim(1), offset: cache?.first?.length ?? 0)
33
+ for (index, layer) in layers.enumerated() {
34
+ hidden = layer(hidden, mask: mask, cache: cache?[index])
35
+ }
36
+ return norm(hidden)
37
+ }
38
+
39
+ public func hiddenStates(
40
+ for inputIDs: MLXArray,
41
+ at layerIndices: Set<Int>,
42
+ attentionMask: MLXArray? = nil
43
+ ) -> [Int: MLXArray] {
44
+ var hidden = embed_tokens(inputIDs)
45
+ let mask = causalMask(sequenceLength: inputIDs.dim(1), offset: 0, attentionMask: attentionMask)
46
+ var result: [Int: MLXArray] = [:]
47
+ if layerIndices.contains(0) {
48
+ MLX.eval(hidden)
49
+ result[0] = hidden
50
+ }
51
+ for (index, layer) in layers.enumerated() {
52
+ hidden = layer(hidden, mask: mask, cache: nil)
53
+ let layerIndex = index + 1
54
+ if layerIndices.contains(layerIndex) {
55
+ let state = layerIndex == layers.count ? norm(hidden) : hidden
56
+ MLX.eval(state)
57
+ result[layerIndex] = state
58
+ }
59
+ }
60
+ return result
61
+ }
62
+
63
+ private func causalMask(
64
+ sequenceLength: Int,
65
+ offset: Int,
66
+ attentionMask: MLXArray? = nil
67
+ ) -> MLXArray? {
68
+ if sequenceLength == 1, attentionMask == nil { return nil }
69
+ let totalLength = sequenceLength + offset
70
+ let rows = MLXArray.arange(sequenceLength, dtype: .float32).expandedDimensions(axis: 1)
71
+ let columns = MLXArray.arange(totalLength, dtype: .float32).expandedDimensions(axis: 0)
72
+ var mask = MLX.where(
73
+ columns .<= rows + Float(offset),
74
+ MLXArray(Float(0)),
75
+ MLXArray(-Float.infinity)
76
+ ).reshaped([1, 1, sequenceLength, totalLength])
77
+ if let attentionMask {
78
+ let padding = MLX.where(
79
+ attentionMask .== Int32(1),
80
+ MLXArray(Float(0)),
81
+ MLXArray(Float(-1e9))
82
+ ).reshaped([attentionMask.dim(0), 1, 1, attentionMask.dim(1)])
83
+ mask = mask + padding
84
+ }
85
+ return mask
86
+ }
87
+ }
88
+
89
+ public final class Qwen3CausalLM: Module, CausalLanguageModel {
90
+ public let configuration: Qwen3ModelConfiguration
91
+ @ModuleInfo public var model: Qwen3TransformerModel
92
+ @ModuleInfo public var lm_head: Linear
93
+ public var usesTiedOutputHead: Bool
94
+
95
+ public var vocabularySize: Int { configuration.vocabularySize }
96
+ public var maximumContextLength: Int { configuration.maximumPositionCount }
97
+
98
+ public init(configuration: Qwen3ModelConfiguration) {
99
+ self.configuration = configuration
100
+ usesTiedOutputHead = configuration.tieWordEmbeddings
101
+ _model = ModuleInfo(wrappedValue: Qwen3TransformerModel(configuration: configuration))
102
+ _lm_head = ModuleInfo(wrappedValue: Linear(configuration.hiddenSize, configuration.vocabularySize, bias: false))
103
+ super.init()
104
+ }
105
+
106
+ public func logits(for inputIDs: MLXArray, cache: [TextKVCache]) -> MLXArray {
107
+ project(model(inputIDs, cache: cache))
108
+ }
109
+
110
+ public func makeCache() -> [TextKVCache] {
111
+ (0..<configuration.hiddenLayerCount).map { _ in TextKVCache() }
112
+ }
113
+
114
+ private func project(_ hiddenStates: MLXArray) -> MLXArray {
115
+ guard usesTiedOutputHead else { return lm_head(hiddenStates) }
116
+ if let embedding = model.embed_tokens as? QuantizedEmbedding {
117
+ return MLX.quantizedMM(
118
+ hiddenStates,
119
+ embedding.weight,
120
+ scales: embedding.scales,
121
+ biases: embedding.biases,
122
+ transpose: true,
123
+ groupSize: embedding.groupSize,
124
+ bits: embedding.bits,
125
+ mode: .affine
126
+ )
127
+ }
128
+ return matmul(hiddenStates, model.embed_tokens.weight.T)
129
+ }
130
+ }