オンデバイスAI:MediaPipeとONNX RuntimeによるSLMのデプロイ

AI tutorial - IT technology blog
AI tutorial - IT technology blog

エッジAIの現実:なぜローカルモデルが優れているのか

GPT-4のようなクラウドベースの巨大モデルは強力ですが、モバイルには必ずしも最適ではありません。高レイテンシ、高額なAPIトークン、データプライバシーのリスクにより、サーバーサイドAIが足かせになることも少なくありません。小規模言語モデル(SLM)がこの状況を変えます。GoogleのGemma 2BやMicrosoftのPhi-3.5 Miniといったモデルは、論理的思考力や推論能力を維持しつつ、スマートフォン上で動作するほど軽量です。

これらのモデルをローカルで実行すれば、地下鉄や電波の届かない場所でもアプリが機能し続けます。ユーザーデータがサードパーティのサーバーに送信されることもないため、厳格なプライバシー要件を満たすことができます。何より、月々の推論費用がかかりません. 筆者のテストでは、テキスト要約をクラウドAPIからオンデバイスSLMに移行したところ、最新ハードウェアでの応答レイテンシが2.5秒から400ms未満に短縮されました。

ここで重要な役割を果たすのがMediaPipeとONNX Runtime (ORT)です。MediaPipeはワークフローを簡素化するハイレベルなLLM Inference APIを提供し、ONNX Runtimeはカスタムアーキテクチャに対する高度な柔軟性を提供します。これらを組み合わせることで、AndroidとiOSの両方のエコシステムでGPUやNPUを効果的に活用できます。

開発環境の構築

PyTorchファイルをそのままAndroidプロジェクトに入れることはできません. モバイルハードウェアには特定のフォーマットと最適化が必要です。コーディングの前に、生ウェイトをモバイル向けのバイナリに変換するパイプラインを準備する必要があります。

1. 変換用Python環境

バージョン競合を避けるため、クリーンな仮想環境を使用してください。genaiバンドルツールを使用するために、MediaPipeのPythonパッケージが必要になります。

# 専用の環境をセットアップ
python3 -m venv slm_env
source slm_env/bin/activate

# 変換に必要なツールをインストール
pip install mediapipe torch numpy huggingface_hub

2. プラットフォームの要件

Androidの場合、最小SDKバージョンは24をターゲットにします。NPUによるハードウェアアクセラレーションを利用する場合は、Android 11以降を搭載した比較的新しいデバイスが推奨されます。iOS開発者の場合は、Xcode 15以降が必要です。.binまたは.onnxファイルを用意してください。これらが実際にモバイルアプリで実行される成果物となります。

変換プロセス:巨大なモデルの軽量化

標準的な2B(20億)パラメータのモデルは約5GBの容量を占有します。これはほとんどのスマートフォンにとって重すぎます。解決策は量子化(Quantization)です。ウェイトの精度を16ビットから4ビットに落とすことで、5GBのモデルを約1.2GBまで圧縮できます。これにより、ミドルレンジ端末のRAM制限内でも快適に動作するようになります。

MediaPipe向けのバンドル作成

MediaPipeは特定のバンドル形式を使用します。Hugging FaceのモデルをモバイルAPIが理解できる形式に変換する方法は以下の通りです。

import mediapipe as mp
from mediapipe.tasks.python.genai import bundler

# ソースと出力先
MODEL_PATH = "./gemma-2b-it-pytorch"
OUTPUT_PATH = "gemma_mobile_gpu.bin"

# モバイル向けに最適化されたバンドルを作成
bundler.create_bundle(
    model_path=MODEL_PATH,
    batch_size=1,
    seq_length=512,
    output_filename=OUTPUT_PATH,
    backend="gpu" # モバイルのパフォーマンスにおいて極めて重要
)

Androidへの統合 (Kotlin)

assetsフォルダに.binファイルを配置したら、LlmInferenceクラスを使用してエンジンを起動します。モデルのメモリ読み込み中にUIがカクつくのを防ぐため、必ずバックグラウンドスレッドで初期化を行ってください。

val options = LlmInference.LlmInferenceOptions.builder()
    .setModelPath("/data/local/tmp/gemma_mobile_gpu.bin")
    .setMaxTokens(512)
    .setTemperature(0.7f)
    .build()

val llmInference = LlmInference.createFromOptions(context, options)

// 非同期でテキストを生成
val response = llmInference.generateResponse("種を植えるための3ステップガイドを書いてください。")

パフォーマンスのチューニングとモニタリング

SLMはリソースを大量に消費します。注意深く監視しないと、モバイルOSがメモリを回収するためにプロセスを強制終了してしまいます。スムーズなユーザー体験を確保するために、筆者は3つのKPIを追跡しています。

1. RAMの上限

Android StudioのMemory Profilerを使用しましょう。4ビット量子化された2BモデルのRAM使用量は1.3GB程度に収まるはずです。アプリの使用量が2.5GBに達すると、デバイスで「Out of Memory (OOM)」エラーが発生する可能性が高くなります。その場合は、seq_lengthを256または512に下げて、メモリへの負荷を軽減してください。

2. サーマルスロットリングの管理

長時間のAI推論はかなりの熱を発生させます。Snapdragon 8 Gen 2では、3分間の連続生成後に速度が40%低下するのを確認しました。これを防ぐには、UIを短いインタラクション向けに設計してください。長いエッセイではなく、100トークン未満の回答を目指うのが理想的です。

3. Tokens Per Second (TPS)

ユーザーは、読みやすいペースでテキストが表示されることを期待します。少なくとも10 TPSを目指しましょう。実際の速度は、簡単なタイマーで計算できます。

val start = System.currentTimeMillis()
val output = llmInference.generateResponse(input)
val seconds = (System.currentTimeMillis() - start) / 1000.0
val tps = output.split(" ").size / seconds

Log.i("AI_Perf", "現在の速度: $tps tokens/sec")

速度が5 TPSを下回る場合は、バックエンドの設定を再確認してください。CPUでの実行は、GPUに比べて5倍から10倍遅くなることが一般的です。筆者の経験上、GPUアクセラレーションを強制することが、実用的なモバイルAI機能を構築するための最も重要なステップです。

オンデバイスAIはもはや未来のコンセプトではありません。適切な量子化とMediaPipeエコシステムを活用すれば、場所を問わず動作するスマートでプライバシーに配慮したアプリを構築できます。まずはスモールスタートで始め、発熱を監視し、モデルを軽量に保つことを心がけましょう。

Share: