import Foundation import Metal import MetalPerformanceShaders import Accelerate /// EmbeddingGemma configuration public struct EmbeddingGemmaConfig: Codable { public let hiddenSize: Int public let numHiddenLayers: Int public let vocabSize: Int public let numAttentionHeads: Int public let numKeyValueHeads: Int public let headDim: Int public let intermediateSize: Int public let maxPositionEmbeddings: Int public let slidingWindow: Int public let rmsNormEps: Float public let ropeTheta: Float public let useBidirectionalAttention: Bool public let layerTypes: [String] enum CodingKeys: String, CodingKey { case hiddenSize = "hidden_size" case numHiddenLayers = "num_hidden_layers" case vocabSize = "vocab_size" case numAttentionHeads = "num_attention_heads" case numKeyValueHeads = "num_key_value_heads" case headDim = "head_dim" case intermediateSize = "intermediate_size" case maxPositionEmbeddings = "max_position_embeddings" case slidingWindow = "sliding_window" case rmsNormEps = "rms_norm_eps" case ropeTheta = "rope_theta" case useBidirectionalAttention = "use_bidirectional_attention" case layerTypes = "layer_types" } public static func load(from modelDir: String) throws -> Self { let url = URL(fileURLWithPath: modelDir).appendingPathComponent("config.json") let data = try Data(contentsOf: url) return try JSONDecoder().decode(Self.self, from: data) } } public final class EmbeddingGemmaModel: @unchecked Sendable { public let config: EmbeddingGemmaConfig public let engine: MarkBaseEngine public let tokenizer: Tokenizer public let reader: SafeTensorsReader public var embedTokens: MTLBuffer! public var finalNorm: MTLBuffer! public var layerNorms: [[MTLBuffer]] = [] public var qProjs: [MTLBuffer] = [] public var kProjs: [MTLBuffer] = [] public var vProjs: [MTLBuffer] = [] public var oProjs: [MTLBuffer] = [] public var qNorms: [MTLBuffer] = [] public var kNorms: [MTLBuffer] = [] public var gateProjs: [MTLBuffer] = [] public var upProjs: [MTLBuffer] = [] public var downProjs: [MTLBuffer] = [] public init(modelDir: String, engine: MarkBaseEngine) throws { self.engine = engine self.config = try EmbeddingGemmaConfig.load(from: modelDir) self.tokenizer = try TokenizerFactory.load(modelDir: modelDir) self.reader = try SafeTensorsReader(path: modelDir + "/model.safetensors") try loadWeights() print("✓ EmbeddingGemma loaded (\(config.numHiddenLayers) layers, hidden=\(config.hiddenSize))") } private func loadWeights() throws { let embedData = try readTensor("embed_tokens.weight") embedTokens = engine.device.makeBuffer(bytes: embedData, length: embedData.count * 4)! for i in 0.. [Float] { var tokens = tokenizer.encode(text: text) if tokens.count > maxLen { tokens = Array(tokens.prefix(maxLen)) } guard !tokens.isEmpty else { return [] } let seqLen = tokens.count, hs = config.hiddenSize // Test 1: Embedding lookup only print(" TEST: Embedding lookup...") let cmdBuf1 = engine.commandQueue.makeCommandBuffer()! let inputBuf = try lookupEmbeddings(tokens: tokens, cmdBuf: cmdBuf1) cmdBuf1.commit() cmdBuf1.waitUntilCompleted() print(" TEST: Embedding lookup OK") // Test 2: Single layer forward (layer 0 only) print(" TEST: Layer 0 forward...") let cmdBuf2 = engine.commandQueue.makeCommandBuffer()! var hidden = try forwardLayerDebug(hidden: inputBuf, layerIdx: 0, seqLen: seqLen, cmdBuf: cmdBuf2) cmdBuf2.commit() cmdBuf2.waitUntilCompleted() print(" TEST: Layer 0 OK") // Full forward pass print(" TEST: Full forward pass...") let cmdBuf = engine.commandQueue.makeCommandBuffer()! hidden = inputBuf for idx in 0..