Core ML Swift 整合
在 iOS App 中載入、配置並執行 Core ML 模型。本技能涵蓋 Swift 端的模型載入、預測、MLTensor、效能分析與部署。目標為 iOS 26+ 及 Swift 6.3,除非特別說明,否則向下相容至 iOS 14。
範圍邊界: Python 端的模型轉換、最佳化(量化、調色板化、剪枝)及框架選擇屬於
apple-on-device-ai技能的範疇。本技能僅掌管 Swift 整合。
有關完整的程式碼模式(包括基於 Actor 的快取、批次推論、影像預處理與測試),請參閱 references/coreml-swift-integration.md。
目錄
- 載入模型
- 模型配置
- 進行預測
- MLTensor (iOS 18+)
- 使用 MLMultiArray
- 影像預處理
- 多模型管線
- Vision 整合
- 效能分析
- 模型部署
- 記憶體管理
- 常見錯誤
- 審查清單
- 參考資料
載入模型
自動生成的類別
當你將 .mlpackage 或 .mlmodelc 拖入 Xcode 時,它會產生一個帶有型別化輸入/輸出的 Swift 類別。請盡可能使用此方式。
import CoreML
let config = MLModelConfiguration()
config.computeUnits = .all
let model = try MyImageClassifier(configuration: config)
手動載入
當模型在執行期間下載或儲存在 Bundle 外部時,從 URL 載入。
let modelURL = Bundle.main.url(
forResource: "MyModel", withExtension: "mlmodelc"
)!
let model = try MLModel(contentsOf: modelURL, configuration: config)
非同步載入 (iOS 16+)
在不阻塞主執行緒的情況下載入模型。對於大型模型,請優先使用此方式。
let model = try await MLModel.load(
contentsOf: modelURL,
configuration: config
)
執行時期編譯
在裝置上將 .mlpackage 或 .mlmodel 編譯為 .mlmodelc。適用於從伺服器下載的模型。
let compiledURL = try await MLModel.compileModel(at: packageURL)
let model = try MLModel(contentsOf: compiledURL, configuration: config)
快取編譯後的 URL——每次啟動都重新編譯會浪費時間。將 compiledURL 複製到持久性位置(例如 Application Support)。
模型配置
MLModelConfiguration 控制計算單元、GPU 存取及模型參數。
計算單元決策表
| 值 | 使用元件 | 使用時機 |
|---|---|---|
.all | CPU + GPU + 神經網路引擎 | 預設值。讓系統決定。 |
.cpuOnly | CPU | 背景任務、音訊工作階段,或 GPU 忙碌時。 |
.cpuAndGPU | CPU + GPU | 需要 GPU,但模型包含 ANE 不支援的操作。 |
.cpuAndNeuralEngine | CPU + 神經網路引擎 | 相容模型的最佳能源效率。 |
let config = MLModelConfiguration()
config.computeUnits = .cpuAndNeuralEngine
// 允許低優先順序的背景推論
config.computeUnits = .cpuOnly
配置屬性
let config = MLModelConfiguration()
config.computeUnits = .all
config.allowLowPrecisionAccumulationOnGPU = true // 更快,些微精度損失
進行預測
使用自動生成的類別
生成的類別提供型別化的輸入/輸出結構體。
let model = try MyImageClassifier(configuration: config)
let input = MyImageClassifierInput(image: pixelBuffer)
let output = try model.prediction(input: input)
print(output.classLabel) // "golden_retriever"
print(output.classLabelProbs) // ["golden_retriever": 0.95, ...]
使用 MLDictionaryFeatureProvider
當輸入是動態或在編譯時期未知時使用。
let inputFeatures = try MLDictionaryFeatureProvider(dictionary: [
"image": MLFeatureValue(pixelBuffer: pixelBuffer),
"confidence_threshold": MLFeatureValue(double: 0.5),
])
let output = try model.prediction(from: inputFeatures)
let label = output.featureValue(for: "classLabel")?.stringValue
非同步預測 (iOS 17+)
let output = try await model.prediction(from: inputFeatures)
批次預測
在單次呼叫中處理多個輸入以獲得更好的吞吐量。
let batchInputs = try MLArrayBatchProvider(array: inputs.map { input in
try MLDictionaryFeatureProvider(dictionary: ["image": MLFeatureValue(pixelBuffer: input)])
})
let batchOutput = try model.predictions(from: batchInputs)
for i in 0..<batchOutput.count {
let result = batchOutput.features(at: i)
print(result.featureValue(for: "classLabel")?.stringValue ?? "unknown")
}
狀態化預測 (iOS 18+)
對需要在多個預測之間維持狀態的模型(序列模型、LLM、音訊累加器)使用 MLState。建立一次狀態,並將其傳遞給每次預測呼叫。
let state = model.makeState()
// 每次預測都會攜帶內部的模型狀態
for frame in audioFrames {
let input = try MLDictionaryFeatureProvider(dictionary: [
"audio_features": MLFeatureValue(multiArray: frame)
])
let output = try await model.prediction(from: input, using: state)
let classification = output.featureValue(for: "label")?.stringValue
}
狀態並非 Sendable——請在單一的 Actor 或任務中使用它。針對並發串流,請呼叫 model.makeState() 來建立獨立的狀態。
MLTensor (iOS 18+)
MLTensor 是一個 Swift 原生的多維陣列,用於前/後處理。操作是惰性執行的——請呼叫 .shapedArray(of:) 來具體化結果。
import CoreML
// 建立
let tensor = MLTensor([1.0, 2.0, 3.0, 4.0])
let zeros = MLTensor(zeros: [3, 224, 224], scalarType: Float.self)
// 重塑
let reshaped = tensor.reshaped(to: [2, 2])
// 數學運算
let softmaxed = tensor.softmax()
let normalized = (tensor - tensor.mean()) / tensor.standardDeviation()
// 與 MLMultiArray 互通
let multiArray = try MLMultiArray([1.0, 2.0, 3.0, 4.0])
let fromMultiArray = MLTensor(multiArray)
let backToArray = tensor.shapedArray(of: Float.self)
使用 MLMultiArray
MLMultiArray 是非影像模型輸入與輸出的主要資料交換型別。當自動生成的類別預期陣列型別的特徵時使用。
// 建立一個 3D 陣列: [batch, sequence, features]
let array = try MLMultiArray(shape: [1, 128, 768], dataType: .float32)
// 寫入數值
for i in 0..<128 {
array[[0, i, 0] as [NSNumber]] = NSNumber(value: Float(i))
}
// 讀取數值
let value = array[[0, 0, 0] as [NSNumber]].floatValue
// 從資料指針建立以實現零複製互通
let data: [Float] = [1.0, 2.0, 3.0]
let fromData = try MLMultiArray(dataPointer: UnsafeMutableRawPointer(mutating: data),
shape: [3],
dataType: .float32,
strides: [1])
有關進階的 MLMultiArray 模式(包括 NLP 分詞與音訊特徵提取),請參閱 references/coreml-swift-integration.md。
影像預處理
影像模型預期 CVPixelBuffer 輸入。使用 CGImage 轉換來處理來自相機或相片圖庫的照片。Vision 的 VNCoreMLRequest 會自動處理此轉換;僅在直接使用 MLModel 進行預測時才需要手動轉換。
import CoreVideo
func createPixelBuffer(from cgImage: CGImage, width: Int, height: Int) -> CVPixelBuffer? {
var pixelBuffer: CVPixelBuffer?
let attrs: [CFString: Any] = [
kCVPixelBufferCGImageCompatibilityKey: true,
kCVPixelBufferCGBitmapContextCompatibilityKey: true,
]
CVPixelBufferCreate(kCFAllocatorDefault, width, height,
kCVPixelFormatType_32ARGB, attrs as CFDictionary, &pixelBuffer)
guard let buffer = pixelBuffer else { return nil }
CVPixelBufferLockBaseAddress(buffer, [])
let context = CGContext(
data: CVPixelBufferGetBaseAddress(buffer),
width: width, height: height,
bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(buffer),
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
)
context?.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
CVPixelBufferUnlockBaseAddress(buffer, [])
return buffer
}
有關其他預處理模式(正規化、中心裁切),請參閱 references/coreml-swift-integration.md。
多模型管線
當預處理或後處理需要單獨的模型時,將模型串聯起來。
// 循序推論: 預處理器 -> 主模型 -> 後處理器
let preprocessed = try preprocessor.prediction(from: rawInput)
let mainOutput = try mainModel.prediction(from: preprocessed)
let finalOutput = try postprocessor.prediction(from: mainOutput)
對於 Xcode 管理的管線,請使用 .mlpackage 中的管線模型類型。每個子模型將在其最佳計算單元上執行。
Vision 整合
使用 Vision 執行 Core ML 影像模型,並自動進行影像預處理(調整大小、正規化、色彩空間、方向)。
現代方式:CoreMLRequest (iOS 18+)
import Vision
import CoreML
let model = try MLModel(contentsOf: modelURL, configuration: config)
let request = CoreMLRequest(model: .init(model))
let results = try await request.perform(on: cgImage)
if let classification = results.first as? ClassificationObservation {
print("\(classification.identifier): \(classification.confidence)")
}
舊有方式:VNCoreMLRequest
let vnModel = try VNCoreMLModel(for: model)
let request = VNCoreMLRequest(model: vnModel) { request, error in
guard let results = request.results as? [VNRecognizedObjectObservation] else { return }
for observation in results {
let label = observation.labels.first?.identifier ?? "unknown"
let confidence = observation.labels.first?.confidence ?? 0
let boundingBox = observation.boundingBox // 正規化座標
print("\(label): \(confidence) at \(boundingBox)")
}
}
request.imageCropAndScaleOption = .scaleFill
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer)
try handler.perform([request])
有關完整的 Vision 框架模式(文字辨識、條碼偵測、文件掃描),請參閱
vision-framework技能。
效能分析
MLComputePlan (iOS 17.4+)
在執行預測之前,檢查每個操作將使用哪個計算裝置。
let computePlan = try await MLComputePlan.load(
contentsOf: modelURL, configuration: config
)
guard case let .program(program) = computePlan.modelStructure else { return }
guard let mainFunction = program.functions["main"] else { return }
for operation in mainFunction.block.operations {
let deviceUsage = computePlan.deviceUsage(for: operation)
let estimatedCost = computePlan.estimatedCost(of: operation)
print("\(operation.operatorName): \(deviceUsage?.preferredComputeDevice ?? "unknown")")
}
Instruments
使用 Instruments 中的 Core ML 儀器範本進行分析:
- 模型載入時間
- 預測延遲(每個操作的分解)
- 計算裝置分派(每個操作的 CPU/GPU/ANE)
- 記憶體配置
請在除錯器之外執行以獲得準確結果(Xcode:Product > Profile)。
模型部署
隨軟體包 vs 隨選資源
| 策略 | 優點 | 缺點 |
|---|---|---|
| 隨軟體包包含 | 立即可用,可離線運作 | 增加 App 下載大小 |
| 隨選資源 | 初始下載較小 | 首次使用前需要下載 |
| 背景資源 (iOS 16+) | 提前下載 | 設定較複雜 |
| CloudKit / 伺服器 | 最大靈活性 | 需要網路,設定時間較長 |
大小考量
- App Store 限制:App Bundle 4 GB
- 蜂窩網路下載限制:200 MB(可請求例外)
- 對於 > 50 MB 的模型,使用 ODR 標籤
- 預先編譯為
.mlmodelc以跳過裝置端編譯
// 隨選資源載入
let request = NSBundleResourceRequest(tags: ["ml-model-v2"])
try await request.beginAccessingResources()
let modelURL = Bundle.main.url(forResource: "LargeModel", withExtension: "mlmodelc")!
let model = try await MLModel.load(contentsOf: modelURL, configuration: config)
// 完成後呼叫 request.endAccessingResources()
記憶體管理
- 進入背景時卸載: 當 App 進入背景時釋放模型參考,以釋放 GPU/ANE 記憶體。返回前景時重新載入。
- 背景任務使用
.cpuOnly: 背景處理無法使用 GPU 或 ANE;設定.cpuOnly可避免無聲的降級與資源競爭。 - 共享模型實例: 切勿從同一個已編譯模型建立多個
MLModel實例。請使用 Actor 提供共享存取。 - 監控記憶體壓力: 大型模型(>100 MB)可能觸發記憶體警告。註冊
UIApplication.didReceiveMemoryWarningNotification並在記憶體壓力下釋放快取的模型。
有關具有生命週期感知載入和快取回收機制的 Actor 模型管理器,請參閱 references/coreml-swift-integration.md。
常見錯誤
不要: 在主執行緒上載入模型。
應該: 使用 MLModel.load(contentsOf:configuration:) 非同步 API 或在背景 Actor 上載入。
原因: 大型模型可能需要數秒鐘來載入,這會凍結使用者介面。
不要: 每次 App 啟動時都將 .mlpackage 重新編譯為 .mlmodelc。
應該: 使用 MLModel.compileModel(at:) 編譯一次,並將編譯後的 URL 持久性地快取。
原因: 編譯成本很高。請將 .mlmodelc 快取在 Application Support 中。
不要: 除非有特定原因,否則不要硬編碼 .cpuOnly。
應該: 使用 .all 讓系統選擇最佳計算單元。
[...已截斷以進行翻譯]