import Metal // Production-ready batch generation with buffer reuse // Eliminates buffer allocation overhead for maximum performance extension E4BModel { /// Batch inference context - reuses buffers across calls public final class BatchContext { let device: MTLDevice let maxBatchSize: Int let hiddenSize: Int let vocabSize: Int // Reusable buffers let batchInputBuffer: MTLBuffer let batchOutputBuffer: MTLBuffer let tempEmbeddingBuffer: MTLBuffer public init(device: MTLDevice, maxBatchSize: Int, hiddenSize: Int, vocabSize: Int) { self.device = device self.maxBatchSize = maxBatchSize self.hiddenSize = hiddenSize self.vocabSize = vocabSize // Pre-allocate buffers self.batchInputBuffer = device.makeBuffer( length: maxBatchSize * hiddenSize * MemoryLayout.stride, options: .storageModeShared )! self.batchOutputBuffer = device.makeBuffer( length: maxBatchSize * vocabSize * MemoryLayout.stride, options: .storageModeShared )! self.tempEmbeddingBuffer = device.makeBuffer( length: hiddenSize * MemoryLayout.stride, options: .storageModeShared )! } } /// Create a batch context for reuse (call once at startup) public func createBatchContext(maxBatchSize: Int = 8) -> BatchContext { return BatchContext( device: engine.device, maxBatchSize: maxBatchSize, hiddenSize: hiddenSize, vocabSize: vocabSize ) } /// Optimized batch forward with buffer reuse public func forwardBatchOptimized( tokenIds: [Int], positions: [Int], context: BatchContext ) throws -> [[Float]] { guard tokenIds.count == positions.count else { throw NSError(domain: "Batch", code: -1, userInfo: [NSLocalizedDescriptionKey: "tokenIds and positions must match"]) } let batchSize = tokenIds.count guard batchSize <= context.maxBatchSize else { throw NSError(domain: "Batch", code: -2, userInfo: [NSLocalizedDescriptionKey: "Batch size exceeds context max"]) } if batchSize == 0 { return [] } if batchSize == 1 { return [try forwardOptimized(tokenId: tokenIds[0], position: positions[0])] } // ── Phase 1: Process embeddings SEPARATELY (must complete before layers) ── let embedCmdBuf = engine.commandQueue.makeCommandBuffer()! let inputPtr = context.batchInputBuffer.contents().assumingMemoryBound(to: Float.self) for i in 0..