mcpField
C
coreml-optimizer

coreml-optimizer

github.com/ckorhonen开发2254 次安装5 周前更新
代码仓库

关于 coreml-optimizer

为开发者提供将机器学习模型转换为 Apple CoreML 格式并进行优化的指导。内容涵盖模型压缩、Neural Engine 执行、性能分析,以及性能或精度问题排查。

主要功能

  1. 01PyTorch 和 TensorFlow 转换指引
  2. 02量化、调色板化与剪枝
  3. 03Neural Engine 配置与分析
  4. 04端到端推理性能分析
  5. 05精度与性能问题排查

适用场景

  1. 01缩减 CoreML 模型体积
  2. 02排查推理速度缓慢
  3. 03诊断压缩后的精度下降
  4. 04检查 Neural Engine 执行情况

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ckorhonen/claude-skills --skill coreml-optimizer

安装后在 Claude Code 中输入/coreml-optimizer确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
compression-techniques.md
neural-engine-guide.md
profiling-tools.md

详细介绍

CoreML 优化器

为在 iOS 和 macOS 设备上针对 Apple CoreML 框架优化机器学习模型提供专业指导。

何时使用此技能

在以下情况下使用此技能:

  • 将 PyTorch/TensorFlow 模型转换为 CoreML 格式
  • 优化 CoreML 模型大小和推理延迟
  • 面向 Neural Engine 以获得最高性能
  • 调试推理缓慢或计算单元相关问题
  • 应用量化、调色板化或剪枝
  • 使用 Instruments 分析模型性能
  • 排查压缩后精度下降的问题

速度优化检查清单

实现快速 CoreML 推理的关键路径:

1. 验证计算单元配置

许多“运行缓慢”的模型实际上意外地受限于 CPU。通过 MLModelConfiguration.computeUnits 进行配置:

选项说明
.all使用所有可用计算单元,包括 Neural Engine(默认,推荐)
.cpuAndNeuralEngineCPU + Neural Engine,不包括 GPU
.cpuAndGPUCPU + 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
  1. Product > Profile(Cmd+I)
  2. 选择“Core ML”模板
  3. 在推理期间录制跟踪数据
  4. 分析:
    • 各层执行时间
    • 计算单元使用情况(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 倍。

解决方案:

  1. 异步加载模型(推荐 - 不会阻塞 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
}
  1. 使用虚拟预测进行预热:
Task {
    let dummy = try MLDictionaryFeatureProvider(dictionary: [
        "input": MLMultiArray(shape: [1, 3, 224, 224], dataType: .float32)
    ])
    for _ in 0..<3 {
        _ = try? model.prediction(from: dummy)
    }
}
  1. 在构建时预编译(Xcode 会将 .mlpackage 编译为优化后的 .mlmodelc)
量化后准确率下降

解决方案:

  1. 使用逐通道量化(默认)
  2. 尝试改用调色板量化
  3. 使用校准数据:
compressed = cto.coreml.linear_quantize_weights(
    model,
    config=config,
    calibration_data=representative_samples
)
  1. 混合精度 - 跳过敏感层:
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

调试步骤:

  1. 比较 .all 与 .cpuAndGPU 的性能
  2. 检查是否存在不受支持的操作
  3. 确认张量形状适合 ANE
  4. 使用 ML Program 格式(而非 NeuralNetwork)
  5. 在调试器中检查线程名称是否包含 H11ANEServicesThread

优化工作流

推荐顺序
  1. 转换为 ML Program 格式,并设置 compute_units=ALL
  2. 建立基线 - 测量大小、延迟和准确率
  3. 应用 8 位量化(通常不会造成明显影响,速度可提升 2-3 倍)
  4. 在设备上测试 - 验证准确率和速度
  5. 如果需要进一步压缩,尝试 4 位量化或调色板量化
  6. 分析完整流水线 - 优化预处理和后处理
  7. 在持续负载下测试热表现
大小、速度和准确率之间的权衡
压缩级别大小速度对准确率的影响
无(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 会议
社区

评论

支持 Markdown