关于 coreml-optimizer
为开发者提供将机器学习模型转换为 Apple CoreML 格式并进行优化的指导。内容涵盖模型压缩、Neural Engine 执行、性能分析,以及性能或精度问题排查。
主要功能
- 01PyTorch 和 TensorFlow 转换指引
- 02量化、调色板化与剪枝
- 03Neural Engine 配置与分析
- 04端到端推理性能分析
- 05精度与性能问题排查
适用场景
- 01缩减 CoreML 模型体积
- 02排查推理速度缓慢
- 03诊断压缩后的精度下降
- 04检查 Neural Engine 执行情况
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ckorhonen/ claude-skills --skill coreml-optimizer安装后在 Claude Code 中输入/coreml-optimizer确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/compression-techniques.mdneural-engine-guide.mdprofiling-tools.md详细介绍
CoreML 优化器
为在 iOS 和 macOS 设备上针对 Apple CoreML 框架优化机器学习模型提供专业指导。
何时使用此技能
在以下情况下使用此技能:
- 将 PyTorch/TensorFlow 模型转换为 CoreML 格式
- 优化 CoreML 模型大小和推理延迟
- 面向 Neural Engine 以获得最高性能
- 调试推理缓慢或计算单元相关问题
- 应用量化、调色板化或剪枝
- 使用 Instruments 分析模型性能
- 排查压缩后精度下降的问题
速度优化检查清单
实现快速 CoreML 推理的关键路径:
1. 验证计算单元配置
许多“运行缓慢”的模型实际上意外地受限于 CPU。通过 MLModelConfiguration.computeUnits 进行配置:
| 选项 | 说明 |
|---|---|
.all | 使用所有可用计算单元,包括 Neural Engine(默认,推荐) |
.cpuAndNeuralEngine | CPU + Neural Engine,不包括 GPU |
.cpuAndGPU | CPU + GPU,不包括 Neural Engine |
.cpuOnly | 强制仅使用 CPU 执行(用于调试/确保一致性) |
Swift 示例:
let config = MLModelConfiguration()
config.computeUnits = .all // .cpuAndNeuralEngine, .cpuAndGPU, .cpuOnly
let model = try MLModel(contentsOf: modelURL, configuration: config)
对每种配置进行基准测试——如果 .all 并不比 .cpuAndGPU 快,模型可能没有使用 Neural Engine。
2. 使用 coremltools 压缩权重
CoreML 执行通常会尽可能使用 float16。使用 coremltools.optimize 进行进一步压缩:
import coremltools as ct
import coremltools.optimize as cto
# 8-bit quantization (2-4x speedup for memory-bound models)
config = cto.coreml.OptimizationConfig(
global_config=cto.coreml.OpLinearQuantizerConfig(
mode="linear_symmetric",
dtype="int8",
granularity="per_channel"
)
)
model = ct.models.MLModel("Model.mlpackage")
quantized = cto.coreml.linear_quantize_weights(model, config=config)
quantized.save("Model_int8.mlpackage")
3. 考虑 4 位量化
较新版本的 coremltools 支持 4 位量化,可实现更激进的压缩:
config = cto.coreml.OptimizationConfig(
global_config=cto.coreml.OpLinearQuantizerConfig(
mode="linear_symmetric",
dtype="int4",
granularity="per_block"
)
)
4. 分析完整流程
预处理/后处理(图像缩放、分词、NMS 等)往往才是真正的瓶颈。应分析完整流程,而不仅是 prediction()。
// Profile everything
let startTotal = CFAbsoluteTimeGetCurrent()
// Preprocessing
let startPreprocess = CFAbsoluteTimeGetCurrent()
let input = preprocessImage(image)
let preprocessTime = CFAbsoluteTimeGetCurrent() - startPreprocess
// Inference
let startInference = CFAbsoluteTimeGetCurrent()
let output = try model.prediction(from: input)
let inferenceTime = CFAbsoluteTimeGetCurrent() - startInference
// Postprocessing
let startPostprocess = CFAbsoluteTimeGetCurrent()
let result = postprocess(output)
let postprocessTime = CFAbsoluteTimeGetCurrent() - startPostprocess
let totalTime = CFAbsoluteTimeGetCurrent() - startTotal
print("Preprocess: \(preprocessTime * 1000)ms")
print("Inference: \(inferenceTime * 1000)ms")
print("Postprocess: \(postprocessTime * 1000)ms")
print("Total: \(totalTime * 1000)ms")
硬件建议(iOS 18 / macOS 15)
| 技术 | 最佳硬件 | 使用场景 |
|---|---|---|
| 权重调色板化(1-8 位) | Neural Engine | 降低运行时内存占用和延迟 |
| W8A8 量化 | Neural Engine(A17 Pro、M4) | 受计算限制的模型 |
| INT4 分块量化 | GPU(Mac) | 在 Mac 上运行大型模型 |
| 剪枝(稀疏权重) | Neural Engine、CPU | 受内存限制的模型 |
核心压缩技术
量化
将精度从 float16/32 降至 int8/int4:
import coremltools.optimize as cto
# Data-free 8-bit quantization (fastest, works well for most models)
config = cto.coreml.OptimizationConfig(
global_config=cto.coreml.OpLinearQuantizerConfig(
mode="linear_symmetric",
dtype="int8",
granularity="per_channel",
weight_threshold=512 # Only quantize layers with >512 params
)
)
quantized = cto.coreml.linear_quantize_weights(model, config=config)
预期结果:
- INT8:模型大小减少约 75%,速度提升 2-3 倍
- INT4:模型大小减少约 87.5%,速度提升 3-4 倍
调色板化
将权重聚类到一个小型查找表中:
config = cto.coreml.OptimizationConfig(
global_config=cto.coreml.OpPalettizerConfig(
mode="kmeans", # or "uniform"
nbits=4, # 16 unique values
granularity="per_channel"
)
)
palettized = cto.coreml.palettize_weights(model, config=config)
适用场景: 对量化敏感的模型通常更能适应调色板化。
剪枝
将不重要的权重置零:
config = cto.coreml.OptimizationConfig(
global_config=cto.coreml.OpMagnitudePrunerConfig(
target_sparsity=0.5 # Remove 50% of weights
)
)
pruned = cto.coreml.prune_weights(model, config=config)
注意: 感知训练的剪枝比训练后剪枝效果更好。
组合压缩
如需最大程度压缩,可组合使用多种技术:
# Pruning + Quantization
pruned = cto.coreml.prune_weights(model, prune_config)
final = cto.coreml.linear_quantize_weights(pruned, quant_config)
Neural Engine 优化
查询 Neural Engine 功能(iOS 17+)
// Get all available compute devices
let devices = MLComputeDevice.allComputeDevices
for device in devices {
switch device {
case .cpu(let cpuDevice):
print("CPU available")
case .gpu(let gpuDevice):
print("GPU available")
case .neuralEngine(let neDevice):
print("Neural Engine available")
print("Total cores: \(neDevice.totalCoreCount)")
@unknown default:
break
}
}
检查 Neural Engine 使用情况
// Method 1: Compare compute unit performance
let configs: [(String, MLComputeUnits)] = [
("All", .all),
("CPU+GPU", .cpuAndGPU),
("CPU+NE", .cpuAndNeuralEngine),
("CPU", .cpuOnly)
]
for (name, units) in configs {
let config = MLModelConfiguration()
config.computeUnits = units
let model = try MLModel(contentsOf: url, configuration: config)
let time = benchmark(model: model)
print("\(name): \(time)ms")
}
// Method 2: Use MLComputePlan (iOS 17.4+)
let plan = try await MLComputePlan.load(contentsOf: modelURL, configuration: config)
// Examine deviceUsage for each operation
// Method 3: Check for H11ANEServicesThread in debugger
// Pause app during inference - if this thread exists, ANE is in use
会阻止使用 Neural Engine 的操作
以下操作会回退到 CPU/GPU:
- 动态张量形状(使用固定形状)
- TopK、Scatter、GatherND
- 不具备 ANE 实现的自定义层
- 空间维度过大(>4096)
- 通道数为奇数(优先使用 8/16 的倍数)
最佳张量形状
# Good for Neural Engine
ct.TensorType(shape=(1, 3, 224, 224)) # Batch=1
ct.TensorType(shape=(1, 64, 56, 56)) # Channels=64 (power of 2)
ct.TensorType(shape=(1, 16, 112, 112)) # Channels=16
# Avoid
ct.TensorType(shape=(4, 3, 224, 224)) # Batch>1 reduces efficiency
ct.TensorType(shape=(1, 13, 224, 224)) # Odd channel count
ct.TensorType(shape=(1, 3, 2048, 2048)) # Very large spatial dims
模型检查
在优化前检查模型结构:
let model = try MLModel(contentsOf: modelURL)
let description = model.modelDescription
// Input/output features
print("Inputs:")
for (name, feature) in description.inputDescriptionsByName {
print(" \(name): \(feature.type)")
}
print("Outputs:")
for (name, feature) in description.outputDescriptionsByName {
print(" \(name): \(feature.type)")
}
// Metadata
if let metadata = description.metadata[.author] {
print("Author: \(metadata)")
}
// Check if updatable
print("Updatable: \(description.isUpdatable)")
模型转换最佳实践
始终使用 ML Program 格式
import coremltools as ct
mlmodel = ct.convert(
traced_model,
inputs=[ct.TensorType(shape=(1, 3, 224, 224))],
convert_to="mlprogram", # NOT "neuralnetwork"
minimum_deployment_target=ct.target.iOS16,
compute_units=ct.ComputeUnit.ALL
)
mlmodel.save("Model.mlpackage")
嵌入预处理
mlmodel = ct.convert(
model,
inputs=[ct.ImageType(
name="image",
shape=(1, 3, 224, 224),
scale=1/255.0,
bias=[0, 0, 0],
color_layout=ct.colorlayout.RGB
)],
convert_to="mlprogram"
)
处理灵活形状
# Range of sizes
ct.TensorType(
name="input",
shape=ct.Shape(shape=(1, 3, ct.RangeDim(224, 1024), ct.RangeDim(224, 1024)))
)
# Specific enumerated sizes
ct.TensorType(
name="input",
shape=ct.EnumeratedShapes(shapes=[(1,3,224,224), (1,3,512,512)])
)
性能分析
Python 基准测试
import time
import numpy as np
model = ct.models.MLModel("Model.mlpackage")
input_data = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)}
# Warm up
for _ in range(5):
_ = model.predict(input_data)
# Benchmark
times = []
for _ in range(100):
start = time.time()
_ = model.predict(input_data)
times.append((time.time() - start) * 1000)
print(f"Mean: {np.mean(times):.2f}ms, Std: {np.std(times):.2f}ms")
print(f"P50: {np.percentile(times, 50):.2f}ms, P99: {np.percentile(times, 99):.2f}ms")
Swift 基准测试
func benchmark(model: MLModel, iterations: Int = 100) throws -> Double {
let input = try MLDictionaryFeatureProvider(dictionary: [
"input": MLMultiArray(shape: [1, 3, 224, 224], dataType: .float32)
])
// Warm up
for _ in 0..<5 { _ = try model.prediction(from: input) }
// Benchmark
let start = CFAbsoluteTimeGetCurrent()
for _ in 0..<iterations { _ = try model.prediction(from: input) }
return (CFAbsoluteTimeGetCurrent() - start) / Double(iterations) * 1000
}
Xcode Instruments
- Product > Profile(Cmd+I)
- 选择“Core ML”模板
- 在推理期间录制跟踪数据
- 分析:
- 各层执行时间
- 计算单元使用情况(ANE/GPU/CPU 图标)
- 内存分配模式
高级优化 API(iOS 17.4+)
MLOptimizationHints
为不同的使用模式配置优化行为:
let config = MLModelConfiguration()
// For models with variable input shapes (default)
config.optimizationHints.reshapeFrequency = .frequent
// For models with stable input shapes (faster inference)
config.optimizationHints.reshapeFrequency = .infrequent
ReshapeFrequency 选项:
.frequent- 形状经常变化时,尽量降低延迟。单次预测可能较慢,但形状转换速度快。.infrequent- 形状变化时,针对新形状重新优化。初始会有延迟,但该形状后续的预测速度更快。
MLComputePlan(执行前分析)
在运行推理前分析计算单元的分配和成本:
// iOS 17.4+
let computePlan = try await MLComputePlan.load(
contentsOf: modelURL,
configuration: config
)
// Check device usage for each operation
for operation in computePlan.modelStructure.mainFunction.operations {
if let deviceUsage = computePlan.deviceUsage(for: operation) {
print("Operation: \(operation.name)")
print("Device: \(deviceUsage)")
}
if let cost = computePlan.estimatedCost(of: operation) {
print("Estimated cost: \(cost)")
}
}
这有助于确定哪些操作在哪些计算单元上运行,而无需执行推理。
MLState(有状态模型 - iOS 18+)
适用于会保留状态的循环模型和 transformer:
// iOS 18+
let model = try await MLModel.load(contentsOf: modelURL)
let state = model.newState()
// Sequential predictions with shared state
for input in inputSequence {
let output = try model.prediction(from: input, using: state)
// State is automatically updated between predictions
}
重要限制:
- 预测期间不要读取或写入状态缓冲区
- 使用同一 MLState 的预测必须按顺序执行(不能并发)
使用 outputBackings 优化内存
预先分配输出缓冲区,以减少内存分配开销:
let options = MLPredictionOptions()
options.outputBackings = [
"output": try MLMultiArray(shape: [1, 1000], dataType: .float32)
]
let result = try model.prediction(from: input, options: options)
线程安全
重要: 一次只能在一个线程或一个调度队列上使用 MLModel 实例。若要并发预测,请创建多个模型实例或使用串行队列。
// Safe: Serial queue
let modelQueue = DispatchQueue(label: "com.app.coreml")
modelQueue.async {
let result = try? model.prediction(from: input)
}
// Safe: Multiple instances for concurrency
let models = (0..<4).map { _ in
try! MLModel(contentsOf: modelURL)
}
常见问题与解决方案
首次推理缓慢
问题: 首次预测耗时是后续预测的 5-10 倍。
解决方案:
- 异步加载模型(推荐 - 不会阻塞 UI):
Task {
let config = MLModelConfiguration()
config.computeUnits = .all
// Async loading - compiles and caches on first load
let model = try await MLModel.load(contentsOf: modelURL, configuration: config)
// Now predictions are fast
}
- 使用虚拟预测进行预热:
Task {
let dummy = try MLDictionaryFeatureProvider(dictionary: [
"input": MLMultiArray(shape: [1, 3, 224, 224], dataType: .float32)
])
for _ in 0..<3 {
_ = try? model.prediction(from: dummy)
}
}
- 在构建时预编译(Xcode 会将
.mlpackage编译为优化后的.mlmodelc)
量化后准确率下降
解决方案:
- 使用逐通道量化(默认)
- 尝试改用调色板量化
- 使用校准数据:
compressed = cto.coreml.linear_quantize_weights(
model,
config=config,
calibration_data=representative_samples
)
- 混合精度 - 跳过敏感层:
config = cto.coreml.OptimizationConfig()
config.set_op_type("conv", cto.coreml.OpLinearQuantizerConfig(dtype="int8"))
config.set_op_name("final_layer", None) # Keep in FP16
模型未使用 Neural Engine
调试步骤:
- 比较
.all与.cpuAndGPU的性能 - 检查是否存在不受支持的操作
- 确认张量形状适合 ANE
- 使用 ML Program 格式(而非 NeuralNetwork)
- 在调试器中检查线程名称是否包含
H11ANEServicesThread
优化工作流
推荐顺序
- 转换为 ML Program 格式,并设置
compute_units=ALL - 建立基线 - 测量大小、延迟和准确率
- 应用 8 位量化(通常不会造成明显影响,速度可提升 2-3 倍)
- 在设备上测试 - 验证准确率和速度
- 如果需要进一步压缩,尝试 4 位量化或调色板量化
- 分析完整流水线 - 优化预处理和后处理
- 在持续负载下测试热表现
大小、速度和准确率之间的权衡
| 压缩级别 | 大小 | 速度 | 对准确率的影响 |
|---|---|---|---|
| 无(FP16) | 基线 | 基线 | 无 |
| 8 位对称量化 | -75% | +2-3 倍 | 极小(<1%) |
| 4 位分块量化 | -87.5% | +3-4 倍 | 较小(1-3%) |
| 4 位调色板量化 | -87.5% | +2-3 倍 | 不定 |
| 剪枝 50% + INT8 | -87.5% | +3-5 倍 | 中等(2-5%) |
快速参考
安装
pip install coremltools
pip install coremltools==9.0b1 # For latest 4-bit features
转换模板
import coremltools as ct
import torch
model.eval()
traced = torch.jit.trace(model, torch.rand(1, 3, 224, 224))
mlmodel = ct.convert(
traced,
inputs=[ct.TensorType(shape=(1, 3, 224, 224))],
convert_to="mlprogram",
minimum_deployment_target=ct.target.iOS16,
compute_units=ct.ComputeUnit.ALL
)
mlmodel.save("Model.mlpackage")
量化模板
import coremltools.optimize as cto
config = cto.coreml.OptimizationConfig(
global_config=cto.coreml.OpLinearQuantizerConfig(
mode="linear_symmetric",
dtype="int8",
granularity="per_channel"
)
)
model = ct.models.MLModel("Model.mlpackage")
quantized = cto.coreml.linear_quantize_weights(model, config=config)
quantized.save("Model_int8.mlpackage")
资源
Apple 文档
CoreML Tools(Python)
WWDC 会议
- WWDC24:将机器学习和 AI 模型带到 Apple silicon
- WWDC23:通过异步预测改进 Core ML 集成
- WWDC23:使用 Core ML Tools 压缩模型
- WWDC22:优化 Core ML 的使用