Section 5/171 menit
5. Custom MLFeatureProvider
5. Custom MLFeatureProvider
Generated code dari Xcode sudah membuat subclass MLFeatureProvider otomatis. Tapi untuk performa ekstrem atau input yang tidak standar, buat implementasi custom.
Mengapa Custom MLFeatureProvider?
- Zero-copy: hindari konversi data yang tidak perlu
- Lazy evaluation: hitung fitur hanya saat diminta model
- Memory efficiency: tidak alokasi semua fitur sekaligus
- Type flexibility: terima input dari sumber berbeda tanpa wrapper
swift
// MARK: - Custom Feature Provider untuk NLP Model
final class NLPFeatureProvider: MLFeatureProvider {
private let tokenIDs: [Int32]
private let attentionMask: [Int32]
private let sequenceLength: Int
// Lazy MLMultiArray — tidak dibuat sampai model request
private var cachedInputIDs: MLMultiArray?
private var cachedMask: MLMultiArray?
init(tokens: [Int32], mask: [Int32]) {
assert(tokens.count == mask.count)
self.tokenIDs = tokens
self.attentionMask = mask
self.sequenceLength = tokens.count
}
// MLFeatureProvider protocol: nama feature yang tersedia
var featureNames: Set<String> {
return ["input_ids", "attention_mask"]
}
// MLFeatureProvider protocol: nilai untuk nama feature tertentu
func featureValue(for featureName: String) -> MLFeatureValue? {
switch featureName {
case "input_ids":
return MLFeatureValue(multiArray: inputIDsArray)
case "attention_mask":
return MLFeatureValue(multiArray: maskArray)
default:
return nil
}
}
// Lazy computation: buat MLMultiArray hanya sekali, cache hasilnya
private var inputIDsArray: MLMultiArray {
if let cached = cachedInputIDs { return cached }
let array = try! MLMultiArray(shape: [1, sequenceLength as NSNumber], dataType: .int32)
tokenIDs.withUnsafeBufferPointer { buffer in
let dst = array.dataPointer.assumingMemoryBound(to: Int32.self)
dst.initialize(from: buffer.baseAddress!, count: buffer.count)
}
cachedInputIDs = array
return array
}
private var maskArray: MLMultiArray {
if let cached = cachedMask { return cached }
let array = try! MLMultiArray(shape: [1, sequenceLength as NSNumber], dataType: .int32)
attentionMask.withUnsafeBufferPointer { buffer in
let dst = array.dataPointer.assumingMemoryBound(to: Int32.self)
dst.initialize(from: buffer.baseAddress!, count: buffer.count)
}
cachedMask = array
return array
}
}
// Penggunaan
let tokenizer = BertTokenizer()
let (ids, mask) = tokenizer.tokenize(text: "Halo dunia")
let input = NLPFeatureProvider(tokens: ids, mask: mask)
let output = try model.prediction(from: input)
let logits = output.featureValue(for: "logits")?.multiArrayValue
Custom Feature Provider untuk Camera Feed
swift
// Zero-copy dari CMSampleBuffer ke Core ML input
final class CameraFeatureProvider: MLFeatureProvider {
private let sampleBuffer: CMSampleBuffer
private let imageConstraint: MLImageConstraint
var featureNames: Set<String> { ["image"] }
init(sampleBuffer: CMSampleBuffer, constraint: MLImageConstraint) {
self.sampleBuffer = sampleBuffer
self.imageConstraint = constraint
}
func featureValue(for featureName: String) -> MLFeatureValue? {
guard featureName == "image",
let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return nil
}
// MLFeatureValue langsung dari CVPixelBuffer — tidak perlu konversi ke UIImage
return try? MLFeatureValue(
pixelBuffer: pixelBuffer,
constraint: imageConstraint
)
}
}
// Penggunaan: ambil constraint dari model description
let description = model.modelDescription
let imageFeatureDescription = description.inputDescriptionsByName["image"]
let constraint = imageFeatureDescription?.imageConstraint
let provider = CameraFeatureProvider(sampleBuffer: sampleBuffer, constraint: constraint!)
let output = try model.prediction(from: provider)