4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ExecuTorchとCoreMLでオンデバイス推論を実現する

4
Posted at

はじめに

iOSアプリの開発において、PyTorchで学習したMobileNetV3モデルをオンデバイスで推論させようとしました。サーバーにリクエストを投げる構成も考えられますが、オフラインでの動作やレイテンシを考えると、オンデバイス推論が望ましいケースは多いです。

本記事では、なぜPyTorchのモデルをそのままiOSに載せられないのかという基本的な背景から、ExecuTorchとは何か、XNNPACKとCoreMLといったバックエンドの違い、そして実際の変換コードやログの読み方までを整理して解説します。

なお、本記事はAIを活用しつつ、実装・検証をもとに執筆しています。

なぜPyTorchモデルをiOSでそのまま動かせないのか

PyTorchの通常の実行モデルはEager Modeと呼ばれています。演算が定義された瞬間に即座に実行されるインタプリタ型の実行方式で、Pythonランタイムが必須となります。これは研究・開発フェーズにおける柔軟性をもたらしますが、デプロイにおいては重大な問題となります。

iOSデバイスにはPythonランタイムが存在しません。仮にPythonを組み込もうとしても、ランタイム自体が数十MB規模になり、モバイルアプリとして現実的ではありません。さらにPyTorchのEager Modeが前提とする動的メモリアロケーションは、メモリ制約の厳しいエッジデバイスでは深刻なパフォーマンス劣化とバッテリー消費の原因になります。

これらの問題を解決するために、モデルをグラフとして静的に捕捉し、C++ランタイムで実行できる形式に変換するという手順が必要になります。

ExecuTorchとは何か

ExecuTorchは、PyTorchがエッジデバイス向けに開発した公式のオンデバイスAIデプロイメントソリューションです。スマートフォンからマイクロコントローラまで、多様なハードウェアへのデプロイを想定したモジュラーかつポータブルなアーキテクチャを持ちます。MetaではInstagramやWhatsApp、Meta Quest 3といったプロダクトのオンデバイス推論にExecuTorchが実際に使われています。

ExecuTorchの主要な設計原則は、サードパーティの中間表現フォーマットへの変換を不要にすることにあります。ONNXやTFLiteへの変換ステップを挟むと、デバッグが困難になったり、変換失敗でモデルが動かなくなるリスクがあります。ExecuTorchはこれらの問題を排除し、PyTorchのエコシステムを維持したままエッジデプロイを可能にします。

ExecuTorchの3ステップ

PyTorchモデルをExecuTorchで動かすまでのフローは3つのフェーズに分かれます。

[PyTorch nn.Module] 
      ↓ 1. Export(torch.export)
[EXIR / ATen Dialect] 
      ↓ 2. Compile(to_edge_transform_and_lower)
[.pteファイル] 
      ↓ 3. Execute(C++ランタイム)
[推論結果]

Step 1: Export — 計算グラフの静的捕捉

torch.export() を呼び出すと、nn.ModuleのPython実装からEXIR(Export Intermediate Representation)が生成されます。EXIRはATen Dialectと呼ばれる表現形式を持ち、PyTorchのTensorライブラリであるATenのオペレータセットで計算グラフを表現します。

このフェーズでは、TorchDynamoによるグラフキャプチャとAOTAutogradによる変換が行われ、Pythonへの依存が取り除かれます。グラフはCore ATen Op Setと呼ばれる標準化された小さなオペレータセットに分解されます。このセットが小さいことで、サードパーティのバックエンドやアクセラレータが対応すべきオペレータの数を抑えられます。

exported = torch.export.export(model, (dummy_input,))

Step 2: Compile — AOT最適化と.pteへの変換

Exportされたプログラムを、実行時に効率よく読み込めるExecuTorchプログラム(.pte形式)に変換するフェーズです。「AOT(Ahead-of-Time)」と呼ばれるように、実行前にできる限りの最適化を済ませておく思想が徹底されています。

具体的には以下が行われます。

  • メモリプランニング: エッジ環境では動的なメモリアロケーション/解放がパフォーマンスとバッテリーに大きな負荷をかけます。AOTメモリプランニングにより静的なメモリ割り当てが確定されます
  • out-variant変換: 関数型オペレータ表現(出力を返す形式)を、出力バッファを引数として受け取るout variant形式に変換します。これによりオブジェクト生成コストが排除されます
  • バックエンドへの委譲(Delegation): Partitionerによって、グラフの一部または全部が専用ハードウェアアクセラレータに委譲されます(後述)
et_program = to_edge_transform_and_lower(
    exported,
    partitioner=[SomePartitioner()],
).to_executorch()

Step 3: Execute — 軽量C++ランタイムでの実行

.pteファイルを軽量なC++ランタイムがロードし、推論を実行します。このランタイムのベースフットプリントは50KB程度と極めて小さく、マイクロコントローラのような制約の強い環境でも動作します。Step 2でAOT最適化が済んでいるため、実行時のオーバーヘッドは最小限です。

バックエンドとPartitionerの仕組み

ExecuTorchの強みの一つがバックエンドシステムです。これは「計算グラフをどのハードウェアで実行するか」を決定する仕組みであり、XNNPACK、CoreML、Qualcomm QNN、ARM Ethos NPUなど12以上のバックエンドが用意されています。

Partitionerの役割

Partitionerは、Exportされたグラフのどのサブグラフをどのバックエンドに委譲するかを決定するコンポーネントです。「グラフのパーティショニング(分割)」という名前の通り、モデル全体を一つのバックエンドに渡すのではなく、そのバックエンドがサポートしているオペレータの部分だけを切り出して委譲し、サポートされていない部分はCPUフォールバック(ポータブルカーネル)で実行するという仕組みになっています。

変換後の.pteファイルには、CoreMLやXNNPACKへの委譲サブグラフが call_delegate ノードとして埋め込まれます。

XNNPACKとは

XNNPACKは、ARM・x86・WebAssemblyなど複数のCPUアーキテクチャ向けに最適化された高性能な浮動小数点ニューラルネットワーク演算ライブラリです。GoogleとMetaによって共同開発されており、クロスプラットフォームで動作するのが大きな特徴です。

XNNPACKはCPUのSIMD(Single Instruction, Multiple Data)命令セットを活用して演算を並列化します。ARMではNEON命令セット、x86ではAVX/AVX-512を使います。

しかしXNNPACKはCPUの最適化ライブラリであり、Apple Siliconに搭載されたGPUやANE(Apple Neural Engine)には一切アクセスしません。今回の10秒という推論時間はこれが原因でした。MobileNetV3のような畳み込みモデルをCPUのみで動かすと、このような遅延が生じます。

# XNNPACKバックエンドへの変換
from executorch.backends.xnnpack.partition.xnnpack_partitioner import XnnpackPartitioner

et_program = to_edge_transform_and_lower(
    exported,
    partitioner=[XnnpackPartitioner()],
).to_executorch()

CoreMLとApple Neural Engine(ANE)

CoreMLとは

CoreMLはAppleが提供するオンデバイス機械学習フレームワークで、iOS 11 / macOS 10.13以降で利用可能です。CoreMLの重要な特徴は、CPU・GPU・ANEを自動的に使い分けるハイブリッド実行プランを生成することにあります。モデルのオペレータ構成やデバイスのハードウェア状況に応じて、最も効率的なハードウェアに処理を割り当てます。

ANE(Apple Neural Engine)とは

ANEはApple A/Mシリーズチップに搭載されたニューラルネットワーク専用のハードウェアアクセラレータ(NPU: Neural Processing Unit)です。初代は2017年のA11チップ(iPhone X)に搭載され、当初はFace IDやAnimojiなどのApple内部機能にのみ使われていました。A12以降でCoreML経由のサードパーティアクセスが解放されました。

ANEは行列積・畳み込み演算など、ディープラーニングの推論で頻出するテンソル演算に特化したアーキテクチャを持ちます。Apple特許によれば、複数のニューラルエンジンコアと多モードプレーナー回路で構成されており、並列計算に最適化された設計になっています。重要な点として、ANEは推論専用のハードウェアでありFP16精度のみをサポートします。FP32モデルはCoreMLが自動的にFP16に変換して実行します。

代表的なスペックを示します。

チップ ANEコア数 TOPS(兆回演算/秒)
A11 Bionic(2017) 2 0.6
A12 Bionic(2018) 8 5
A15 Bionic(2021) 16 15.8
A17 Pro(2023) 16 35
A18(2024) 16 35

サードパーティ開発者はANEに直接アクセスするAPIを持ちません。CoreMLを通じて間接的にアクセスする形になっており、CoreMLが内部でオペレータをANEにマッピングするかどうかを決定します。

なぜCoreMLだけでなくExecuTorchと組み合わせるのか

coremltools を使えばPyTorchモデルを直接 .mlpackage 形式に変換してiOSで使うことは可能です。それをしなかった理由は以下の通りです。

ExecuTorch + CoreML(今回の選択)

  • PyTorchのエクスポートパイプライン(torch.export)をそのまま使えます
  • ExecuTorchのAOTメモリプランニングや静的グラフ最適化の恩恵を受けられます
  • CoreMLがサポートしないオペレータが存在しても、ExecuTorchのCPUフォールバックで自動的に処理されます(Partitionerの部分委譲)
  • PyTorchエコシステムからの乖離がなく、将来的に別バックエンドへの移行も容易です

coremltools単体変換の場合

  • CoreMLがサポートしないオペレータがあると変換そのものが失敗するリスクがあります
  • PyTorchのグラフ最適化パイプラインを別途管理する必要があります
  • ExecuTorchの軽量C++ランタイムによる実行時最適化が得られません

ExecuTorchのCoreMLバックエンドは内部でcoremltoolsを使ってEdge DialectをCoreML形式に変換し、それを.pteファイルにバンドルします。つまり「ExecuTorchのパイプライン管理 + CoreML/ANEの実行性能」といういいとこ取りの構成になります。

変換コード

学習済みモデルをCoreMLバックエンドで.pteに変換するスクリプトです。

import torch
import torch.nn as nn
from torchvision import models
from torch.export import export
from executorch.exir import to_edge_transform_and_lower
from executorch.backends.apple.coreml.partition import CoreMLPartitioner

def create_model(num_classes):
    model = models.mobilenet_v3_small(weights=None)
    in_features = model.classifier[3].in_features
    model.classifier[3] = nn.Linear(in_features, num_classes)
    return model

# モデルをCPUにロード(ExportはCPUで行う)
model = create_model(10)
model.load_state_dict(torch.load("best_model.pth", map_location="cpu"))
model.eval()

# ダミー入力(torch.exportのトレース用)
dummy_input = torch.randn(1, 3, 224, 224)

# Step 1: Export
exported = export(model, (dummy_input,))

# Step 2: CoreMLバックエンドに委譲してto_executorchで.pteに変換
et_program = to_edge_transform_and_lower(
    exported,
    partitioner=[CoreMLPartitioner()],
).to_executorch()

with open("abacus.pte", "wb") as f:
    f.write(et_program.buffer)

print("abacus.pte 完成")

iOSデプロイターゲットの指定(推奨)

デフォルトでは最新のCoreML Specification Version(今回はVersion 6)が使われます。これはiOS 16以降でないと動作しない可能性があります。対象デバイスに応じてデプロイターゲットを明示的に指定するのが安全です。

import coremltools as ct
from executorch.backends.apple.coreml.compiler import CoreMLBackend

et_program = to_edge_transform_and_lower(
    exported,
    partitioner=[CoreMLPartitioner(
        compile_specs=CoreMLBackend.generate_compile_specs(
            minimum_deployment_target=ct.target.iOS15
        )
    )],
).to_executorch()

変換ログの読み方

変換を実行すると以下のようなログが出力されます。

Skipping import of cpp extensions due to incompatible torch version 2.10.0 for torchao version 0.15.0
scikit-learn version 1.7.1 is not supported. Minimum required version: 0.17. Maximum required version: 1.5.1. Disabling scikit-learn conversion API.
Torch version 2.10.0 has not been tested with coremltools. You may run into unexpected errors. Torch 2.7.0 is the most recent version that has been tested.
Converting PyTorch Frontend ==> MIL Ops: 99%|...| 189/190
Running MIL frontend_pytorch pipeline: 100%| 5/5
Running MIL default pipeline: 100%| 95/95
Running MIL backend_mlprogram pipeline: 100%| 12/12
WARNING: The model was exported with CoreML specification version 6, and it will not run on all versions of iOS/macOS.
abacus.pte 完成

各メッセージの意味を解説します。

Converting PyTorch Frontend ==> MIL Ops: 189/190
CoreMLの内部表現であるMIL(Model Intermediate Language)へのオペレータ変換が進んでいます。190オペレータのうち189がCoreMLにマッピングされました。残り1つはCPUフォールバックになりますが、MobileNetV3の場合ほぼ全体がCoreMLに委譲されます。

Running MIL pipelines
frontend_pytorch → default → backend_mlprogramと3段階のパイプラインが通過しており、MILの最適化とバックエンド形式への変換が完了しています。これが成功している=CoreMLへの変換が正常に完了したことを意味します。

Torch version 2.10.0 has not been tested with coremltools
coremltoolsの公式テスト対象外のPyTorchバージョンを使っている旨の警告です。動作はしますが、未知のエラーが発生する可能性があるという意味です。今回は変換が完了しているため実害はありません。

specification version 6, will not run on all versions of iOS/macOS
最も重要な警告です。CoreML Specification Version 6はiOS 16以降対応です。サポート対象デバイスがiOS 15以前を含む場合は minimum_deployment_target の指定が必要になります。

結果

バックエンド 推論時間
XNNPACK(CPU) 約10,000ms
CoreML(ANE/GPU) 200〜300ms

約50倍の高速化を達成しました。XNNPACKで10秒かかっていた原因は、MobileNetV3の畳み込み演算がiOSのCPUのみで処理されていたことにあります。CoreMLバックエンドに切り替えることでANEに処理が委譲され、ANEがこうした推論ワークロードに対して桁違いに高いスループットとエネルギー効率を持つことが確認できました。

注意点とまとめ

  • ExecuTorchのCoreMLバックエンドへの変換はmacOS上でのみ実行可能です。Google ColabなどのLinux環境ではCoreML変換ができないため、ローカルのMacで変換スクリプトを実行する必要があります
  • ANEはFP16精度のみをサポートするため、FP32モデルはCoreML側で自動変換されます。数値精度に敏感なタスクでは精度検証が必要です
  • minimum_deployment_target を明示的に設定しないとSpecification Version 6が使われ、iOS 16未満のデバイスで動作しない可能性があります
  • InstrumentsのCore MLテンプレートを使うと、推論が実際にCPU・GPU・ANEのどれで実行されているかを実機で確認できます

ExecuTorchはPyTorchエコシステムを維持しながら、CoreMLのようなプラットフォーム固有の最適化を活用できる柔軟な設計になっています。今回のようにバックエンドをXNNPACKからCoreMLに変更するだけで、コードの変更はPartitionerの一行のみで済みます。iOSでのオンデバイス推論を検討している場合、ExecuTorch + CoreMLバックエンドの組み合わせは有力な選択肢の一つです。

参考文献

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?