Section 8/171 menit

8. Custom Layers: MLCustomLayer dan MLCustomModel

8. Custom Layers: MLCustomLayer dan MLCustomModel

Ketika model mengandung operasi yang tidak didukung Core ML secara native (Flash Attention, RoPE embedding, custom activation), implementasikan sebagai custom layer.

MLCustomLayer untuk Operasi CPU

swift
// Custom layer: Sigmoid Linear Unit (SiLU) activation
// SiLU(x) = x * sigmoid(x) — tidak ada di Core ML native sebelum iOS 17
@objc(SiLULayer)
class SiLULayer: NSObject, MLCustomLayer {
    // Dipanggil saat model di-load
    required init(parameters: [String: Any]) throws {
        super.init()
        // Parsing parameter dari model spec jika ada
    }

    // Deskripsi output shape berdasarkan input
    func setInputShapes(_ inputShapes: [[NSNumber]]) throws -> [[NSNumber]] {
        // SiLU tidak mengubah shape
        return inputShapes
    }

    // Evaluasi forward pass (CPU)
    func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws {
        let input = inputs[0]
        let output = outputs[0]
        let count = input.count

        let srcPtr = input.dataPointer.assumingMemoryBound(to: Float.self)
        let dstPtr = output.dataPointer.assumingMemoryBound(to: Float.self)

        // SiLU: x * (1 / (1 + exp(-x)))
        // Gunakan Accelerate untuk SIMD-optimized computation
        var negated = [Float](repeating: 0, count: count)
        vDSP_vneg(srcPtr, 1, &negated, 1, vDSP_Length(count))

        var sigmoid = [Float](repeating: 0, count: count)
        var countInt = Int32(count)
        vvexpf(&sigmoid, negated, &countInt)  // exp(-x)

        var ones = [Float](repeating: 1, count: count)
        vDSP_vadd(sigmoid, 1, ones, 1, &sigmoid, 1, vDSP_Length(count))  // 1 + exp(-x)
        vDSP_vdiv(sigmoid, 1, ones, 1, &sigmoid, 1, vDSP_Length(count))  // 1/(1+exp(-x))

        // x * sigmoid(x)
        vDSP_vmul(srcPtr, 1, sigmoid, 1, dstPtr, 1, vDSP_Length(count))
    }

    // Optional: Metal GPU implementation untuk performa lebih baik
    func encodeToCommandBuffer(
        _ commandBuffer: MTLCommandBuffer,
        inputs: [MTLTexture],
        outputs: [MTLTexture]
    ) throws {
        // Implementasi Metal shader jika diperlukan
        // Jika tidak diimplementasikan, fallback ke CPU evaluate()
    }
}

Registrasi Custom Layer di Model

Setelah implementasi, daftarkan ke Core ML saat model di-load:

swift
// Registrasi custom layer sebelum load model
let config = MLModelConfiguration()
config.computeUnits = .cpuAndGPU

// Custom layer harus di-register sebelum model load
// Ini dilakukan melalui coremltools saat convert model:
// coremltools.convert(..., custom_conversion_functions={"SiLU": convert_silu})

// Di Swift: model yang sudah di-package dengan custom layer spec
// akan otomatis menggunakan implementasi yang di-register via @objc class name
let model = try MLModel(contentsOf: compiledModelURL, configuration: config)

MLCustomModel untuk Kontrol Penuh

MLCustomModel memberikan kontrol total atas seluruh inferensi — berguna untuk model yang sama sekali tidak bisa dikonversi:

swift
// Custom model: implementasi seluruh inferensi secara manual
@objc(TFLiteWrapperModel)
class TFLiteWrapperModel: NSObject, MLCustomModel {
    private var interpreter: AnyObject?  // TensorFlow Lite interpreter

    required init(modelDescription: MLModelDescription, parameters: [String: Any]) throws {
        super.init()
        // Inisialisasi TFLite interpreter atau custom runtime
        // Berguna untuk mengintegrasikan model dari framework lain
    }

    func prediction(from input: MLFeatureProvider, options: MLPredictionOptions) throws -> MLFeatureProvider {
        // Jalankan inferensi menggunakan custom runtime
        // Konversi output ke MLFeatureProvider
        return SimpleFeatureProvider(features: [:])
    }
}

class SimpleFeatureProvider: MLFeatureProvider {
    let features: [String: MLFeatureValue]
    var featureNames: Set<String> { Set(features.keys) }
    init(features: [String: MLFeatureValue]) { self.features = features }
    func featureValue(for featureName: String) -> MLFeatureValue? { features[featureName] }
}