v2: use MPSMatrixMultiplication for matmul in EmbeddingGemma
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import Metal
|
||||
import MetalPerformanceShaders
|
||||
import Accelerate
|
||||
|
||||
/// EmbeddingGemma configuration
|
||||
@@ -80,15 +81,15 @@ public final class EmbeddingGemmaModel: @unchecked Sendable {
|
||||
try loadBuffer("\(p).post_attention_layernorm.weight"),
|
||||
try loadBuffer("\(p).post_feedforward_layernorm.weight"),
|
||||
])
|
||||
qProjs.append(try loadBuffer("\(p).self_attn.q_proj.weight"))
|
||||
kProjs.append(try loadBuffer("\(p).self_attn.k_proj.weight"))
|
||||
vProjs.append(try loadBuffer("\(p).self_attn.v_proj.weight"))
|
||||
oProjs.append(try loadBuffer("\(p).self_attn.o_proj.weight"))
|
||||
qProjs.append(try loadAndTranspose("\(p).self_attn.q_proj.weight", rows: config.hiddenSize, cols: config.hiddenSize))
|
||||
kProjs.append(try loadAndTranspose("\(p).self_attn.k_proj.weight", rows: config.numKeyValueHeads * config.headDim, cols: config.hiddenSize))
|
||||
vProjs.append(try loadAndTranspose("\(p).self_attn.v_proj.weight", rows: config.numKeyValueHeads * config.headDim, cols: config.hiddenSize))
|
||||
oProjs.append(try loadAndTranspose("\(p).self_attn.o_proj.weight", rows: config.numAttentionHeads * config.headDim, cols: config.hiddenSize))
|
||||
qNorms.append(try loadBuffer("\(p).self_attn.q_norm.weight"))
|
||||
kNorms.append(try loadBuffer("\(p).self_attn.k_norm.weight"))
|
||||
gateProjs.append(try loadBuffer("\(p).mlp.gate_proj.weight"))
|
||||
upProjs.append(try loadBuffer("\(p).mlp.up_proj.weight"))
|
||||
downProjs.append(try loadBuffer("\(p).mlp.down_proj.weight"))
|
||||
gateProjs.append(try loadAndTranspose("\(p).mlp.gate_proj.weight", rows: config.intermediateSize, cols: config.hiddenSize))
|
||||
upProjs.append(try loadAndTranspose("\(p).mlp.up_proj.weight", rows: config.intermediateSize, cols: config.hiddenSize))
|
||||
downProjs.append(try loadAndTranspose("\(p).mlp.down_proj.weight", rows: config.hiddenSize, cols: config.intermediateSize))
|
||||
}
|
||||
let fnData = try readTensor("norm.weight")
|
||||
finalNorm = engine.device.makeBuffer(bytes: fnData, length: fnData.count * 4)!
|
||||
@@ -161,6 +162,18 @@ public final class EmbeddingGemmaModel: @unchecked Sendable {
|
||||
return engine.device.makeBuffer(bytes: data, length: data.count * 4)!
|
||||
}
|
||||
|
||||
private func loadAndTranspose(_ name: String, rows: Int, cols: Int) throws -> MTLBuffer {
|
||||
// Load [rows, cols] and transpose to [cols, rows] for MPS matmul C = A × B
|
||||
let data = try readTensor(name)
|
||||
var transposed = [Float](repeating: 0, count: data.count)
|
||||
for r in 0..<rows {
|
||||
for c in 0..<cols {
|
||||
transposed[c * rows + r] = data[r * cols + c]
|
||||
}
|
||||
}
|
||||
return engine.device.makeBuffer(bytes: transposed, length: transposed.count * 4)!
|
||||
}
|
||||
|
||||
private func lookupEmbeddings(tokens: [Int], cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
|
||||
let seqLen = tokens.count, hs = config.hiddenSize
|
||||
let buf = engine.device.makeBuffer(length: seqLen * hs * 4)!
|
||||
@@ -196,22 +209,24 @@ public final class EmbeddingGemmaModel: @unchecked Sendable {
|
||||
}
|
||||
|
||||
private func matmulSeq(input: MTLBuffer, weight: MTLBuffer, output: MTLBuffer, m: Int, k: Int, n: Int, cmdBuf: MTLCommandBuffer) throws {
|
||||
let enc = cmdBuf.makeComputeCommandEncoder()!
|
||||
defer { enc.endEncoding() }
|
||||
let pso = try engine.pipeline(named: "matmul_f32")
|
||||
enc.setComputePipelineState(pso)
|
||||
for i in 0..<m {
|
||||
let inputOffset = i * k * 4
|
||||
let outputOffset = i * n * 4
|
||||
enc.setBuffer(input, offset: inputOffset, index: 0)
|
||||
enc.setBuffer(weight, offset: 0, index: 1)
|
||||
enc.setBuffer(output, offset: outputOffset, index: 2)
|
||||
var mm: UInt32 = 1, kk = UInt32(k), nn = UInt32(n)
|
||||
enc.setBytes(&mm, length: 4, index: 3)
|
||||
enc.setBytes(&kk, length: 4, index: 4)
|
||||
enc.setBytes(&nn, length: 4, index: 5)
|
||||
enc.dispatchThreads(MTLSize(width: n, height: 1, depth: 1), threadsPerThreadgroup: MTLSize(width: min(256, n), height: 1, depth: 1))
|
||||
}
|
||||
// Use MPS for optimized matrix multiplication on Apple Silicon
|
||||
// Weight is stored transposed [k, n] for C = A × B
|
||||
let descA = MPSMatrixDescriptor(rows: m, columns: k, rowBytes: k * 4, dataType: .float32)
|
||||
let descB = MPSMatrixDescriptor(rows: k, columns: n, rowBytes: n * 4, dataType: .float32)
|
||||
let descC = MPSMatrixDescriptor(rows: m, columns: n, rowBytes: n * 4, dataType: .float32)
|
||||
let matA = MPSMatrix(buffer: input, descriptor: descA)
|
||||
let matB = MPSMatrix(buffer: weight, descriptor: descB)
|
||||
let matC = MPSMatrix(buffer: output, descriptor: descC)
|
||||
|
||||
let matMul = MPSMatrixMultiplication(device: engine.device,
|
||||
transposeLeft: false,
|
||||
transposeRight: false,
|
||||
resultRows: m,
|
||||
resultColumns: n,
|
||||
interiorColumns: k,
|
||||
alpha: 1.0,
|
||||
beta: 0.0)
|
||||
matMul.encode(commandBuffer: cmdBuf, leftMatrix: matA, rightMatrix: matB, resultMatrix: matC)
|
||||
}
|
||||
|
||||
private func eltwiseAdd(a: MTLBuffer, b: MTLBuffer, output: MTLBuffer, count: Int, cmdBuf: MTLCommandBuffer) throws {
|
||||
|
||||
Reference in New Issue
Block a user