把一个训练好的模型放到Mac或iPhone上跑起来,听起来只是调用一下API的事,实际上中间隔着模型格式转换、计算单元选择、算子兼容性检查、量化压缩这一整条链路。Core ML是苹果为自家设备提供的推理框架,在Apple Silicon上它能同时调度CPU、GPU和神经网络引擎(ANE)三种计算资源。很多人转换完模型直接用默认配置上线,结果发现推理速度远低于预期,问题往往出在模型根本没跑到ANE上。这篇文章从部署流程出发,把转换、加载、优化几个环节拆开讲清楚。

一、模型格式与转换流程
Core ML不认PyTorch或TensorFlow的原始格式,必须通过coremltools转换成苹果自己的格式。老格式是.mlmodel,单一文件,基于protobuf序列化;从iOS 14、macOS 11开始苹果推出了.mlpackage,它实际上是一个目录结构,内部可以包含多个权重文件和模型规格,对大模型和动态形状的支持明显更好。新项目建议直接用mlpackage。
以PyTorch为例,转换前需要先用torch.jit.trace把模型固化成确定性的计算图,因为Core ML是基于静态图的推理框架,不支持Python层面的动态分支。示例如下:
import torch
import coremltools as ct
model = MyModel().eval()
example = torch.rand(1, 3, 224, 224)
traced = torch.jit.trace(model, example)
mlmodel = ct.convert(
traced,
inputs=[ct.ImageType(name="input", shape=example.shape)],
compute_precision=ct.precision.FLOAT16,
minimum_deployment_target=ct.target.macOS13,
)
mlmodel.save("MyModel.mlpackage")这里有两个细节值得注意。compute_precision设为FLOAT16是因为ANE原生以FP16运算,转换时直接降到半精度可以避免运行时来回转换。minimum_deployment_target决定了生成的模型规格版本,目标系统越新,可用的算子和优化就越多,但兼容的设备范围也会收窄,需要根据实际用户设备分布权衡。
二、compute_units如何决定模型跑在哪里
加载模型时最关键的一个参数是compute_units,它有四个取值:cpu_only、cpu_and_gpu、cpu_and_neural_engine、all。Core ML拿到模型后会在内部做一次调度决策,决定每个算子放在哪个单元执行。ANE吞吐量最高、能耗最低,但它的限制也最多:只支持FP16,不支持某些复杂动态形状,个别算子会触发回退。
import CoreML let config = MLModelConfiguration() config.computeUnits = .cpuAndNeuralEngine let url = Bundle.main.url(forResource: "MyModel", withExtension: "mlpackage")! let model = try MLModel(contentsOf: url, configuration: config) let prediction = try await model.prediction(input: input)
一个常见误区是以为设成all就一定最快。实际上调度器有时会把本来能上ANE的算子留在CPU上,原因可能是前一个算子的输出格式与ANE要求的内存布局不匹配,中间插入的数据重排反而抵消了ANE的收益。判断模型实际跑在哪里,可以用Xcode的Core ML性能报告,或者在转换时输出MLModel的spec查看每个算子上标注的设备分配。
还有一种做法是在coremltools里逐个单元压测。用ComputeUnit.CPU_ONLY和ComputeUnit.CPU_AND_NE分别加载模型跑推理,对比耗时,如果两者差距不大,基本可以断定模型没有有效利用ANE,需要回头检查算子兼容性。
三、量化与性能优化实操
权重量化是最直接有效的优化手段。coremltools支持FP16、INT8甚至更低精度的权重压缩,FP16通常无损且文件体积减半;INT8能把模型压到四分之一左右,但需要用校准数据集走一遍量化流程,对精度的影响要逐层评估。对卷积占主体的视觉模型,INT8量化在ANE上往往还能获得额外加速。
from coremltools.models.neural_network import quantization_utils
quantized = quantization_utils.quantize_weights(
mlmodel, nbits=8,
quantization_mode="linear")
quantized.save("MyModel_int8.mlpackage")除了量化,模型架构本身也决定了能否吃到Apple Silicon的全部算力。ANE偏爱规整的算子序列,比如连续的卷积加BN加激活;动态shape、控制流、自定义算子都会导致回退CPU。如果精度允许,把attention里的某些超越数运算换成多项式近似,或者在导出前把常量折叠做掉,都能显著提升ANE覆盖率。
内存布局是另一个容易被忽视的点。ANE对输入张量的通道布局有要求,图像类输入用ct.ImageType指定scale和bias,比手动在CPU上做归一化再传数组要高效,因为Core ML能把预处理直接融合进计算图。输入分辨率尽量固定,频繁变化shape会让调度器无法缓存最优执行计划。
最后建议在真机上做基准测试,不要依赖模拟器数据。用os_signpost或Xcode的Instruments采样推理耗时,重点观察首帧延迟和稳态延迟的差异:首次推理包含图编译和内存分配,可以通过App启动时预热一次模型来消除。把这套流程走顺之后,一个中等规模的视觉模型在M系列芯片上做到个位数毫秒的推理延迟是完全现实的。
Core MLApple Silicon模型转换修改时间:2026-09-14 10:04:53