Section 16/171 menit

16. Real Use Cases

16. Real Use Cases

Use Case 1: Real-time Style Transfer untuk Camera

Skenario: App filter kamera yang menerapkan artistic style ke setiap frame video secara real-time dengan target 30fps (33ms per frame).

swift
// MARK: - Real-time Style Transfer Pipeline

final class StyleTransferPipeline {
    private let model: MLModel
    private let outputWidth = 512
    private let outputHeight = 512

    // Pre-allocated output buffer untuk menghindari alokasi per frame
    private let outputBuffer: CVPixelBuffer

    init() throws {
        let config = MLModelConfiguration()
        config.computeUnits = .cpuAndNeuralEngine  // ANE lebih hemat baterai untuk style transfer
        model = try StyleTransferModel(configuration: config).model

        // Pre-allocate output buffer
        var buffer: CVPixelBuffer?
        CVPixelBufferCreate(
            kCFAllocatorDefault,
            outputWidth,
            outputHeight,
            kCVPixelFormatType_32BGRA,
            nil,
            &buffer
        )
        outputBuffer = buffer!
    }

    // Input: CMSampleBuffer dari AVCaptureSession
    // Output: UIImage yang sudah di-style transfer
    func process(sampleBuffer: CMSampleBuffer) throws -> CVPixelBuffer? {
        guard let inputPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            return nil
        }

        // Custom feature provider: zero-copy dari camera buffer
        let inputProvider = StyleTransferInput(pixelBuffer: inputPixelBuffer)
        let outputProvider = StyleTransferOutput(outputBuffer: outputBuffer)

        // Predict dengan pre-allocated output
        let options = MLPredictionOptions()
        let result = try model.prediction(from: inputProvider, options: options)

        return result.featureValue(for: "stylizedImage")?.imageBufferValue
    }
}

// Zero-copy input provider
final class StyleTransferInput: MLFeatureProvider {
    private let pixelBuffer: CVPixelBuffer
    var featureNames: Set<String> { ["image"] }

    init(pixelBuffer: CVPixelBuffer) {
        self.pixelBuffer = pixelBuffer
    }

    func featureValue(for featureName: String) -> MLFeatureValue? {
        guard featureName == "image" else { return nil }
        return try? MLFeatureValue(pixelBuffer: pixelBuffer, pixelFormatType: kCVPixelFormatType_32BGRA)
    }
}

final class StyleTransferOutput: MLFeatureProvider {
    private let buffer: CVPixelBuffer
    var featureNames: Set<String> { ["stylizedImage"] }

    init(outputBuffer: CVPixelBuffer) { self.buffer = outputBuffer }

    func featureValue(for featureName: String) -> MLFeatureValue? {
        guard featureName == "stylizedImage" else { return nil }
        return try? MLFeatureValue(pixelBuffer: buffer, pixelFormatType: kCVPixelFormatType_32BGRA)
    }
}

// MARK: - Camera Integration

final class CameraStyleViewController: UIViewController {
    private var captureSession: AVCaptureSession!
    private var pipeline: StyleTransferPipeline!
    private var displayLayer: AVSampleBufferDisplayLayer!
    private var processingQueue = DispatchQueue(label: "style.transfer", qos: .userInteractive)

    override func viewDidLoad() {
        super.viewDidLoad()
        try? setupPipeline()
        setupCamera()
        setupDisplayLayer()
    }

    private func setupPipeline() throws {
        pipeline = try StyleTransferPipeline()
    }

    private func setupCamera() {
        captureSession = AVCaptureSession()
        captureSession.sessionPreset = .hd1280x720

        guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
              let input = try? AVCaptureDeviceInput(device: device) else { return }

        captureSession.addInput(input)

        let output = AVCaptureVideoDataOutput()
        output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
        output.setSampleBufferDelegate(self, queue: processingQueue)
        captureSession.addOutput(output)

        captureSession.startRunning()
    }

    private func setupDisplayLayer() {
        displayLayer = AVSampleBufferDisplayLayer()
        displayLayer.frame = view.bounds
        view.layer.addSublayer(displayLayer)
    }
}

extension CameraStyleViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(
        _ output: AVCaptureOutput,
        didOutput sampleBuffer: CMSampleBuffer,
        from connection: AVCaptureConnection
    ) {
        guard let styledBuffer = try? pipeline.process(sampleBuffer: sampleBuffer) else { return }

        // Convert CVPixelBuffer back to CMSampleBuffer for display
        // (simplified — production code perlu timing info)
        displayLayer.enqueue(sampleBuffer)
    }
}

Use Case 2: On-Device Recommendation Engine dengan Updatable Model

Skenario: E-commerce app yang belajar preferensi user dari interaksi dan mempersonalisasi rekomendasi produk on-device.

swift
// MARK: - Personalized Recommendation Engine

actor ProductRecommendationEngine {
    private var model: MLModel
    private var pendingInteractions: [UserInteraction] = []
    private let minInteractionsForUpdate = 20
    private let modelURL: URL

    struct UserInteraction {
        let productEmbedding: [Float]  // 128-dim embedding dari server
        let engaged: Bool              // user klik/beli = true, skip = false
    }

    struct Recommendation {
        let productID: String
        let score: Float
    }

    init() throws {
        let localModelURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
            .first!.appending(component: "PersonalRecommender.mlmodelc")

        // Gunakan model personal jika ada, fallback ke base model
        if FileManager.default.fileExists(atPath: localModelURL.path) {
            model = try MLModel(contentsOf: localModelURL)
            modelURL = localModelURL
        } else {
            let baseURL = Bundle.main.url(forResource: "BaseRecommender", withExtension: "mlmodelc")!
            model = try MLModel(contentsOf: baseURL)
            modelURL = baseURL
        }
    }

    func recordInteraction(_ interaction: UserInteraction) async throws {
        pendingInteractions.append(interaction)

        // Update model setelah cukup data terkumpul
        if pendingInteractions.count >= minInteractionsForUpdate {
            try await updateModelWithPendingInteractions()
        }
    }

    func rankProducts(_ productEmbeddings: [(id: String, embedding: [Float])]) throws -> [Recommendation] {
        return try productEmbeddings.compactMap { product in
            let input = try makeRecommenderInput(embedding: product.embedding)
            let output = try model.prediction(from: input)
            let score = output.featureValue(for: "engagementScore")?.multiArrayValue?[0].floatValue ?? 0
            return Recommendation(productID: product.id, score: score)
        }
        .sorted { $0.score > $1.score }
    }

    private func updateModelWithPendingInteractions() async throws {
        let interactions = pendingInteractions
        pendingInteractions.removeAll()

        let providers: [MLFeatureProvider] = try interactions.map { interaction in
            try makeTrainingInput(
                embedding: interaction.productEmbedding,
                label: interaction.engaged ? 1 : 0
            )
        }

        let trainingData = MLArrayBatchProvider(array: providers)

        // Update task
        let updatedModelURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
            .first!.appending(component: "PersonalRecommender_updated.mlmodelc")

        let progressHandlers = MLUpdateTask.progressHandlers(
            forStoreAt: modelURL,
            trainingData: trainingData,
            configuration: MLModelConfiguration()
        ) { _ in  // progress
        } completionHandler: { [weak self] context in
            guard let self else { return }
            Task {
                if context.task.error == nil {
                    try? context.model.write(to: updatedModelURL)
                    if let newModel = try? MLModel(contentsOf: updatedModelURL) {
                        await self.updateModel(newModel)
                    }
                }
            }
        }

        let task = try MLUpdateTask(
            forModelAt: modelURL,
            trainingData: trainingData,
            configuration: MLModelConfiguration(),
            progressHandlers: progressHandlers
        )
        task.resume()
    }

    private func updateModel(_ newModel: MLModel) {
        model = newModel
    }

    private func makeRecommenderInput(embedding: [Float]) throws -> MLFeatureProvider {
        let array = try MLMultiArray(shape: [128], dataType: .float32)
        embedding.withUnsafeBufferPointer { buffer in
            array.dataPointer.assumingMemoryBound(to: Float.self)
                .initialize(from: buffer.baseAddress!, count: buffer.count)
        }
        return SimpleFeatureProvider(features: [
            "productEmbedding": MLFeatureValue(multiArray: array)
        ])
    }

    private func makeTrainingInput(embedding: [Float], label: Int) throws -> MLFeatureProvider {
        let embeddingArray = try MLMultiArray(shape: [128], dataType: .float32)
        embedding.withUnsafeBufferPointer { buffer in
            embeddingArray.dataPointer.assumingMemoryBound(to: Float.self)
                .initialize(from: buffer.baseAddress!, count: buffer.count)
        }
        let labelArray = try MLMultiArray(shape: [1], dataType: .int32)
        labelArray.dataPointer.assumingMemoryBound(to: Int32.self)[0] = Int32(label)

        return SimpleFeatureProvider(features: [
            "productEmbedding": MLFeatureValue(multiArray: embeddingArray),
            "engagementLabel": MLFeatureValue(multiArray: labelArray)
        ])
    }
}