Highlight
Core ML 引入 MLShapedArray 替代 MLMultiArray 处理多维数据,新增 ML Package 格式分离模型组件便于版本控制,并推出 ML Program 模型类型支持 typed execution,让开发者能在转换时精确控制 Float16/Float32 精度。
核心内容
MLMultiArray 的 Swift 化替代
Core ML 模型经常输出多维数组数据,比如目标检测模型 YOLO 会输出一个二维数组,每行代表一个检测框,每列代表对不同类别的置信度。以前用 MLMultiArray 处理这种数据,代码写起来很别扭:初始化时要传运行时类型,索引时要用 NSNumber,没有类型安全。
WWDC2021 推出了 MLShapedArray,一个纯 Swift 的多维数组类型(01:34)。它是值类型,支持 copy-on-write,有和 Array 类似的切片语法。最重要的是,MLShapedArray 和 MLMultiArray 完全兼容,可以互相转换。
ML Package:模型文件的版本控制方案
传统的 .mlmodel 文件是一个 protobuf 二进制文件,把元数据、接口定义、网络架构和权重参数全部打包在一起。这带来一个问题:改一个 metadata 字段(比如作者名字),整个二进制文件都会变,git diff 会显示几十 MB 的变更。
ML Package 用 macOS 的 package 功能把模型组件拆分成独立文件(05:30):架构、权重、元数据各存一个文件。改 metadata 只改一个 JSON 文件,git diff 清晰可读。Xcode 直接支持在 UI 里编辑 metadata 和输入输出描述。
ML Program:面向未来的模型格式
ML Program 是一种新的模型表示方式,把神经网络描述为一系列强类型的操作(ops),而不是传统的层(layers)(10:05)。权重单独序列化到二进制文件,中间张量的类型在转换时就确定下来。
Typed execution 的核心优势是精度控制。传统神经网络模型的中间张量类型由运行时计算单元决定:Neural Engine 和 GPU 用 Float16,CPU 用 Float32。如果模型对 Float16 精度敏感,只能把 computeUnits 设为 .cpuOnly,牺牲大量性能。
ML Program 的中间张量类型在模型转换时就固定下来。Core ML 运行时可以把 Float32 的 ops 分配到 GPU 执行,同时保留 Float16 的 ops 在 Neural Engine 上执行,不再需要全局的 computeUnits 设置。
详细内容
MLShapedArray 的基本用法
(01:55)
// 初始化一个 2x3 的 Float ShapedArray
let shapedArray = MLShapedArray<Float>(
[[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]]
)
// 访问第二行作为切片
let secondRow = shapedArray[1]
// 访问多行多列的切片
let subArray = shapedArray[0..<2, 1..<3]
关键点:
MLShapedArray<Float>是泛型类型,编译时就确定元素类型,不需要运行时传类型参数- 支持多维索引和范围切片,语法和 Swift Array 一致
- 值类型的 copy-on-write 语义,切片操作不会复制底层数据
MLShapedArray 与 MLMultiArray 互转
(02:31)
// MLMultiArray 转 MLShapedArray
let multiArray: MLMultiArray = ...
let shapedArray = MLShapedArray(multiArray)
// MLShapedArray 转 MLMultiArray
let shapedArray: MLShapedArray<Float> = ...
let multiArray = MLMultiArray(shapedArray)
// 类型转换:Double MultiArray 转 Float ShapedArray
let floatShapedArray = MLShapedArray<Float>(multiArrayOfDoubles)
关键点:
- 互转通过初始化器完成,不需要手动遍历数据
- 支持跨类型转换,比如 Double 转 Float
- Xcode 自动生成的模型包装类会为每个 MultiArray 输出新增对应的 ShapedArray 属性
YOLO 模型输出处理对比
(02:51)
MLMultiArray 版本:
// 用 MLMultiArray 处理 YOLO 输出
func bestLabels(from confidence: MLMultiArray) -> [String] {
var results: [String] = []
let rowCount = confidence.shape[0].intValue
let columnCount = confidence.shape[1].intValue
for row in 0..<rowCount {
var maxConfidence: Double = 0
var bestLabel = ""
for column in 0..<columnCount {
let value = confidence[[NSNumber(value: row), NSNumber(value: column)]].doubleValue
if value > maxConfidence {
maxConfidence = value
bestLabel = labels[column]
}
}
results.append(bestLabel)
}
return results
}
MLShapedArray 版本:
// 用 MLShapedArray 处理同样的输出
func bestLabels(from confidence: MLShapedArray<Float>) -> [String] {
return confidence.map { row in
let maxIndex = row.argmax()
return labels[maxIndex]
}
}
关键点:
- MLMultiArray 版本需要手动处理 NSNumber 转换和二维索引,代码冗长
- MLShapedArray 版本利用 Swift 集合协议(
map、argmax),一行完成同样逻辑 - 模型预测结果如果输出 ShapedArray,直接就能用 Swift 标准库方法处理
转换 TensorFlow 模型为 ML Program
(16:54)
import coremltools as ct
# 加载 TensorFlow 模型
model_path = "style_transfer_model"
image_path = "test_image.jpg"
# 设置输入类型
input_type = ct.ImageType(
shape=(1, 256, 256, 3),
bias=[-1, -1, -1],
scale=1/127.0
)
# 转换为 ML Program(iOS 15 / macOS 12 起)
ml_program = ct.convert(
model_path,
inputs=[input_type],
minimum_deployment_target=ct.target.iOS15
)
# 默认生成 Float16 精度的 ML Program
# 保存模型
ml_program.save("StyleTransfer.mlmodel")
关键点:
minimum_deployment_target设为 iOS15 或 macOS12 时,converter 输出 ML Program 格式- 默认启用 FP16ComputePrecision pass,所有 Float32 张量转为 Float16
- Float16 模型可在 Neural Engine 上执行,获得显著性能提升和功耗降低
控制 ML Program 精度
(20:16)
# 生成 Float32 精度的 ML Program
ml_program_fp32 = ct.convert(
model_path,
inputs=[input_type],
minimum_deployment_target=ct.target.iOS15,
compute_precision=ct.precision.FLOAT32
)
# 评估精度差异
# Float16 版本的 SNR 通常在 70+,Float32 版本在 100+
# 对大多数深度学习模型,Float16 精度损失肉眼不可见
关键点:
compute_precision=FLOAT32禁用 Float16 转换,保持完整精度- Float32 ML Program 在 GPU 和 CPU 上执行,不会在 Neural Engine 上运行
- 如果模型对 Float16 敏感,不需要在 App 代码里改 computeUnits,转换时指定精度即可
- 还支持混合精度:指定特定 ops 用 Float32,其余用 Float16
核心启发
1. 升级现有 Core ML 模型到 ML Package
- 做什么:把项目中的 .mlmodel 文件转换为 .mlpackage,获得 metadata 编辑和版本控制优势
- 为什么值得做:改 metadata 不再产生几十 MB 的 git diff,团队成员可以 review 模型变更
- 怎么开始:在 Xcode 中选中 .mlmodel 文件,点击 Edit 按钮,Xcode 会自动提示转换为 ML Package
2. 用 MLShapedArray 简化模型输出处理
- 做什么:把处理 MLMultiArray 的代码迁移到 MLShapedArray,减少代码量并提高可读性
- 为什么值得做:MLShapedArray 支持 Swift 标准集合协议,可以用
map、filter、argmax等原生方法,代码从几十行减到几行 - 怎么开始:检查 Xcode 自动生成的模型类,使用新增的
xxxShapedArray属性替代xxxMultiArray 属性
3. 为精度敏感模型采用 Float32 ML Program
- 做什么:识别对数值精度敏感的模型(如科学计算、金融预测),在转换时指定 Float32 精度
- 为什么值得做:不再需要把 computeUnits 限制为 .cpuOnly,Float32 ML Program 可以在 GPU 上执行,保留性能的同时确保精度
- 怎么开始:用
coremltools转换时设置compute_precision=FLOAT32,对比 Float16 和 Float32 的 SNR 指标
4. 构建模型版本管理系统
- 做什么:利用 ML Package 的分离文件结构,建立模型的 CI/CD 流程
- 为什么值得做:架构文件(JSON)和权重文件(二进制)分开存储,架构变更可以 code review,权重变更可以走独立的发布流程
- 怎么开始:把 .mlpackage 目录加入 git,metadata.json 和特征描述 JSON 正常 review,大权重文件用 Git LFS 管理
关联 Session
- Build dynamic iOS apps with the Create ML framework — 在设备上训练 Core ML 模型,与 ML Program 的 typed execution 结合使用
- Classify hand poses and actions with Create ML — 手势分类模型训练,了解模型精度与性能的平衡
- Detect people, faces, and poses using Vision — Vision 框架的 ML 模型使用,可受益于 MLShapedArray 和 ML Program
- Accelerate machine learning with Metal Performance Shaders Graph — Metal 加速机器学习,与 Core ML 的 GPU 执行互补
评论
GitHub Issues · utterances