Section 6/171 menit

6. Batch Prediction

6. Batch Prediction

Batch prediction memungkinkan multiple input diproses sekaligus — lebih efisien karena mengurangi overhead dispatch per-prediction.

MLArrayBatchProvider

swift
// Batch prediction dengan MLArrayBatchProvider
func batchClassify(images: [CVPixelBuffer]) throws -> [String] {
    // Buat array of feature providers
    let providers: [MLFeatureProvider] = try images.map { buffer in
        let input = MyClassifierInput(image: buffer)
        return input
    }

    // Buat batch provider
    let batchProvider = MLArrayBatchProvider(array: providers)

    // Predict semua sekaligus
    let options = MLPredictionOptions()
    options.usesCPUOnly = false  // izinkan GPU/ANE

    let batchOutput = try model.predictions(
        fromBatch: batchProvider,
        options: options
    )

    // Extract hasil
    var labels: [String] = []
    for i in 0..<batchOutput.count {
        let output = batchOutput.features(at: i)
        if let label = output.featureValue(for: "classLabel")?.stringValue {
            labels.append(label)
        }
    }
    return labels
}

Adaptive Batch Size

Batch yang terlalu besar menyebabkan memory pressure; terlalu kecil tidak optimal. Adaptive batch size menyesuaikan dengan kondisi perangkat:

swift
// Adaptive batch size berdasarkan available memory
func optimalBatchSize(for model: MLModel) -> Int {
    // Perkiraan memory per sample (dari model description)
    let inputDesc = model.modelDescription.inputDescriptionsByName
    var bytesPerSample = 0
    for (_, desc) in inputDesc {
        if let imageConstraint = desc.imageConstraint {
            let w = imageConstraint.pixelsWide
            let h = imageConstraint.pixelsHigh
            bytesPerSample += w * h * 4  // RGBA Float32
        } else if let multiArrayConstraint = desc.multiArrayConstraint {
            let count = multiArrayConstraint.shape.reduce(1) { $0 * $1.intValue }
            bytesPerSample += count * 4  // Float32
        }
    }

    // Gunakan 10% dari available memory sebagai batch budget
    let availableMemory = ProcessInfo.processInfo.physicalMemory
    let budget = Int(Double(availableMemory) * 0.10)
    let rawBatchSize = budget / max(bytesPerSample, 1)

    // Clamp ke rentang yang wajar
    return min(max(rawBatchSize, 1), 64)
}

// Streaming batch processor
actor BatchProcessor {
    private let model: MLModel
    private let batchSize: Int

    init(model: MLModel) {
        self.model = model
        self.batchSize = optimalBatchSize(for: model)
    }

    func processAll(inputs: [CVPixelBuffer]) async throws -> [ClassificationResult] {
        var results: [ClassificationResult] = []

        for chunk in inputs.chunks(ofSize: batchSize) {
            let providers = chunk.map { MyModelInput(image: $0) as MLFeatureProvider }
            let batch = MLArrayBatchProvider(array: providers)
            let batchOutput = try model.predictions(fromBatch: batch)

            for i in 0..<batchOutput.count {
                let output = batchOutput.features(at: i)
                results.append(ClassificationResult(from: output))
            }
        }

        return results
    }
}

struct ClassificationResult {
    let label: String
    let confidence: Float

    init(from output: MLFeatureProvider) {
        label = output.featureValue(for: "classLabel")?.stringValue ?? "unknown"
        confidence = output.featureValue(for: "classLabelProbs")?
            .dictionaryValue?[label as NSObject] as? Float ?? 0
    }
}

extension Array {
    func chunks(ofSize size: Int) -> [[Element]] {
        stride(from: 0, to: count, by: size).map { Array(self[$0..<Swift.min($0 + size, count)]) }
    }
}