Core ML Swift 集成
在 iOS 应用中加载、配置和运行 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
}
State 不是 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)。
模型部署
Bundle vs 按需资源
| 策略 | 优点 | 缺点 |
|---|---|---|
| 打包在应用中 | 即时可用,离线工作 | 增加应用下载大小 |
| 按需资源 | 初始下载更小 | 首次使用前需要下载 |
| 后台资源 (iOS 16+) | 提前下载 | 设置更复杂 |
| CloudKit / 服务器 | 最大灵活性 | 需要网络,设置时间更长 |
大小注意事项
- App Store 限制:应用包 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()
内存管理
- 进入后台时卸载: 当应用进入后台时释放模型引用,以释放 GPU/ANE 内存。返回前台时重新加载。
- 后台任务使用
.cpuOnly: 后台处理无法使用 GPU 或 ANE;设置.cpuOnly可避免静默回退和资源争用。 - 共享模型实例: 切勿从同一编译模型创建多个
MLModel实例。使用 actor 提供共享访问。 - 监控内存压力: 大型模型(>100 MB)可能触发内存警告。注册
UIApplication.didReceiveMemoryWarningNotification,并在内存压力下释放缓存的模型。
有关包含生命周期感知加载和缓存淘汰的基于 actor 的模型管理器,请参阅 references/coreml-swift-integration.md。
常见错误
不要: 在主线程上加载模型。
应该: 使用 MLModel.load(contentsOf:configuration:) 异步 API 或在后台 actor 上加载。
原因: 大型模型可能需要几秒钟来加载,从而冻结 UI。
不要: 每次应用启动时都将 .mlpackage 重新编译为 .mlmodelc。
应该: 使用 MLModel.compileModel(at:) 编译一次,并持久化缓存编译后的 URL。
原因: 编译成本高昂。将 .mlmodelc 缓存到 Application Support 中。
不要: 除非有特定原因,否则硬编码 .cpuOnly。
应该: 使用 .all 并让系统选择最佳计算单元。
...(内容已截断以便翻译)
审查清单
- 模型加载使用异步 API 或在后台执行?
- 编译后的模型 URL 已缓存并持久化?
- 计算单元配置符合部署目标?
- 对于有状态模型,状态管理正确(actor 隔离)?
- 图像预处理与模型输入要求匹配?
- 内存压力处理已实现(卸载、重新加载)?
- 性能已使用 MLComputePlan 分析?
- 部署策略适合模型大小和使用模式?