Files
PhoneClaw/Packages/InferenceKit/Libraries/MLXLMCommon/BaseConfiguration.swift
T

170 lines
6.0 KiB
Swift

// Copyright © 2025 Apple Inc.
import Foundation
import MLX
/// Base ``LanguageModel`` configuration -- provides `modelType`
/// and `quantization` (used in loading the model).
///
/// This is used by ``ModelFactory/load(from:configuration:progressHandler:)``
/// to determine the type of model to load.
public struct BaseConfiguration: Codable, Sendable {
public let modelType: String
public struct Quantization: Codable, Sendable, Equatable {
public init(groupSize: Int, bits: Int) {
self.groupSize = groupSize
self.bits = bits
}
public let groupSize: Int
public let bits: Int
private var _mode: QuantizationMode? = nil
public var mode: QuantizationMode { _mode ?? .affine }
public var asTuple: (Int, Int, QuantizationMode) { (groupSize, bits, mode) }
enum CodingKeys: String, CodingKey {
case groupSize = "group_size"
case bits = "bits"
case _mode = "mode"
}
}
/// handling instructions for ``PerLayerQuantization``
public enum QuantizationOption: Sendable {
case skip
case quantize(Quantization)
}
/// Per-layer ``Quantization`` values with optional default.
public struct PerLayerQuantization: Sendable {
public var quantization: Quantization? = nil
public var perLayerQuantization: [String: QuantizationOption]
public init(
quantization: BaseConfiguration.Quantization? = nil,
perLayerQuantization: [String: BaseConfiguration.QuantizationOption]
) {
self.quantization = quantization
self.perLayerQuantization = perLayerQuantization
}
/// The quantization to apply for the given layer name or nil for no quantization.
public func quantization(layer: String) -> Quantization? {
if let perLayer = perLayerQuantization[layer] {
switch perLayer {
case .skip:
return nil
case .quantize(let quantization):
return quantization
}
} else {
return quantization
}
}
}
/// Special codable to support a mixed key: Int / key: Quantization
/// structure for hereogenous quantization, e.g.
///
/// ```
/// "quantization": {
/// "group_size": 64,
/// "bits": 4,
/// "model.embed_tokens": {
/// "group_size": 32,
/// "bits": 4
/// },
/// "model.layers.0.self_attn.q_norm": false,
/// ```
///
/// This mixed type structure requires manual decoding.
struct QuantizationContainer: Codable, Sendable {
var quantization: Quantization
var perLayerQuantization: PerLayerQuantization
// based on Dictionary's coding key
internal struct _DictionaryCodingKey: CodingKey {
internal let stringValue: String
internal let intValue: Int?
internal init(stringValue: String) {
self.stringValue = stringValue
self.intValue = Int(stringValue)
}
internal init(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
}
init(from decoder: any Decoder) throws {
// handle the embedded Quantization
self.quantization = try Quantization(from: decoder)
// and the interleaved per-layer values
var perLayerQuantization = [String: QuantizationOption]()
let container = try decoder.container(keyedBy: _DictionaryCodingKey.self)
for key in container.allKeys {
switch key.stringValue {
case Quantization.CodingKeys.groupSize.rawValue: continue
case Quantization.CodingKeys.bits.rawValue: continue
case Quantization.CodingKeys._mode.rawValue: continue
// additional keys that are not layer instructions, see
// mlx-community/bitnet-b1.58-2B-4T-4bit
case "quant_method", "linear_class", "quantization_mode": continue
default:
if let f = try? container.decode(Bool.self, forKey: key) {
if !f {
perLayerQuantization[key.stringValue] = .skip
}
} else {
perLayerQuantization[key.stringValue] = .quantize(
try container.decode(Quantization.self, forKey: key))
}
}
}
self.perLayerQuantization = PerLayerQuantization(
quantization: quantization, perLayerQuantization: perLayerQuantization)
}
func encode(to encoder: any Encoder) throws {
try quantization.encode(to: encoder)
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in perLayerQuantization.perLayerQuantization {
switch value {
case .skip:
try container.encode(false, forKey: .init(stringValue: key))
case .quantize(let q):
try container.encode(q, forKey: .init(stringValue: key))
}
}
}
}
var quantizationContainer: QuantizationContainer?
/// EOS token IDs from config.json. Can be a single Int or an array of Ints.
public var eosTokenIds: IntOrIntArray?
@available(*, deprecated, message: "Please use perLayerQuantization instead")
public var quantization: Quantization? {
quantizationContainer?.quantization
}
public var perLayerQuantization: PerLayerQuantization? {
quantizationContainer?.perLayerQuantization
}
enum CodingKeys: String, CodingKey {
case modelType = "model_type"
case quantizationContainer = "quantization"
case eosTokenIds = "eos_token_id"
}
}