coreml-optimizer について
Apple の CoreML 向けに機械学習モデルを変換・最適化する開発者向けガイダンススキルです。モデル圧縮、Neural Engine の実行、プロファイリング、性能や精度の問題への対処を扱います。
主な機能
- 01PyTorch・TensorFlow の変換案内
- 02量子化・パレタイゼーション・プルーニング
- 03Neural Engine の設定と分析
- 04推論パイプライン全体のプロファイリング
- 05精度と性能の問題の調査
活用シーン
- 01CoreML モデルサイズの削減
- 02推論速度低下の調査
- 03圧縮後の精度低下の診断
- 04Neural 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 Optimizer
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 以降)
状態を保持するリカレントモデルやトランスフォーマー向けです。
// 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 倍の時間がかかります。
解決策:
- モデルを非同期で読み込む(推奨 - 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があるか確認する
最適化ワークフロー
推奨する順序
compute_units=ALLを指定して ML Program 形式に変換する- ベースラインを確立する - サイズ、レイテンシ、精度を測定する
- 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 のドキュメント
- Core ML フレームワーク
- MLModel
- MLModelConfiguration
- MLComputeUnits
- MLOptimizationHints
- MLComputePlan
- アプリサイズの削減
CoreML Tools(Python)
WWDC セッション
- WWDC24: 機械学習モデルと AI モデルを Apple シリコンに対応させる
- WWDC23: 非同期予測で Core ML の統合を改善する
- WWDC23: Core ML Tools でモデルを圧縮する
- WWDC22: Core ML の使用を最適化する