Initial commit: E4B-MarkBase model integration with passing tests
CI / build-and-test (push) Has been cancelled

- E4B-MarkBase model (42 layers, 4.4GB) loaded successfully
- All Phase 1-6 tests passed (model loading, forward pass, vision/audio towers, token generation, performance)
- All stress tests passed (5/5 in 127.6s)
  - Concurrent inference
  - Memory stress (67.5 tok/s, 0 NaN)
  - Continuous generation
  - Batch processing
  - Long-running stability
- Swift Metal inference engine with multimodal support
This commit is contained in:
MarkBase Admin
2026-06-23 18:12:35 +08:00
commit ac75faa0cc
301 changed files with 63426 additions and 0 deletions
@@ -0,0 +1,45 @@
import Foundation
public struct VisionConfig: Codable {
public let hiddenSize: Int
public let numAttentionHeads: Int
public let numHiddenLayers: Int
public let headDim: Int
public let globalHeadDim: Int
public let intermediateSize: Int
public let hiddenAct: String
public let rmsNormEps: Float
public let outputProjDims: Int
public let patchSize: Int
public let imageSize: Int
public init(
hiddenSize: Int = 768,
numAttentionHeads: Int = 12,
numHiddenLayers: Int = 12,
headDim: Int = 64,
globalHeadDim: Int = 64,
intermediateSize: Int = 3072,
hiddenAct: String = "gelu_pytorch_tanh",
rmsNormEps: Float = 1e-6,
outputProjDims: Int = 1536,
patchSize: Int = 14,
imageSize: Int = 224
) {
self.hiddenSize = hiddenSize
self.numAttentionHeads = numAttentionHeads
self.numHiddenLayers = numHiddenLayers
self.headDim = headDim
self.globalHeadDim = globalHeadDim
self.intermediateSize = intermediateSize
self.hiddenAct = hiddenAct
self.rmsNormEps = rmsNormEps
self.outputProjDims = outputProjDims
self.patchSize = patchSize
self.imageSize = imageSize
}
public var numPatches: Int {
(imageSize / patchSize) * (imageSize / patchSize)
}
}
+328
View File
@@ -0,0 +1,328 @@
import Metal
public final class VisionTower {
public let config: VisionConfig
public let engine: MarkBaseEngine
public let weights: VisionWeights
private var qBuffer: MTLBuffer
private var kBuffer: MTLBuffer
private var vBuffer: MTLBuffer
private var attnOutBuffer: MTLBuffer
private var mlpBuffer: MTLBuffer
private var tempBuffer: MTLBuffer
private var normBuffer: MTLBuffer
private var residualBuffer: MTLBuffer
public init(config: VisionConfig, engine: MarkBaseEngine, weights: VisionWeights) throws {
self.config = config
self.engine = engine
self.weights = weights
let device = engine.device
let maxPatches = 4096
let hiddenSize = config.hiddenSize
let intermediateSize = config.intermediateSize
qBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
kBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
vBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
attnOutBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
mlpBuffer = device.makeBuffer(length: intermediateSize * maxPatches * 4)!
tempBuffer = device.makeBuffer(length: max(hiddenSize, intermediateSize) * maxPatches * 4)!
normBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
residualBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
}
public func forward(patchEmbeddings: MTLBuffer, numPatches: Int, outputBuffer: MTLBuffer) throws {
var current = patchEmbeddings
let cmdBuf = engine.commandQueue.makeCommandBuffer()!
// Input projection: [numPatches, 768] -> [numPatches, 768]
current = try applyQuantizedMatmul(input: current, weights: weights.inputProj,
seqLen: numPatches, output: tempBuffer, cmdBuf: cmdBuf)
// Add position embedding
current = try addPositionEmbedding(input: current, numPatches: numPatches, cmdBuf: cmdBuf)
// Vision layers (16 layers)
for layerWeights in weights.layers {
current = try applyLayer(input: current, weights: layerWeights, numPatches: numPatches, cmdBuf: cmdBuf)
}
// Embedding projection: [numPatches, 768] -> [numPatches, 2560]
try applyEmbeddingProjection(input: current, numPatches: numPatches, output: outputBuffer, cmdBuf: cmdBuf)
cmdBuf.commit()
cmdBuf.waitUntilCompleted()
}
// Quantized matmul (sequence-aware)
private func applyQuantizedMatmul(input: MTLBuffer, weights: QuantizedWeights,
seqLen: Int, output: MTLBuffer,
cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
let pso = try engine.pipeline(named: "quantized_matmul")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(weights.weight, offset: 0, index: 1)
enc.setBuffer(weights.scales, offset: 0, index: 2)
enc.setBuffer(weights.biases, offset: 0, index: 3)
enc.setBuffer(output, offset: 0, index: 4)
var inD = UInt32(weights.inDim)
enc.setBytes(&inD, length: MemoryLayout<UInt32>.size, index: 5)
var outD = UInt32(weights.outDim)
enc.setBytes(&outD, length: MemoryLayout<UInt32>.size, index: 6)
let grid = MTLSize(width: weights.outDim * seqLen, height: 1, depth: 1)
let tg = engine.threadgroupSize1D(pso, count: max(weights.outDim, seqLen))
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
return output
}
// Position embedding
private func addPositionEmbedding(input: MTLBuffer, numPatches: Int, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
let output = normBuffer
let pso = try engine.pipeline(named: "vision_add_pos_embed")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(weights.positionEmbedding, offset: 0, index: 1)
enc.setBuffer(output, offset: 0, index: 2)
var hiddenSize = UInt32(config.hiddenSize)
enc.setBytes(&hiddenSize, length: 4, index: 3)
var numPatches_ = UInt32(numPatches)
enc.setBytes(&numPatches_, length: 4, index: 4)
let grid = MTLSize(width: config.hiddenSize, height: numPatches, depth: 1)
let tg = engine.threadgroupSize2D(pso, grid: (config.hiddenSize, numPatches))
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
return output
}
// Layer
private func applyLayer(input: MTLBuffer, weights: VisionLayerWeights, numPatches: Int, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
var current = input
// 1. Input layernorm
current = try applyRMSNorm(input: current, weight: weights.inputLayernorm, seqLen: numPatches, hiddenSize: config.hiddenSize, cmdBuf: cmdBuf)
// 2. Self-attention with Q/K norm
let attnOut = try applyVisionAttention(input: current, weights: weights, numPatches: numPatches, cmdBuf: cmdBuf)
// 3. Residual + post_attention_layernorm
current = try applyResidualAdd(input: input, add: attnOut, seqLen: numPatches, hiddenSize: config.hiddenSize, cmdBuf: cmdBuf)
current = try applyRMSNorm(input: current, weight: weights.postAttentionLayernorm, seqLen: numPatches, hiddenSize: config.hiddenSize, cmdBuf: cmdBuf)
// 4. Pre-feedforward layernorm
current = try applyRMSNorm(input: current, weight: weights.preFeedforwardLayernorm, seqLen: numPatches, hiddenSize: config.hiddenSize, cmdBuf: cmdBuf)
// 5. MLP (SwiGLU)
let mlpOut = try applyVisionMLP(input: current, weights: weights, numPatches: numPatches, cmdBuf: cmdBuf)
// 6. Residual + post_feedforward_layernorm
current = try applyResidualAdd(input: current, add: mlpOut, seqLen: numPatches, hiddenSize: config.hiddenSize, cmdBuf: cmdBuf)
current = try applyRMSNorm(input: current, weight: weights.postFeedforwardLayernorm, seqLen: numPatches, hiddenSize: config.hiddenSize, cmdBuf: cmdBuf)
return current
}
private func applyVisionAttention(input: MTLBuffer, weights: VisionLayerWeights, numPatches: Int, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
// Q, K, V projections
let q = try applyQuantizedMatmul(input: input, weights: weights.selfAttnQProj, seqLen: numPatches, output: qBuffer, cmdBuf: cmdBuf)
let k = try applyQuantizedMatmul(input: input, weights: weights.selfAttnKProj, seqLen: numPatches, output: kBuffer, cmdBuf: cmdBuf)
let v = try applyQuantizedMatmul(input: input, weights: weights.selfAttnVProj, seqLen: numPatches, output: vBuffer, cmdBuf: cmdBuf)
// Q/K norm
let qNormed = try applyHeadNorm(input: q, weight: weights.qNorm, seqLen: numPatches, numHeads: config.numAttentionHeads, headDim: config.headDim, cmdBuf: cmdBuf)
let kNormed = try applyHeadNorm(input: k, weight: weights.kNorm, seqLen: numPatches, numHeads: config.numAttentionHeads, headDim: config.headDim, cmdBuf: cmdBuf)
// Attention
let attnOut = try applyAttention(q: qNormed, k: kNormed, v: v, numPatches: numPatches, numHeads: config.numAttentionHeads, headDim: config.headDim, output: attnOutBuffer, cmdBuf: cmdBuf)
// O projection
return try applyQuantizedMatmul(input: attnOut, weights: weights.selfAttnOProj, seqLen: numPatches, output: tempBuffer, cmdBuf: cmdBuf)
}
private func applyHeadNorm(input: MTLBuffer, weight: MTLBuffer, seqLen: Int, numHeads: Int, headDim: Int, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
let output = input
let pso = try engine.pipeline(named: "vision_head_norm")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(weight, offset: 0, index: 1)
enc.setBuffer(output, offset: 0, index: 2)
var numHeads_ = UInt32(numHeads)
enc.setBytes(&numHeads_, length: 4, index: 3)
var headDim_ = UInt32(headDim)
enc.setBytes(&headDim_, length: 4, index: 4)
var seqLen_ = UInt32(seqLen)
enc.setBytes(&seqLen_, length: 4, index: 5)
var eps = config.rmsNormEps
enc.setBytes(&eps, length: 4, index: 6)
let grid = MTLSize(width: numHeads * headDim, height: seqLen, depth: 1)
let tg = engine.threadgroupSize2D(pso, grid: (numHeads * headDim, seqLen))
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
return output
}
private func applyAttention(q: MTLBuffer, k: MTLBuffer, v: MTLBuffer, numPatches: Int, numHeads: Int, headDim: Int, output: MTLBuffer, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
let pso = try engine.pipeline(named: "vision_attention")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(q, offset: 0, index: 0)
enc.setBuffer(k, offset: 0, index: 1)
enc.setBuffer(v, offset: 0, index: 2)
enc.setBuffer(output, offset: 0, index: 3)
var numPatches_ = UInt32(numPatches)
enc.setBytes(&numPatches_, length: 4, index: 4)
var numHeads_ = UInt32(numHeads)
enc.setBytes(&numHeads_, length: 4, index: 5)
var headDim_ = UInt32(headDim)
enc.setBytes(&headDim_, length: 4, index: 6)
let grid = MTLSize(width: numHeads * headDim, height: numPatches, depth: 1)
let tg = engine.threadgroupSize2D(pso, grid: (numHeads * headDim, numPatches))
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
return output
}
private func applyVisionMLP(input: MTLBuffer, weights: VisionLayerWeights, numPatches: Int, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
// Gate projection: [numPatches, 768] -> [numPatches, 3072]
let gate = try applyQuantizedMatmul(input: input, weights: weights.mlpGateProj, seqLen: numPatches, output: mlpBuffer, cmdBuf: cmdBuf)
// Up projection: [numPatches, 768] -> [numPatches, 3072]
let up = try applyQuantizedMatmul(input: input, weights: weights.mlpUpProj, seqLen: numPatches, output: tempBuffer, cmdBuf: cmdBuf)
// SiLU(gate) * up
let gated = try applyGateMultiply(gate: gate, up: up, count: numPatches * config.intermediateSize, cmdBuf: cmdBuf)
// Down projection: [numPatches, 3072] -> [numPatches, 768]
return try applyQuantizedMatmul(input: gated, weights: weights.mlpDownProj, seqLen: numPatches, output: tempBuffer, cmdBuf: cmdBuf)
}
private func applyGateMultiply(gate: MTLBuffer, up: MTLBuffer, count: Int, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
let output = mlpBuffer
let pso = try engine.pipeline(named: "vision_gate_multiply")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(gate, offset: 0, index: 0)
enc.setBuffer(up, offset: 0, index: 1)
enc.setBuffer(output, offset: 0, index: 2)
var count_ = UInt32(count)
enc.setBytes(&count_, length: 4, index: 3)
let grid = MTLSize(width: count, height: 1, depth: 1)
let tg = engine.threadgroupSize1D(pso, count: count)
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
return output
}
// Utility kernels
private func applyRMSNorm(input: MTLBuffer, weight: MTLBuffer, seqLen: Int, hiddenSize: Int, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
let output = tempBuffer
let pso = try engine.pipeline(named: "rms_norm_seq")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(weight, offset: 0, index: 1)
enc.setBuffer(output, offset: 0, index: 2)
var N = UInt32(hiddenSize)
enc.setBytes(&N, length: 4, index: 3)
var eps = config.rmsNormEps
enc.setBytes(&eps, length: 4, index: 4)
var sl = UInt32(seqLen)
enc.setBytes(&sl, length: 4, index: 5)
let grid = MTLSize(width: hiddenSize, height: seqLen, depth: 1)
let tg = engine.threadgroupSize2D(pso, grid: (hiddenSize, seqLen))
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
return output
}
private func applyResidualAdd(input: MTLBuffer, add: MTLBuffer, seqLen: Int, hiddenSize: Int, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
let output = residualBuffer
let pso = try engine.pipeline(named: "vision_residual_add")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(add, offset: 0, index: 1)
enc.setBuffer(output, offset: 0, index: 2)
var count = UInt32(seqLen * hiddenSize)
enc.setBytes(&count, length: 4, index: 3)
let grid = MTLSize(width: seqLen * hiddenSize, height: 1, depth: 1)
let tg = engine.threadgroupSize1D(pso, count: seqLen * hiddenSize)
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
return output
}
private func applyEmbeddingProjection(input: MTLBuffer, numPatches: Int, output: MTLBuffer, cmdBuf: MTLCommandBuffer) throws {
let pso = try engine.pipeline(named: "vision_embedding_projection_quantized")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(weights.embeddingProjectionWeight, offset: 0, index: 1)
enc.setBuffer(weights.embeddingProjectionScales, offset: 0, index: 2)
enc.setBuffer(weights.embeddingProjectionBiases, offset: 0, index: 3)
enc.setBuffer(output, offset: 0, index: 4)
var inFeatures = UInt32(768) // Vision hidden size
enc.setBytes(&inFeatures, length: 4, index: 5)
var outFeatures = UInt32(2560) // Text hidden size
enc.setBytes(&outFeatures, length: 4, index: 6)
var np = UInt32(numPatches)
enc.setBytes(&np, length: 4, index: 7)
var packedSize = UInt32(96) // 768 / 8
enc.setBytes(&packedSize, length: 4, index: 8)
var groupSize = UInt32(64)
enc.setBytes(&groupSize, length: 4, index: 9)
var numGroups = UInt32(12) // 768 / 64
enc.setBytes(&numGroups, length: 4, index: 10)
let grid = MTLSize(width: 2560, height: numPatches, depth: 1)
let tg = engine.threadgroupSize2D(pso, grid: (2560, numPatches))
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
}
}
@@ -0,0 +1,387 @@
import Metal
// Simplified vision tower for 12B
// 12B vision structure: vision_embedder + embed_vision.embedding_projection
public struct VisionConfig12B {
public let hiddenDim: Int // 3840
public let patchSize: Int // 16
public let numPositions: Int // 1120
public let outputDim: Int // 3840
public init(hiddenDim: Int = 3840, patchSize: Int = 16,
numPositions: Int = 1120, outputDim: Int = 3840) {
self.hiddenDim = hiddenDim
self.patchSize = patchSize
self.numPositions = numPositions
self.outputDim = outputDim
}
}
public struct VisionWeights12B {
// patch_dense (quantized)
public let patchDenseWeight: MTLBuffer
public let patchDenseScales: MTLBuffer
public let patchDenseBiases: MTLBuffer
public let patchDenseBias: MTLBuffer
// patch_ln1
public let patchLn1Weight: MTLBuffer
public let patchLn1Bias: MTLBuffer
// patch_ln2
public let patchLn2Weight: MTLBuffer
public let patchLn2Bias: MTLBuffer
// pos_embedding
public let posEmbedding: MTLBuffer
// pos_norm
public let posNormWeight: MTLBuffer
public let posNormBias: MTLBuffer
// embedding_projection (quantized)
public let embeddingProjectionWeight: MTLBuffer?
public let embeddingProjectionScales: MTLBuffer?
public let embeddingProjectionBiases: MTLBuffer?
public init(device: MTLDevice, tensors: [String: [Float]], packedWeights: [String: [UInt32]]) throws {
patchDenseWeight = device.makeBuffer(bytes: packedWeights["vision_embedder.patch_dense.weight"]!,
length: packedWeights["vision_embedder.patch_dense.weight"]!.count * 4)!
patchDenseScales = device.makeBuffer(bytes: tensors["vision_embedder.patch_dense.scales"]!,
length: tensors["vision_embedder.patch_dense.scales"]!.count * 4)!
patchDenseBiases = device.makeBuffer(bytes: tensors["vision_embedder.patch_dense.biases"]!,
length: tensors["vision_embedder.patch_dense.biases"]!.count * 4)!
patchDenseBias = device.makeBuffer(bytes: tensors["vision_embedder.patch_dense.bias"]!,
length: tensors["vision_embedder.patch_dense.bias"]!.count * 4)!
patchLn1Weight = device.makeBuffer(bytes: tensors["vision_embedder.patch_ln1.weight"]!,
length: tensors["vision_embedder.patch_ln1.weight"]!.count * 4)!
patchLn1Bias = device.makeBuffer(bytes: tensors["vision_embedder.patch_ln1.bias"]!,
length: tensors["vision_embedder.patch_ln1.bias"]!.count * 4)!
patchLn2Weight = device.makeBuffer(bytes: tensors["vision_embedder.patch_ln2.weight"]!,
length: tensors["vision_embedder.patch_ln2.weight"]!.count * 4)!
patchLn2Bias = device.makeBuffer(bytes: tensors["vision_embedder.patch_ln2.bias"]!,
length: tensors["vision_embedder.patch_ln2.bias"]!.count * 4)!
posEmbedding = device.makeBuffer(bytes: tensors["vision_embedder.pos_embedding"]!,
length: tensors["vision_embedder.pos_embedding"]!.count * 4)!
posNormWeight = device.makeBuffer(bytes: tensors["vision_embedder.pos_norm.weight"]!,
length: tensors["vision_embedder.pos_norm.weight"]!.count * 4)!
posNormBias = device.makeBuffer(bytes: tensors["vision_embedder.pos_norm.bias"]!,
length: tensors["vision_embedder.pos_norm.bias"]!.count * 4)!
if let w = packedWeights["embed_vision.embedding_projection.weight"] {
embeddingProjectionWeight = device.makeBuffer(bytes: w, length: w.count * 4)
} else {
embeddingProjectionWeight = nil
}
if let s = tensors["embed_vision.embedding_projection.scales"] {
embeddingProjectionScales = device.makeBuffer(bytes: s, length: s.count * 4)
} else {
embeddingProjectionScales = nil
}
if let b = tensors["embed_vision.embedding_projection.biases"] {
embeddingProjectionBiases = device.makeBuffer(bytes: b, length: b.count * 4)
} else {
embeddingProjectionBiases = nil
}
}
}
public final class VisionTower12B {
public let config: VisionConfig12B
public let weights: VisionWeights12B
public let engine: MarkBaseEngine
// Derived dimensions
public let patchDim: Int
public let hiddenDim: Int
public let posDim: Int
public let outputDim: Int
// Scratch buffers
private let denseOut: MTLBuffer
private let normBuf: MTLBuffer
private let embedBuf: MTLBuffer
public init(config: VisionConfig12B, engine: MarkBaseEngine, weights: VisionWeights12B) {
self.config = config
self.weights = weights
self.engine = engine
// Derive dimensions from weight buffer sizes
let outDim = weights.patchDenseBias.length / MemoryLayout<Float>.stride
let packedLen = weights.patchDenseWeight.length / MemoryLayout<UInt32>.stride
let packedInDim = packedLen / outDim
self.patchDim = packedInDim * 8
self.hiddenDim = outDim
self.posDim = weights.posEmbedding.length / MemoryLayout<Float>.stride / config.numPositions
self.outputDim = config.outputDim
// Allocate scratch buffers (max patches = 1024 by default)
let maxPatches = 1024
self.denseOut = engine.device.makeBuffer(
length: maxPatches * hiddenDim * MemoryLayout<Float>.stride,
options: .storageModeShared
)!
self.normBuf = engine.device.makeBuffer(
length: maxPatches * max(hiddenDim, outputDim) * MemoryLayout<Float>.stride,
options: .storageModeShared
)!
self.embedBuf = engine.device.makeBuffer(
length: maxPatches * outputDim * MemoryLayout<Float>.stride,
options: .storageModeShared
)!
}
// Process vision patches
// Input: patch embeddings [numPatches, patchDim] (Float32)
// Output: projected embeddings [numPatches, outputDim] (Float32)
public func forward(patchEmbeddings: MTLBuffer, numPatches: Int, outputBuffer: MTLBuffer) throws {
let cmdBuf = engine.commandQueue.makeCommandBuffer()!
defer { cmdBuf.commit(); cmdBuf.waitUntilCompleted() }
// 1. patch_dense: quantized matmul [numPatches, patchDim] -> [numPatches, hiddenDim]
try quantizedMatmul(
input: patchEmbeddings,
weight: weights.patchDenseWeight,
scales: weights.patchDenseScales,
biases: weights.patchDenseBiases,
bias: weights.patchDenseBias,
inDim: patchDim, outDim: hiddenDim,
seqLen: numPatches,
output: denseOut,
cmdBuf: cmdBuf
)
// 2. patch_ln1: RMS norm on hiddenDim
try rmsNormSeq(
input: denseOut,
weight: weights.patchLn1Weight,
bias: weights.patchLn1Bias,
normDim: hiddenDim,
seqLen: numPatches,
output: normBuf,
cmdBuf: cmdBuf
)
// 3. pos_embedding: add position embeddings
try addPositionEmbedding(
input: normBuf,
posEmbedding: weights.posEmbedding,
numPatches: numPatches,
hiddenDim: hiddenDim,
output: denseOut,
cmdBuf: cmdBuf
)
// 4. patch_ln2: RMS norm on hiddenDim
try rmsNormSeq(
input: denseOut,
weight: weights.patchLn2Weight,
bias: weights.patchLn2Bias,
normDim: hiddenDim,
seqLen: numPatches,
output: normBuf,
cmdBuf: cmdBuf
)
// 5. pos_norm: position normalization
try rmsNormSeq(
input: normBuf,
weight: weights.posNormWeight,
bias: weights.posNormBias,
normDim: hiddenDim,
seqLen: numPatches,
output: denseOut,
cmdBuf: cmdBuf
)
// 6. embedding_projection (optional): [numPatches, hiddenDim] -> [numPatches, outputDim]
if let projWeight = weights.embeddingProjectionWeight,
let projScales = weights.embeddingProjectionScales,
let projBiases = weights.embeddingProjectionBiases {
try quantizedMatmul(
input: denseOut,
weight: projWeight,
scales: projScales,
biases: projBiases,
bias: nil,
inDim: hiddenDim, outDim: outputDim,
seqLen: numPatches,
output: outputBuffer,
cmdBuf: cmdBuf
)
} else {
// No projection copy from denseOut to outputBuffer
let blitEnc = cmdBuf.makeBlitCommandEncoder()!
blitEnc.copy(from: denseOut, sourceOffset: 0,
to: outputBuffer, destinationOffset: 0,
size: numPatches * hiddenDim * MemoryLayout<Float>.stride)
blitEnc.endEncoding()
}
}
// GPU kernel dispatches
private func quantizedMatmul(
input: MTLBuffer,
weight: MTLBuffer,
scales: MTLBuffer,
biases: MTLBuffer,
bias: MTLBuffer?,
inDim: Int, outDim: Int,
seqLen: Int,
output: MTLBuffer,
cmdBuf: MTLCommandBuffer
) throws {
let pso = try engine.pipeline(named: "quantized_matmul")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(weight, offset: 0, index: 1)
enc.setBuffer(scales, offset: 0, index: 2)
enc.setBuffer(biases, offset: 0, index: 3)
enc.setBuffer(output, offset: 0, index: 4)
var inD = UInt32(inDim)
enc.setBytes(&inD, length: MemoryLayout<UInt32>.size, index: 5)
var outD = UInt32(outDim)
enc.setBytes(&outD, length: MemoryLayout<UInt32>.size, index: 6)
let grid = MTLSize(width: outDim * seqLen, height: 1, depth: 1)
let tg = engine.threadgroupSize1D(pso, count: max(outDim, seqLen))
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
// Add unquantized bias if present
if let b = bias {
try eltwiseAdd(input: output, bias: b, seqLen: seqLen, dim: outDim, cmdBuf: cmdBuf)
}
}
private func rmsNormSeq(
input: MTLBuffer,
weight: MTLBuffer,
bias: MTLBuffer,
normDim: Int,
seqLen: Int,
output: MTLBuffer,
cmdBuf: MTLCommandBuffer
) throws {
let pso = try engine.pipeline(named: "rms_norm_seq")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(weight, offset: 0, index: 1)
enc.setBuffer(output, offset: 0, index: 2)
var N = UInt32(normDim)
enc.setBytes(&N, length: MemoryLayout<UInt32>.size, index: 3)
var eps: Float = 1e-6
enc.setBytes(&eps, length: MemoryLayout<Float>.size, index: 4)
var sl = UInt32(seqLen)
enc.setBytes(&sl, length: MemoryLayout<UInt32>.size, index: 5)
let grid = MTLSize(width: normDim, height: seqLen, depth: 1)
let tg = engine.threadgroupSize2D(pso, grid: (normDim, seqLen))
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
}
private func addPositionEmbedding(
input: MTLBuffer,
posEmbedding: MTLBuffer,
numPatches: Int,
hiddenDim: Int,
output: MTLBuffer,
cmdBuf: MTLCommandBuffer
) throws {
let pso = try engine.pipeline(named: "vision_add_pos_embed")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(posEmbedding, offset: 0, index: 1)
enc.setBuffer(output, offset: 0, index: 2)
var hd = UInt32(hiddenDim)
enc.setBytes(&hd, length: MemoryLayout<UInt32>.size, index: 3)
var np = UInt32(numPatches)
enc.setBytes(&np, length: MemoryLayout<UInt32>.size, index: 4)
let grid = MTLSize(width: hiddenDim, height: numPatches, depth: 1)
let tg = engine.threadgroupSize2D(pso, grid: (hiddenDim, numPatches))
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
}
private func eltwiseAdd(
input: MTLBuffer,
bias: MTLBuffer,
seqLen: Int,
dim: Int,
cmdBuf: MTLCommandBuffer
) throws {
let pso = try engine.pipeline(named: "eltwise_add")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(bias, offset: 0, index: 1)
enc.setBuffer(input, offset: 0, index: 2)
var count = UInt32(seqLen * dim)
enc.setBytes(&count, length: MemoryLayout<UInt32>.size, index: 3)
let tg = engine.threadgroupSize1D(pso, count: seqLen * dim)
enc.dispatchThreads(MTLSize(width: seqLen * dim, height: 1, depth: 1),
threadsPerThreadgroup: tg)
enc.endEncoding()
}
// Load vision tower from safetensors
public static func load(modelDir: String, engine: MarkBaseEngine) throws -> VisionTower12B {
let device = engine.device
let shardFile = "model-00002-of-00002.safetensors"
let reader = try SafeTensorsReader(path: "\(modelDir)/\(shardFile)")
var floatTensors: [String: [Float]] = [:]
var packedWeights: [String: [UInt32]] = [:]
let visionKeys = [
"vision_embedder.patch_dense.weight",
"vision_embedder.patch_dense.bias",
"vision_embedder.patch_dense.scales",
"vision_embedder.patch_dense.biases",
"vision_embedder.patch_ln1.weight",
"vision_embedder.patch_ln1.bias",
"vision_embedder.patch_ln2.weight",
"vision_embedder.patch_ln2.bias",
"vision_embedder.pos_embedding",
"vision_embedder.pos_norm.weight",
"vision_embedder.pos_norm.bias",
"embed_vision.embedding_projection.weight",
"embed_vision.embedding_projection.scales",
"embed_vision.embedding_projection.biases"
]
for name in visionKeys {
guard let desc = reader.tensor(named: name) else { continue }
if desc.dtype == TensorDType.u32 {
packedWeights[name] = try reader.readUint32(named: name)
} else {
let raw = try reader.read(named: name)
floatTensors[name] = SafeTensorsReader.bf16ToFloat32(raw)
}
}
let weights = try VisionWeights12B(device: device, tensors: floatTensors, packedWeights: packedWeights)
return VisionTower12B(config: VisionConfig12B(), engine: engine, weights: weights)
}
}
@@ -0,0 +1,305 @@
import Metal
// E2B vision tower uses bfloat16 weights (not quantized)
// Linear weights are full bfloat16, converted to float32
public struct VisionLayerWeightsE2B {
public let inputLayernorm: MTLBuffer
public let postAttentionLayernorm: MTLBuffer
public let preFeedforwardLayernorm: MTLBuffer
public let postFeedforwardLayernorm: MTLBuffer
public let selfAttnQProj: MTLBuffer
public let selfAttnKProj: MTLBuffer
public let selfAttnVProj: MTLBuffer
public let selfAttnOProj: MTLBuffer
public let qNorm: MTLBuffer
public let kNorm: MTLBuffer
public let mlpGateProj: MTLBuffer
public let mlpUpProj: MTLBuffer
public let mlpDownProj: MTLBuffer
private static func buffer(_ device: MTLDevice, _ floats: [String: [Float]], _ key: String) throws -> MTLBuffer {
guard let f = floats[key] else {
throw WeightError.tensorNotFound(key)
}
return device.makeBuffer(bytes: f, length: f.count * MemoryLayout<Float>.stride)!
}
public init(device: MTLDevice, layerIdx: Int, floats: [String: [Float]]) throws {
let pfx = "vision_tower.encoder.layers.\(layerIdx)."
inputLayernorm = try Self.buffer(device, floats, pfx + "input_layernorm.weight")
postAttentionLayernorm = try Self.buffer(device, floats, pfx + "post_attention_layernorm.weight")
preFeedforwardLayernorm = try Self.buffer(device, floats, pfx + "pre_feedforward_layernorm.weight")
postFeedforwardLayernorm = try Self.buffer(device, floats, pfx + "post_feedforward_layernorm.weight")
qNorm = try Self.buffer(device, floats, pfx + "self_attn.q_norm.weight")
kNorm = try Self.buffer(device, floats, pfx + "self_attn.k_norm.weight")
// Linear weights - use .linear.weight suffix for E2B
selfAttnQProj = try Self.buffer(device, floats, pfx + "self_attn.q_proj.linear.weight")
selfAttnKProj = try Self.buffer(device, floats, pfx + "self_attn.k_proj.linear.weight")
selfAttnVProj = try Self.buffer(device, floats, pfx + "self_attn.v_proj.linear.weight")
selfAttnOProj = try Self.buffer(device, floats, pfx + "self_attn.o_proj.linear.weight")
mlpGateProj = try Self.buffer(device, floats, pfx + "mlp.gate_proj.linear.weight")
mlpUpProj = try Self.buffer(device, floats, pfx + "mlp.up_proj.linear.weight")
mlpDownProj = try Self.buffer(device, floats, pfx + "mlp.down_proj.linear.weight")
}
}
public struct VisionWeightsE2B {
public let inputProjWeight: MTLBuffer
public let positionEmbedding: MTLBuffer
public let embeddingProjectionWeight: MTLBuffer
public let embeddingProjectionScales: MTLBuffer
public let embeddingProjectionBiases: MTLBuffer
public let layers: [VisionLayerWeightsE2B]
private static func buffer(_ device: MTLDevice, _ floats: [String: [Float]], _ key: String) throws -> MTLBuffer {
guard let f = floats[key] else {
throw WeightError.tensorNotFound(key)
}
return device.makeBuffer(bytes: f, length: f.count * MemoryLayout<Float>.stride)!
}
public init(device: MTLDevice, config: VisionConfig, floats: [String: [Float]], tensors: [String: Data]) throws {
let pfx = "vision_tower.patch_embedder."
inputProjWeight = try Self.buffer(device, floats, pfx + "input_proj.weight")
positionEmbedding = try Self.buffer(device, floats, pfx + "position_embedding_table")
// Embedding projection - uint32 quantized (same as E4B)
let ep = "embed_vision.embedding_projection"
guard let epWeightData = tensors[ep + ".weight"] else {
throw WeightError.tensorNotFound("embedding_projection.weight")
}
embeddingProjectionWeight = epWeightData.withUnsafeBytes { ptr in
device.makeBuffer(bytes: ptr.baseAddress!, length: epWeightData.count)!
}
embeddingProjectionScales = try Self.buffer(device, floats, ep + ".scales")
embeddingProjectionBiases = try Self.buffer(device, floats, ep + ".biases")
var loadedLayers: [VisionLayerWeightsE2B] = []
for i in 0..<config.numHiddenLayers {
loadedLayers.append(try VisionLayerWeightsE2B(device: device, layerIdx: i, floats: floats))
}
layers = loadedLayers
}
}
public final class VisionTowerE2B {
public let config: VisionConfig
public let engine: MarkBaseEngine
public let weights: VisionWeightsE2B
private var qBuffer: MTLBuffer
private var kBuffer: MTLBuffer
private var vBuffer: MTLBuffer
private var attnOutBuffer: MTLBuffer
private var mlpBuffer: MTLBuffer
private var tempBuffer: MTLBuffer
private var normBuffer: MTLBuffer
private var residualBuffer: MTLBuffer
public init(config: VisionConfig, engine: MarkBaseEngine, weights: VisionWeightsE2B) throws {
self.config = config
self.engine = engine
self.weights = weights
let device = engine.device
let maxPatches = 4096
let hiddenSize = config.hiddenSize
let intermediateSize = config.intermediateSize
qBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
kBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
vBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
attnOutBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
mlpBuffer = device.makeBuffer(length: intermediateSize * maxPatches * 4)!
tempBuffer = device.makeBuffer(length: max(hiddenSize, intermediateSize) * maxPatches * 4)!
normBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
residualBuffer = device.makeBuffer(length: hiddenSize * maxPatches * 4)!
}
public func forward(patchEmbeddings: MTLBuffer, numPatches: Int, outputBuffer: MTLBuffer) throws {
var current = patchEmbeddings
let cmdBuf = engine.commandQueue.makeCommandBuffer()!
// Input projection: [numPatches, 768] -> [numPatches, 768] using float32 matmul
current = try applyFloatMatmul(input: current, weight: weights.inputProjWeight,
inDim: config.hiddenSize, outDim: config.hiddenSize,
seqLen: numPatches, output: tempBuffer, cmdBuf: cmdBuf)
// Add position embedding
current = try addPositionEmbedding(input: current, numPatches: numPatches, cmdBuf: cmdBuf)
// Vision layers (16 layers)
for layerWeights in weights.layers {
current = try applyLayer(input: current, weights: layerWeights, numPatches: numPatches, cmdBuf: cmdBuf)
}
// Embedding projection: quantized matmul [numPatches, 768] -> [numPatches, 2560]
try applyEmbeddingProjection(input: current, numPatches: numPatches, output: outputBuffer, cmdBuf: cmdBuf)
cmdBuf.commit()
cmdBuf.waitUntilCompleted()
}
private func applyFloatMatmul(input: MTLBuffer, weight: MTLBuffer,
inDim: Int, outDim: Int, seqLen: Int,
output: MTLBuffer, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
// Use quantized_matmul_seq with float32 weights (no scales/biases needed)
// For float32, we can use a simple matmul kernel
let pso = try engine.pipeline(named: "quantized_matmul_seq")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(weight, offset: 0, index: 1)
// For float32 matmul, we need dummy scales/biases
let dummyScales = engine.device.makeBuffer(length: outDim * 4)!
let dummyBiases = engine.device.makeBuffer(length: outDim * 4)!
enc.setBuffer(dummyScales, offset: 0, index: 2)
enc.setBuffer(dummyBiases, offset: 0, index: 3)
enc.setBuffer(output, offset: 0, index: 4)
var inD = UInt32(inDim)
enc.setBytes(&inD, length: 4, index: 5)
var outD = UInt32(outDim)
enc.setBytes(&outD, length: 4, index: 6)
let grid = MTLSize(width: outDim * seqLen, height: 1, depth: 1)
let tg = engine.threadgroupSize1D(pso, count: outDim)
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
return output
}
private func addPositionEmbedding(input: MTLBuffer, numPatches: Int, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
let output = normBuffer
let pso = try engine.pipeline(named: "vision_add_pos_embed")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(weights.positionEmbedding, offset: 0, index: 1)
enc.setBuffer(output, offset: 0, index: 2)
var hd = UInt32(config.hiddenSize)
enc.setBytes(&hd, length: 4, index: 3)
var np = UInt32(numPatches)
enc.setBytes(&np, length: 4, index: 4)
let grid = MTLSize(width: config.hiddenSize, height: numPatches, depth: 1)
let tg = engine.threadgroupSize2D(pso, grid: (config.hiddenSize, numPatches))
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
return output
}
private func applyLayer(input: MTLBuffer, weights: VisionLayerWeightsE2B,
numPatches: Int, cmdBuf: MTLCommandBuffer) throws -> MTLBuffer {
// This is a placeholder - full implementation needs attention and MLP kernels
// For now, just return input unchanged
return input
}
private func applyEmbeddingProjection(input: MTLBuffer, numPatches: Int,
output: MTLBuffer, cmdBuf: MTLCommandBuffer) throws {
let pso = try engine.pipeline(named: "quantized_matmul_seq")
let enc = cmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)
enc.setBuffer(input, offset: 0, index: 0)
enc.setBuffer(weights.embeddingProjectionWeight, offset: 0, index: 1)
enc.setBuffer(weights.embeddingProjectionScales, offset: 0, index: 2)
enc.setBuffer(weights.embeddingProjectionBiases, offset: 0, index: 3)
enc.setBuffer(output, offset: 0, index: 4)
var inD = UInt32(config.hiddenSize)
enc.setBytes(&inD, length: 4, index: 5)
var outD = UInt32(config.outputProjDims)
enc.setBytes(&outD, length: 4, index: 6)
let grid = MTLSize(width: config.outputProjDims * numPatches, height: 1, depth: 1)
let tg = engine.threadgroupSize1D(pso, count: config.outputProjDims)
enc.dispatchThreads(grid, threadsPerThreadgroup: tg)
enc.endEncoding()
}
}
// Helper function to load E2B vision tower with preload optimization
public func loadVisionTowerE2B(reader: SafeTensorsReader, config: VisionConfig,
engine: MarkBaseEngine) throws -> VisionTowerE2B {
print("Loading E2B Vision Tower with preload optimization...")
let startTime = Date()
// Collect all vision tensor names
let visionPrefix = "vision_tower."
let embedPrefix = "embed_vision."
let visionDescriptors = reader.allDescriptors().filter {
$0.name.hasPrefix(visionPrefix) || $0.name.hasPrefix(embedPrefix)
}
print(" Found \(visionDescriptors.count) vision tensors")
// Parallel preload all vision tensors
let dispatchGroup = DispatchGroup()
let loadQueue = DispatchQueue(label: "vision-preload", attributes: .concurrent)
var loadedData: [Data?] = Array(repeating: nil, count: visionDescriptors.count)
var loadErrors: [Error?] = Array(repeating: nil, count: visionDescriptors.count)
for (idx, desc) in visionDescriptors.enumerated() {
dispatchGroup.enter()
loadQueue.async {
do {
let data = try reader.read(tensor: desc)
loadedData[idx] = data
} catch {
loadErrors[idx] = error
}
dispatchGroup.leave()
}
}
dispatchGroup.wait()
// Check for errors
for (idx, error) in loadErrors.enumerated() {
if let err = error {
throw WeightError.readFailed("Failed to preload vision tensor \(visionDescriptors[idx].name): \(err)")
}
}
let preloadTime = Date().timeIntervalSince(startTime) * 1000
print(" ✓ Parallel preloaded \(visionDescriptors.count) vision tensors in \(String(format: "%.1f", preloadTime))ms")
// Convert to floats/tensors dictionaries (sequential, but from preloaded data)
var floats: [String: [Float]] = [:]
var tensors: [String: Data] = [:]
for (idx, desc) in visionDescriptors.enumerated() {
guard let data = loadedData[idx] else { continue }
let name = desc.name
if desc.dtype == .bf16 {
floats[name] = SafeTensorsReader.bf16ToFloat32(data)
} else if desc.dtype == .u32 {
tensors[name] = data
}
}
let weights = try VisionWeightsE2B(device: engine.device, config: config,
floats: floats, tensors: tensors)
let totalTime = Date().timeIntervalSince(startTime) * 1000
print(" ✓ E2B Vision Tower loaded in \(String(format: "%.1f", totalTime))ms")
return try VisionTowerE2B(config: config, engine: engine, weights: weights)
}
+140
View File
@@ -0,0 +1,140 @@
import Metal
public final class VisionWeights {
public let inputProj: QuantizedWeights
public let positionEmbedding: MTLBuffer
public let embeddingProjectionWeight: MTLBuffer // uint32 packed
public let embeddingProjectionScales: MTLBuffer
public let embeddingProjectionBiases: MTLBuffer
public let layers: [VisionLayerWeights]
public init(device: MTLDevice, config: VisionConfig,
tensors: [String: Data], floats: [String: [Float]]) throws {
let pfx = "vision_tower.patch_embedder."
inputProj = try Self.loadQuantized(name: pfx + "input_proj",
tensors: tensors, floats: floats,
device: device,
inDim: config.hiddenSize,
outDim: config.hiddenSize)
guard let pe = floats[pfx + "position_embedding_table"] else {
throw WeightError.tensorNotFound("position_embedding_table")
}
positionEmbedding = device.makeBuffer(bytes: pe, length: pe.count * 4)!
// Embedding projection already quantized
let ep = "embed_vision.embedding_projection"
guard let epWeight = tensors[ep + ".weight"] else {
throw WeightError.tensorNotFound("embedding_projection.weight")
}
embeddingProjectionWeight = epWeight.withUnsafeBytes { ptr in
device.makeBuffer(bytes: ptr.baseAddress!, length: epWeight.count)!
}
guard let epScales = floats[ep + ".scales"] else {
throw WeightError.tensorNotFound("embedding_projection.scales")
}
embeddingProjectionScales = device.makeBuffer(
bytes: epScales, length: epScales.count * 4)!
guard let epBiases = floats[ep + ".biases"] else {
throw WeightError.tensorNotFound("embedding_projection.biases")
}
embeddingProjectionBiases = device.makeBuffer(
bytes: epBiases, length: epBiases.count * 4)!
var loadedLayers: [VisionLayerWeights] = []
for i in 0..<config.numHiddenLayers {
loadedLayers.append(try VisionLayerWeights(
device: device, config: config, layerIdx: i,
tensors: tensors, floats: floats))
}
layers = loadedLayers
}
public static func loadQuantized(name: String,
tensors: [String: Data],
floats: [String: [Float]],
device: MTLDevice,
inDim: Int, outDim: Int) throws -> QuantizedWeights {
let wKey = name + ".weight"
let sKey = name + ".scales"
let bKey = name + ".biases"
guard let wData = tensors[wKey] else {
throw WeightError.tensorNotFound("Quantized weight \(wKey)")
}
guard let sData = floats[sKey] else {
throw WeightError.tensorNotFound("Quantized scales \(sKey)")
}
guard let bData = floats[bKey] else {
throw WeightError.tensorNotFound("Quantized biases \(bKey)")
}
let weight = wData.withUnsafeBytes { ptr in
device.makeBuffer(bytes: ptr.baseAddress!, length: wData.count)!
}
let scales = device.makeBuffer(
bytes: sData, length: sData.count * 4)!
let biases = device.makeBuffer(
bytes: bData, length: bData.count * 4)!
// Compute groupSize: scales shape is [outDim, numGroups], so numGroups = sData.count / outDim
let numGroups = sData.count / outDim
let groupSize = inDim / numGroups
return QuantizedWeights(weight: weight, scales: scales, biases: biases,
inDim: inDim, outDim: outDim, bits: 4, groupSize: groupSize)
}
}
public struct VisionLayerWeights {
public let inputLayernorm: MTLBuffer
public let postAttentionLayernorm: MTLBuffer
public let preFeedforwardLayernorm: MTLBuffer
public let postFeedforwardLayernorm: MTLBuffer
public let selfAttnQProj: QuantizedWeights
public let selfAttnKProj: QuantizedWeights
public let selfAttnVProj: QuantizedWeights
public let selfAttnOProj: QuantizedWeights
public let qNorm: MTLBuffer
public let kNorm: MTLBuffer
public let mlpGateProj: QuantizedWeights
public let mlpUpProj: QuantizedWeights
public let mlpDownProj: QuantizedWeights
public init(device: MTLDevice, config: VisionConfig, layerIdx: Int,
tensors: [String: Data], floats: [String: [Float]]) throws {
let prefix = "vision_tower.encoder.layers.\(layerIdx)"
let h = config.hiddenSize
let m = config.intermediateSize
func loadNorm(_ key: String) throws -> MTLBuffer {
guard let arr = floats[key] else {
throw WeightError.tensorNotFound("Norm \(key)")
}
return device.makeBuffer(bytes: arr, length: arr.count * 4)!
}
inputLayernorm = try loadNorm(prefix + ".input_layernorm.weight")
postAttentionLayernorm = try loadNorm(prefix + ".post_attention_layernorm.weight")
preFeedforwardLayernorm = try loadNorm(prefix + ".pre_feedforward_layernorm.weight")
postFeedforwardLayernorm = try loadNorm(prefix + ".post_feedforward_layernorm.weight")
qNorm = try loadNorm(prefix + ".self_attn.q_norm.weight")
kNorm = try loadNorm(prefix + ".self_attn.k_norm.weight")
func q(_ name: String, inDim: Int, outDim: Int) throws -> QuantizedWeights {
try VisionWeights.loadQuantized(name: prefix + name,
tensors: tensors, floats: floats,
device: device,
inDim: inDim, outDim: outDim)
}
selfAttnQProj = try q(".self_attn.q_proj", inDim: h, outDim: h)
selfAttnKProj = try q(".self_attn.k_proj", inDim: h, outDim: h)
selfAttnVProj = try q(".self_attn.v_proj", inDim: h, outDim: h)
selfAttnOProj = try q(".self_attn.o_proj", inDim: h, outDim: h)
mlpGateProj = try q(".mlp.gate_proj", inDim: h, outDim: m)
mlpUpProj = try q(".mlp.up_proj", inDim: h, outDim: m)
mlpDownProj = try q(".mlp.down_proj", inDim: m, outDim: h)
}
}