Neural EngineでCore MLの処理が行われているか調べる方法

Neural Engine（ニューラルエンジン）は、ニューラルネットワークの推論演算に特化した機構を持つことで、**GPUよりもさらに高速な処理と消費電力の低下**を両立するプロセッサ。A11 Bionicは毎秒6000億回、A12 Bionicではなんと毎秒5兆回の演算が可能とされている。

いうまでもなくCore MLのポテンシャルを最大限引き出す鍵となるのがこのNeural Engineなのだが、実は**Neural Engineを制御するフレームワークやAPIは存在せず[^api]、Appleは開発者向けの情報もほとんど何も出してない**。

[^api]: 唯一、[「Neural Engineを**使わない**」という指定だけはできる](https://developer.apple.com/documentation/coreml/mlcomputeunits)。Neural Engineを使うという指定はできない。

残念ながら先のWWDC 2020でもその状況はあまり変わらなかった。

そんな中、書籍「Core ML Survival Guide」やOSS「CoreMLHelpers」でCore ML界隈で知らない人はいないMatthijs Hollemans氏が、GitHubでNeural Engine（ニューラルエンジン）のドキュメントのリポジトリを開設した。

https://github.com/hollance/neural-engine

氏がNeural Engineについて現状把握していることをまとめてくれたのが本リポジトリ。公開APIはない中で、プライベートフレームワークを利用してモデルの処理においてNeural Engineが使用されているか（あるいは使用されておらずCPUやGPUで処理されているか）を調べる方法や、そういった手法を用いてどういった種類のレイヤーはANEで処理されないか、といった非常に有益な情報が書かれている。本記事はそれを読んだメモ。

なお、頻出する**「ANE」はApple Neural Engineの略**。Neural Engineを扱うプライベートフレームワークのプレフィックスがこれになっているらしい。（確かにCore MLを使っているとANEなんとかは目にすることが度々あった）

## ANEが使用されているかを調べる方法1

> **Tip:** Press the Pause button in the debugger. If there's a thread **H11ANEServicesThread** then Core ML is using the Neural Engine.


Xcodeにつないでデバッグ中にポーズボタン（⏸）を押してスレッド一覧の中に"H11ANEServicesThread"という名前のスレッドがあれば、Core MLはNeural Engineを使用していると判断できる。（前述の通りANEはApple Neural Engineの略なので）

## ANEが使用されているかを調べる方法2

シンボリック・ブレークポイント（symbolic breakpoint）を使用すると、CPU, GPU, ANEのうちどのプロセッサが使用されているかを知ることができる。

たとえば`-[_ANEModel program]`にシンボリック・ブレークポイントを張ってそこで止まれば、Core MLはANEを使用している。

ちなみに氏がドキュメントの中で何度も口を酸っぱくして書いてるのは、『だからといって、モデルの処理がすべてANEで処理されているとは限らない』ということ。一部のレイヤーだけANEで処理されていて、ほかはCPUやGPUで処理されているのかもしれない、という話。

またCore MLはEspressoというプライベートフレームワークで以下の3つの「エンジン」実装を使用しているとのこと。

> - ANE — `Espresso::ANERuntimeEngine`
- GPU — `Espresso::MPSEngine`
- CPU — `Espresso::BNNSEngine`

スタックトレースにこれらの名前があれば、現在どのプロセッサで処理されているかが判断できる。

また次のようにシンボリック・ブレークポイントを貼れば、CPU・GPUで処理が行われていることを発見できる。

> - `Espresso::MPSEngine::context::__launch_kernel`
- `Espresso::BNNSEngine::convolution_kernel::__launch`

これでうまくいかなければ、以下のどちらかをブレークポイントに使用して、コードをステップ実行で追っていく。

> - `Espresso::layer::__launch`
- `Espresso::net::__forward`

`Espresso::elementwise_kernel_cpu::__launch`のような関数で終われば、それはCPUで実行中というヒントとなる。

## Espressoフレームワークのシンボルリストを取得する

Espressoフレームワークの他のシンボルも知りたければ、アプリ実行中にブレークポイントでアプリを止め、LLDBで次のコマンドを実行する。

```
(lldb) image list Espresso
```

これを実行するとEspress frameworkへのパスがprintされるので、そのパスを用いて次のようにdumpを実行する。

```
(lldb) image dump symtab 'put-the-path-here'
```

## ANEがサポートしていないレイヤー

以下のタイプのレイヤーはANEでは処理されないことがわかっている。

> - RNN layers such as LSTM or GRU
- dilated convolutions
- upsampling layers
- broadcastable and "ND" layers
- custom layers（・・・ANEのAPIが公開されていないので当然）


各タイプの詳細は`unsupported-layers.md`を参照のこと。（ここに書くとほとんどの内容を引用することになってしまうので。）

ちなみにすべてのレイヤータイプを試したわけではないので上記は網羅的なリストではないよとのこと。

## coremltoolsでモデルを変換する際の注意点

上に挙がっている"Broadcastable layers"はANEで動作しないが、実はCore ML 2 の古いレイヤタイプと置き換えることができるらしい。そして古い方はANEで処理される。

しかしcoremltoolsでモデルを変換する際に、

```
minimum_ios_deployment_target='13'
```

と最新バージョンを指定すると、コンバータは新しい"Broadcastable layers"の方（ANEで処理されない方）を使用する傾向にあるらしい。

そういった場合にcoremltoolsを使って互換性のある古いバージョンのものと置き換えるといいよというTipsが紹介されている。

> - `AddBroadcastableLayer` → `AddLayer`
- `MultiplyBroadcastableLayer` → `MultiplyLayer` or `ScaleLayer` or a linear `Activation` layer
- `ConcatND` → `Concat`
- and so on...

また`SubtractBroadcastableLayer`みたいに互換性のある古いバージョンのものがない場合はワークアラウンドとして別のレイヤーを使って同様のレイヤー機能を実装するといいよ的なことが書かれている。

## その他

なんだかわからないが、重みを16ビット浮動小数点で保存したmlmodelファイルを使うと、32ビットモデルを使う場合よりも遅くなるらしい。GPUでは常に16ビットで扱うので個人的に最近は16ビットでいいやと思っていたのでこれは要注意。