以下は、Pytorch と Tensorflow でシンプルなモデルをトレーニングし、i.MX93 Ethos-65 ニューラル プロセッシング ユニット (NPU) を使用してアプリケーションにデプロイするためのガイドです。
このガイドに従うと、次のことが達成されます。
このガイドに従うには次のものが必要です:
アプリケーションの実装は Python と C++ の両方で提供されます。Pythonアプリケーションを使用する場合は、代わりに事前に構築された完全なイメージを使用できます。Python スクリプトをターゲットにコピーして、次のように実行するだけです。
# Running quantized example on the CPU
./run.py -m cnn_tf_quant.tflite
# Running example on the Ethos NPU
./run.py -m cnn_tf_quant_vela.tflite -d /usr/lib/liblitert_ethosu_delegate.so
添付ファイルには事前に構築されたモデルが提供されていますが、モデルのトレーニングと生成に使用される手順とスクリプトも含まれています (以下を参照)。
デモンストレーションに使用される GUI アプリケーションはGTKMM3 ( GTKライブラリの C++ ラッパー) で記述されているため、GTKMM3 をサポートするイメージが必要ですが、幸いなことに、これを Yocto イメージに簡単に統合できるレシピが既にあります。
イメージをビルドするには、 Yocto ユーザー ガイドの指示に従うだけです。この記事の執筆時点では最新の BSP は 6.12.49_2.2.0 なので、これを使用します。
ホストとインストールされたリポジトリですべての要件を設定したら、次のようにビルド環境を設定できます。
リポジトリ init -u https://github.com/nxp-imx/imx-manifest -b imx-linux-walnascar -m imx-6.12.49-2.2.0.xml
リポジトリ同期
ターゲットに応じてビルド ディレクトリを設定できるようになりました。例として、X11 をサポートする Wayland グラフィックスとiMX93 Freedomボードを使用します。
ディストリビューション=fsl-imx-xwayland マシン=imx93-11x11-lpddr4x-frdm ソース imx-setup-release.sh -b 93-frdm-xwayland
ボードに合った MACHINE 構成を選択するだけです。
これでビルドを開始する準備がほぼ整いました。まだイメージに GTKMM3 サポートを追加する必要があります。conf/local.conf の下にある local.conf ファイルを変更し、次のコードを追加するだけです。
IMAGE_INSTALL:append = "gtkmm3"
ビルド時に問題が発生しないように、gtkmm3 の前にスペースがあることを確認してください。ビルドはリソースを大量に消費するため、ビルド中にメモリ不足の問題が発生する可能性があります。一度にビルドを試みる同時レシピの数を制限するために、以下も追加することをお勧めします。
BB_NUMBER_THREADS="8"
PARALLEL_MAKE="-j8"
BB_PRESSURE_MAX_CPU ?= "50000"
BB_PRESSURE_MAX_IO ?= "100000"
BB_PRESSURE_MAX_MEMORY ?= "25000"
その後、local.conf は次のようになります。
注意:ビルドを完了するには 500 GB 以上必要なので、マシンに十分なストレージ容量があることを確認してください。
これでビルドを開始できます。GTKMM アプリケーションをソースからビルドする場合は、利用可能な SDK が必要であり、次のように作成します。
bitbake imx-image-full -c populate_sdk
画像を作成するには、次のようにするだけです。
bitbake imx-image-full
すべての Tensorflow Lite ライブラリとさまざまな例が含まれているため、完全なイメージが必要です。
ビルドが完了したら、ツールチェーンをインストールし、イメージをボードにフラッシュできます。
ツールチェーンをインストールするには:
./tmp/deploy/sdk/fsl-imx-xwayland-glibc-x86_64 -imx-image-full-armv8a-imx93-11x11-lpddr4x-frdm-toolchain-6.12-walnascar.sh
その後、ツールチェーンを使用するたびに次の操作を実行します。
ソース /opt/fsl-imx-xwayland/6.12-walnascar-full-gtkmm3/environment-setup-armv8a-poky-linux
イメージを SD カードにフラッシュするには:
zstdcat imx-image-full-imx93-11x11-lpddr4x-frdm.rootfs.wic.zst | sudo dd of=/dev/mmcblk0 bs=1M conv=fsync
これで、アプリケーションを構築し、いくつかのモデルをトレーニングしてデプロイする準備が整いました。
アプリケーションのソースはここにあります。また、ビルド済みのバイナリも提供されており、ここに添付されています。
アプリケーションには、マウスまたはタッチ ディスプレイを使用して数字を簡単に描画できる描画領域と、描画領域をクリアするためのボタンと、モデルの実行をトリガーして数字を予測するためのボタンの 2 つのボタンが含まれています。
最初からビルドするには、CMake と、GTKMM3 をサポートするツールチェーン (上記参照) が必要です。プロジェクトをビルドするには、次の手順に従ってください。
sudo apt install cmake
git クローンhttps://github.com/ManRod2982/drawing_window_imx
cd drawing_window_imx/drawing_window_cpp/
ソース /opt/fsl-imx-xwayland/6.12-walnascar-full-gtkmm3/environment-setup-armv8a-poky-linux
cmake -B ビルド -DCMAKE_TOOLCHAIN_FILE=$OECORE_NATIVE_SYSROOT/usr/share/cmake/OEToolchainConfig.cmake
cmake --build ビルド
この後、ビルド ディレクトリの下に window というバイナリが作成され、それをターゲットの SD カードに簡単にコピーできるようになります。
Linux を使用している場合はファイルシステムがマウントされるので、バイナリをルート ディレクトリにコピーするだけです。
sudo cp build/window /media/user/root/root/
ボードへの接続がすでに確立されている場合も SCP を使用できます。
scp ビルド/ウィンドウ root@192.168.xx:/root
そして、この後、ターゲット上でアプリケーションを次のように起動できます。
./window -m model_path [オプション] -d delegate_path [オプション] -v
アプリケーションでは次の 3 つのパラメータが受け入れられます。
ここで、実行するモデルが必要です。
このリリースの機械学習ユーザーガイドを見てみましょう。各デバイスで利用可能なコンピューティング エンジンに関するさまざまなフレームワークのサポートは次のとおりです。
Tensorflow Lite とLiteRT (Tensorflow Lite の最新リリースであり、今後リリースされる唯一のもの) は、i.MX9 ファミリのほとんどのコンピューティング エンジンで広くサポートされているフレームワークです。このガイドでは、例として C++ を使用し、LiteRT の現在のリリースでは Python のみがサポートされているため、Tensorflow Lite を使用しますが、インターフェースとプロセスはほぼ同じです。
サンプル リポジトリには、モデルをトレーニングして tflite 形式に変換するために使用されるさまざまな Python スクリプトが含まれています。次の手順を実行するには、python3 のインストールが必要です。
仮想環境を設定することをお勧めします。
python3 -m venv myenv
ソース myenv/bin/activate
pip インストール -r 要件.txt
これにより、Tensorflow と Pytorch の両方に必要なすべてのパッケージがインストールされます。
Tensorflow を使用すると、モデルを量子化して Tensorflow Lite に変換するための簡単なパスが可能になり、畳み込みニューラル ネットワーク (CNN) アーキテクチャは次のようになります。
model = tf.keras.models.Sequential([
tf.keras.layers.Input(batch_shape=(1, 28, 28, 1)),
tf.keras.layers.Conv2D(16, 5, padding='same', activation='relu'),
tf.keras.layers.Conv2D(32, 3, activation='relu'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.MaxPool2D(2, strides=(2,2)),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(100, activation='relu'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10, activation='softmax')
])
train_tf.pyスクリプトを実行してモデルをトレーニングすることができます。通常のノートPCでトレーニングするには約 2 分かかり、テスト データセットで 99.05% の精度を達成します。フレームワークの詳細については、公式のTensorflow ドキュメントを参照してください。
スクリプトを実行した後、 eIQ ツールキットモデル ビジュアライザーまたはNetron.app を使用してモデルを視覚化できます。
i.MX93 には、重み、バイアス、入力が整数である必要があるArm Ethos-65 NPUが搭載されており、現在のモデルは float32 を使用しているため、モデルを量子化する必要があります。これを実現するには、モデルを量子化して tflite に変換する tf2quant_tflite.py を実行します。
整数の入力と出力を取り、重みとバイアスも量子化されており、ファイル サイズの違いが簡単に確認できます。
量子化モデルは 555 KB ですが、float32 モデルは 2.2 MB です。これは、float32 では重みとバイアスをそれぞれ保存するのに 4 バイトが必要なのに対し、量子化モデルでは 1 バイトしか必要ないからです。
これで、ターゲットで使用できるモデルができました。ただし、現状では、XNN デリゲートを使用して CPU 上で実行されます。モデルを実行するには、次のようにするだけです。
./window -m cnn_tf_quant.tflite
これで、Arm Ethos NPU 用に量子化モデルをコンパイルできるようになりました。eIQ ツールキットが使用されます。モデル ツールを使用してモデルを開きます。
量子化されたモデル(この場合は cnn_tf_quant.tflite)があるフォルダーに移動して開きます。モデルを視覚化できるはずです。オプション メニューをクリックして変換を選択します。
i.MX93 コンバータを選択すると、保存先フォルダも選択するように求められます。
宛先フォルダを選択した後、すべてがうまくいけば変換が完了し、Ethos で実行できるように最適化されたモデルを視覚化できるようになります。NPU でサポートされていない操作はすべて CPU によって表示および実行されます。この単純な例では、すべての操作は NPU によって実行されます。
そして、次のようにしてターゲット上でモデルを実行できるようになりました。
./window -m cnn_tf_quant_vela.tflite -d /usr/lib/libethosu_delegate.so
リポジトリには、畳み込みニューラル ネットワークを使用して MNIST データ セットをトレーニングするサンプル モデルが含まれています。モデル構造は次のとおりです。
NeuralNetwork(
(cnn): Sequential(
(0): Conv2d(1, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): ReLU()
(2): Conv2d(16, 32, kernel_size=(3, 3), stride=(1, 1))
(3): ReLU()
(4): Dropout(p=0.2, inplace=False)
(5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(6): Flatten(start_dim=1, end_dim=-1)
(7): Linear(in_features=5408, out_features=100, bias=True)
(8): ReLU()
(9): Dropout(p=0.2, inplace=False)
(10): Linear(in_features=100, out_features=10, bias=True)
)
)
Pytorch モデルは、Tensorflow lite (量子化なし) に簡単に変換して CPU 上で実行できます。また、 Open Neural Network Exchange モデル (ONNX)も同様です。ただし、最近リリースされたExecutorchは組み込みデバイス上の Pytorch モデル用の推論モデルであり、現在そのサポートが進められています。
Pytorch モデルは pytorch_model.py で定義されています。
#!/usr/bin/env python3
import torch
from torch import nn
# Define model
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.cnn = nn.Sequential(
# Input 28x28x1, after padding 32x32x1, output 28x28x16
nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5, padding=2),
nn.ReLU(),
# Input 28x28x16, output 26x26x32
nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3),
nn.ReLU(),
nn.Dropout(p=0.2),
# Input 26x26x32, output 13x13x32
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(13*13*32, 100),
nn.ReLU(),
nn.Dropout(p=0.2),
nn.Linear(100, 10)
)
def forward(self, x):
logits = self.cnn(x)
return logits
トレーニングは train_pytorch.py を実行して実行され、テスト データセットで 99.3% の精度を達成し、通常のノートPCで完了するまでに約 7 分かかります。フレームワーク自体とトレーニング プロセスの詳細については、公式のpytorch ドキュメントを参照してください。
その後、pytorch モデルは pytorch_model.pth に保存されますが、pytorch はグラフ情報ではなく重みとバイアスのみを保存します。保存したモデルをnetronまたはeIQ ツールキットモデル ビジュアライザーで視覚化すると、分離された重みとバイアスを観察できます。
モデルをより良く視覚化するために、スクリプトpytorch2onnx.pyを使用してONNX形式に変換するだけです。そして、Neutron 上でモデルのグラフを視覚化できるようになりました。
注: ONNX は量子化して量子化されたモデルを tflite に変換する方法も提供している可能性がありますが、onnx-tf のテストではツールが最新の Tensorflow フレームワークと同期していないようでした。Tensorflow で同様のモデルを作成し、量子化してエクスポートする方が簡単でした。
これでモデルを tflite にエクスポートできるようになりました。モデルは量子化されていないため、CPU (XNN デリゲート) 上で実行されます。エクスポートするには、 pytorch2tflite.py を実行し、エクスポートされたモデルを視覚化できるようになりました。
そして、次のようにしてこのモデルをターゲット上で実行できます。
./window -m pytorch_cnn.tflite
これで、数字を描画できるアプリケーションと、それらの数字を検出できるモデルができましたが、アプリケーションはそのモデルを実行して結果を取得できる必要があります。これが次のステップです。
ターゲット上でモデルを実行できるようにするには、次のことが必要です。
ここでは最小限の例が提供されていますが、NPU でモデルを実行するために必要な外部デリゲートの読み込みは含まれていません。
必要なヘッダーは次のとおりです。
#include "tensorflow/lite/delegates/external/external_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/interpreter_builder.h"
#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/model_builder.h"
TFLite APIを使用して次のようにモデルをロードできるようになりました。
std::unique_ptr<:flatbuffermodel> model =
tflite::FlatBufferModel::BuildFromFile(model_path);ここでインタープリターを作成する必要があります。そのためには、モデルだけでなく操作リゾルバーも必要です。
tflite::ops::builtin::BuiltinOpResolver resolver;
std::unique_ptr<:interpreter> interpreter;
tflite::InterpreterBuilder(*model, resolver)(&interpreter);
デリゲートが必要な場合は、それを作成して実行グラフを更新し、インタープリターがサポートされている操作でデリゲートを呼び出すことを認識できるようにする必要があります。
// Create external delegate option and pass the delegate library
TfLiteExternalDelegateOptions external_delegate_options =
TfLiteExternalDelegateOptionsDefault(delegate_path);
// Create the External Delegate. This will load the delegate.
TfLiteDelegate *external_delegate =
TfLiteExternalDelegateCreate(&external_delegate_options);
// Add External Delegate into TFLite Interpreter to automatically delegate nodes.
if (interpreter->ModifyGraphWithDelegate(external_delegate) != kTfLiteOk) {
std::cerr << "Failed to add delegate" << std::endl;
}
これで、モデルにテンソルを割り当てることができます。
// Allocate tensors for the model
if (interpreter->AllocateTensors() != kTfLiteOk) {
std::cerr << "Failed to allocate tensors" << std::endl;
}
これで、モデルを使用して推論を実行する準備が整いました。
最後のステップは、入力バッファにデータを入力し、インタープリタを呼び出して、出力バッファから結果を取得することです。次の例では、float モデルを使用しています。
// Fill input buffers
// Note: The buffer of the input tensor with index `i` of type T can
// be accessed with `T* input = interpreter->typed_input_tensor(i);`
float *input_tensor = interpreter->typed_input_tensor(0);
std::memcpy(input_tensor, input.data(), input.size() * sizeof(float));
// Run inference
if (interpreter->Invoke() != kTfLiteOk) {
std::cerr << "Failed to invoke Interpreter!" << std::endl;
return {};
}
// Read output buffers
// Note: The buffer of the output tensor with index `i` of type T can
// be accessed with `T* output = interpreter->typed_output_tensor(i);`
float *output_tensor = interpreter->typed_output_tensor(0);
std::memcpy(output, output_tensor, output.size() * sizeof(float));
私たちのサンプル アプリケーションでは、インタープリターの作成と推論の呼び出しは NnModel というクラスにラップされています。その実装はリポジトリで確認できますが、変更なしで float モデルと int8 モデルの両方を処理できます。
クラスはメイン ルーチン内でインスタンス化され、予測ボタンがクリックされるたびに推論が呼び出されます。
// Create model with parsed parameters
NnModel nn(model_path, delegate_path, verbose);
void Window::on_predict_clicked() {
// Save screen to file
std::cout << "Predict clicked!" << std::endl;
// Call inference on NnModel depending on the type
// the model expects
int number;
auto data_type = nn_.get_dtype();
switch (data_type) {
case kTfLiteFloat32: {
std::vector drawing =
mouse_drawing.export_to_vector(28, 28, 255.0);
std::vector output_vec_f = nn_.infer(drawing);
number = get_max_index(output_vec_f);
break;
}
case kTfLiteInt8: {
std::vector drawing =
mouse_drawing.export_to_vector(28, 28, 255.0);
std::vector output_vec_int = nn_.infer(drawing);
number = get_max_index(output_vec_int);
break;
}
default:
std::cerr << "Cannot handle input type: " << std::to_string(data_type)
<< std::endl;
break;
}
std::string display = "You drew a: " + std::to_string(number);
std::cout << display << std::endl;
text_view.set_text(display);
}
Python でインタープリターを作成するプロセスはほぼ同じですが、デリゲートが使用されている場合はそれをロードし、モデルをロードしてテンソルを割り当てる必要があります。
この例では、代わりに LiteRT が使用されていますが、API は同じままで、必要な変更は解釈されたものがどこからインポートされるかだけです。
次の最小限のコードを使用して、モデルと外部デリゲートを読み込むことができます。
from ai_edge_litert.interpreter import Interpreter
# Create interpreter
if delegate_path is not None:
# attempt to load external delegate if provided (platform specific)
try:
from ai_edge_litert.interpreter import load_delegate
delegate = load_delegate(delegate_path)
self.interpreter = Interpreter(model_path=model_path, experimental_delegates=[delegate])
except Exception as e:
raise RuntimeError(f"Failed to load delegate: {e}")
else:
self.interpreter = Interpreter(model_path=model_path)
self.interpreter.allocate_tensors()
これで使用できるインタープリターができました。入力テンソルを入力し、インタープリターを呼び出して、モデルからの結果を含む出力テンソルを取得するだけです。
# Set input
input_details = self.interpreter.get_input_details()[0]
self.interpreter.set_tensor(input_details['index'], input_data)
# Run inference
self.interpreter.invoke()
# Get results
out_details = self.interpreter.get_output_details()[0]
output_data = self.interpreter.get_tensor(out_details['index'])
これらの手順は、 nn_model.pyの下のラッパー クラスに含まれています。
リリースには、ランダムな入力を生成し、モデルで推論を実行するのにかかる時間を測定するための事前構築されたベンチマーク ツールが付属しています。以下は、i.MX93 でさまざまなモデルを実行した結果です。
./benchmaark_model --graph=モデル --num_threads=コア数
| CPU 1コア | CPU 2コア | NPU | |
| pytorch_cnn.tflite | 1559.61米ドル | 1023.22米ドル | N/A |
| cnn_tf_quant.tflite | 585.37米ドル | 379.69米ドル | N/A |
| cnn_tf_quant_vela.tflite | N/A | N/A | 221.84米ドル |
これはもちろん単なる例ですが、専用ハードウェア上で実行すると推論速度が大幅に向上することがわかります。
以下は、Pytorch と Tensorflow でシンプルなモデルをトレーニングし、i.MX93 Ethos-65 ニューラル プロセッシング ユニット (NPU) と i.MX95 eIQ Neutron NPU を使用してアプリケーションにデプロイするためのガイドです。