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. 01CoreML モデルサイズの削減
  2. 02推論速度低下の調査
  3. 03圧縮後の精度低下の診断
  4. 04Neural 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 Optimizer

iOS および macOS デバイス上の Apple の CoreML フレームワーク向けに、機械学習モデルを最適化するための専門的なガイダンスです。

このスキルを使用するタイミング

次の場合にこのスキルを使用します:

  • PyTorch/TensorFlow モデルを CoreML 形式に変換するとき
  • CoreML モデルのサイズと推論レイテンシを最適化するとき
  • 最大のパフォーマンスを得るために Neural Engine を対象にするとき
  • モデルの推論が遅い場合や、計算ユニットに問題がある場合のデバッグ
  • 量子化、パレタイゼーション、プルーニングを適用するとき
  • Instruments でモデルのパフォーマンスをプロファイリングするとき
  • 圧縮後に精度が低下した場合のトラブルシューティング

高速化の最適化チェックリスト

CoreML 推論を高速化するための重要な手順:

1. 計算ユニットの設定を確認する

「遅い」モデルの多くは、誤って CPU に依存しています。MLModelConfiguration.computeUnits で設定します:

オプション説明
.allNeural Engine を含む、利用可能なすべての計算ユニットを使用します(デフォルト、推奨)
.cpuAndNeuralEngineCPU + Neural Engine を使用し、GPU は除外します
.cpuAndGPUCPU + GPU を使用し、Neural Engine は除外します
.cpuOnlyCPU のみで実行します(デバッグ/一貫性の確認用)

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 以降)

状態を保持するリカレントモデルやトランスフォーマー向けです。

// 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 インスタンスは、一度に 1 つのスレッドまたは 1 つのディスパッチキューで使用してください。予測を並行実行するには、複数のモデルインスタンスを作成するか、シリアルキューを使用します。

// 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)
}

よくある問題と解決策

初回推論が遅い

問題: 初回の予測に、2 回目以降と比べて 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. compute_units=ALL を指定して ML Program 形式に変換する
  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 セッション
コミュニティ

コメント