ailia LLM の QNN バックエンドを使用して、Qualcomm SoC の NPU で LLM / VLM を高速に推論する方法を説明します。
ailia LLM 1.5.0 以降は Qualcomm SoC の NPU(Hexagon)を使用した高速推論に対応しています。NPU 向けには、llama.cpp とは異なるアイリア社独自の実装を搭載しています。
NPU を使用することで、高速・低消費電力に LLM を実行可能です。テキストの LLM だけでなく、画像を入力する VLM(Vision Language Model)や音声入力にも対応し、NPU で処理できます。また、Snapdragon 7s など、ミドルレンジの端末でも Gemma 4 E2B や E4B を実行可能です。
実測値では、Android 端末上で Prefill を最大 4.2 倍高速化し、Prefill 中の CPU 使用率を 1/30 以下に抑えられます(Snapdragon 8+ Gen 1、2,048 トークン入力時の同端末 CPU 比)。
QNN(Qualcomm AI Engine Direct)は Qualcomm が提供するバックエンド API です。ailia LLM では、llama.cpp のモデル形式である GGUF を PC 上で SoC 専用の QNN Model(.qnn)に変換し、端末上の ailia LLM で NPU を使って実行します。端末には GGUF は不要で、QNN Model のみを配置します。
Windows on Arm では QNN 対応の ailia LLM ARM64 ビルドと、端末の SoC に一致する .qnn を使用します。Snapdragon X Plus 実機と QCS6490 実機(Windows 11 IoT Enterprise)で E2B のテキスト・画像・音声パッケージの動作を確認しています。異なる SoC 向けのモデルは流用できません。
現在は Gemma 4 E2B と Gemma 4 E4B(いずれもテキスト・画像・音声入力)に対応しています。画像・音声入力では、テキスト用の QNN Model に加えて、画像 / 音声エンコーダ(mmproj)の QNN Model を使用します。対応するモデルは SoC の Hexagon バージョンによって異なります。
| Hexagon バージョン | SoC の例 | 対応モデル | コンテキスト長 |
|---|---|---|---|
| v68 | QCS6490 | Gemma 4 E2B | 8K |
| v69 | Snapdragon 8 Gen 1 / 8+ Gen 1 | Gemma 4 E2B | 8K |
| v73 以降 | Snapdragon 7s Gen 3、8 Gen 2 以降 | Gemma 4 E2B / E4B | 8K |
QNN Model はモデルと Snapdragon の機種(SoC)ごとに別ファイルになります。変換済みのモデルファイルを提供していますので、対象端末の SoC に対応したファイルをご利用ください。
| ファイル | 内容 |
|---|---|
gemma4-<model>-<soc>.qnn(例: gemma4-e2b-sm8475.qnn) | テキストモデル(Prefill + Decode) |
gemma4-<model>-<soc>-mmproj.qnn(例: gemma4-e2b-sm8475-mmproj.qnn) | 画像 / 音声エンコーダ(mmproj) |
変換済みの Gemma 4 の QNN Model は下記からダウンロードできます。VLM / 音声入力を使用する場合は、テキストモデルと mmproj の両方をダウンロードしてください。E4B は Hexagon v73 以降のみ対応です。
| SoC | 端末の例 | モデル | ファイル | 内容 | サイズ |
|---|---|---|---|---|---|
| sm8475 (Hexagon v69 / FP16) | Snapdragon 8+ Gen 1 | E2B | gemma4-e2b-sm8475.qnn | テキストモデル(Prefill + Decode) | 3.60 GB |
| gemma4-e2b-sm8475-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 1.10 GB | |||
| sm7635 (Hexagon v73 / Int16) | Snapdragon 7s Gen 3 | E2B | gemma4-e2b-sm7635.qnn | テキストモデル(Prefill + Decode) | 3.56 GB |
| gemma4-e2b-sm7635-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 0.63 GB | |||
| E4B | gemma4-e4b-sm7635.qnn | テキストモデル(Prefill + Decode) | 5.54 GB | ||
| gemma4-e4b-sm7635-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 0.64 GB | |||
| sc8380xp (Hexagon v73 / FP16) | Surface(Snapdragon X Plus) | E2B | gemma4-e2b-sc8380xp.qnn | テキストモデル(Prefill + Decode) | 3.61 GB |
| gemma4-e2b-sc8380xp-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 1.09 GB | |||
| E4B | gemma4-e4b-sc8380xp.qnn | テキストモデル(Prefill + Decode) | 5.58 GB | ||
| gemma4-e4b-sc8380xp-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 1.10 GB | |||
| qcs6490 (Hexagon v68 / Int16) | QCS6490 | E2B | gemma4-e2b-qcs6490.qnn | テキストモデル(Prefill + Decode) | 3.60 GB |
| gemma4-e2b-qcs6490-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 0.63 GB |
いずれも ailia LLM 1.5.0(QAIRT 2.47.0.260601)向けで、コンテキスト長は 8192、AR-256 / AR-1 構成です。
ailiaLLMGetQNNModelName API で取得できる名称)を添えて ailia までお問い合わせください。
ailiaLLMGetQNNModelName API で取得できます(例: "sm8475")。
以下はアプリで事前コンパイル済みの .qnn を実行するための設定です。モデルをコンパイルする手順ではありません。QNN ランタイムはモデル生成時と同じ QAIRT 2.47.0.260601 を使用してください。
Qualcomm AI Engine Direct SDK から QAIRT 2.47.0.260601(配布 ZIP)を取得します。取得に Qualcomm アカウントが必要な場合があります。配布ファイルの場所と Hexagon バージョンの選び方は ailia SDK の QNN セットアップを参照してください。Snapdragon X Plus(Hexagon v73)の例では、SDK 内の lib/aarch64-windows-msvc にある QNN DLL と、lib/hexagon-v73/unsigned にある NPU 側の .so および .cat を使用します。
QNN 対応の Windows ARM64 版では、ailia_llm.dll と ailia_llm_qnn.dll、共有 GGML/llama DLL、SDK の QNN DLL、NPU 側の .so と .cat をアプリから参照できる場所に配置します。Qualcomm NPU ドライバも必要です。ailia SDK 用の ailia_qnn と ailia LLM 用の ailia_llm_qnn は別のプラグインです。ビルドと配置の詳細は Windows ARM64 QNN 手順を参照してください。
Android プロジェクトの app/src/main/jniLibs/arm64-v8a/ に、配布物の libailia_llm.so、libailia_llm_qnn.so、同梱された libggml*.so / libllama*.so と CPU variant、および対象 Hexagon 世代の QNN Stub / Skel を配置します。libailia_llm.so をロードすると、QNN プラグインは必要時に遅延ロードされます。SDK の再配布条件を確認してください。
app/src/main/AndroidManifest.xml の <application> に FastRPC の宣言と native library の抽出を追加します。Skel が実ファイルとして見える必要があります。
<application android:extractNativeLibs="true" ...>
<uses-native-library android:name="libcdsprpc.so" android:required="false" />
</application>
app/build.gradle の dependencies に、同じ世代の QNN ランタイムを追加します。
dependencies {
implementation 'com.qualcomm.qti:qnn-runtime:2.47.0'
}
.qnn は assets 内のパスを直接指定できません。初回起動時にアプリのプライベートストレージへコピーするかダウンロードし、その実ファイルパスを以下の API に渡してください。
Android 向けの評価版 APK は ailia-models-kotlin-qnn.apk です。
Windows on Arm 向けの評価版アプリは ailia-models-flutter-qnn-v261008.zip です。Snapdragon X Plus と QCS6490 で動作します。
まず SoC 名(例: sm7635)を取得し、同じ SoC 用にコンパイルした gemma4-e2b-<soc>.qnn などのテキストモデルを選びます。返る文字列はファイル名全体ではなく SoC 名です。n_ctx=0 はパッケージに埋め込まれたコンテキスト長を選択します。モデル未公開の SoC に別 SoC 用のファイルを流用しないでください。画像・音声入力には、テキストモデルを開いた後、同じモデル・SoC の -mmproj.qnn を通常のプロジェクタ API で追加します。
以下は Android のテキストモデル例です。モデルファイルは前述のプライベートストレージ内にあるものとします。Windows on Arm の QNN 対応ビルドでも同じ API を使用し、ファイルパスを Windows の配置先に置き換えます。QNN プラグインがない場合は UNIMPLEMENTED、未対応 SoC の場合は OTHER_ERROR になります。
#include "ailia_llm.h"
#include <stdio.h>
/* model_dir: アプリのプライベートストレージ。成功時は呼び出し側が Destroy する。 */
int open_qnn_model(const char *model_dir, struct AILIALLM **out) {
if (model_dir == NULL || out == NULL) return AILIA_LLM_STATUS_INVALID_ARGUMENT;
*out = NULL;
const char *soc = NULL; /* ライブラリ所有。解放しない */
int status = ailiaLLMGetQNNModelName(&soc);
if (status != AILIA_LLM_STATUS_SUCCESS) return status;
char path[512];
int n = snprintf(path, sizeof(path), "%s/gemma4-e2b-%s.qnn", model_dir, soc);
if (n < 0 || (size_t)n >= sizeof(path)) return AILIA_LLM_STATUS_INVALID_ARGUMENT;
struct AILIALLM *llm = NULL;
status = ailiaLLMCreate(&llm);
if (status != AILIA_LLM_STATUS_SUCCESS) return status;
status = ailiaLLMOpenModelFileA(llm, path, 0);
if (status != AILIA_LLM_STATUS_SUCCESS) { ailiaLLMDestroy(llm); return status; }
*out = llm;
return AILIA_LLM_STATUS_SUCCESS;
}
from pathlib import Path
import ailia_llm
soc = ailia_llm.AiliaLLM.get_qnn_model_name()
model_path = Path("/data/user/0/your.package/files") / f"gemma4-e2b-{soc}.qnn"
llm = ailia_llm.AiliaLLM()
llm.open(str(model_path), n_ctx=0)
import 'package:ailia_llm/ailia_llm_model.dart';
final soc = AiliaLLMModel.getQNNModelName();
final model = AiliaLLMModel();
model.open('/data/user/0/your.package/files/gemma4-e2b-$soc.qnn', 0);
// 使用後は model.close();
context は Android の Activity または Application の Context です。
import axip.ailia_llm.AiliaLLM
val soc = AiliaLLM.getQNNModelName()
val llm = AiliaLLM()
val modelPath = "${context.filesDir}/gemma4-e2b-$soc.qnn"
llm.openModelFile(modelPath, 0)
// 使用後は llm.close()
using System;
using System.IO;
using UnityEngine;
using ailiaLLM;
string soc = AiliaLLMModel.GetQNNModelName();
var llm = new AiliaLLMModel();
if (!llm.Create()) throw new Exception("ailiaLLMCreate failed");
string modelPath = Path.Combine(Application.persistentDataPath, $"gemma4-e2b-{soc}.qnn");
if (!llm.Open(modelPath, 0)) throw new Exception(llm.GetErrorDetail());
// 使用後は llm.Dispose();
QNN Model は Prefill 用の固定長グラフ(AR-N)と Decode 用のグラフ(AR-1)を同じ QNN Context に格納し、重みと KV キャッシュを共有します。重みは変換時に 4bit(W4)に量子化され、FP16 モデルでは活性値と KV キャッシュは FP16 で処理されます。
ailia LLM Compiler は FP16 と Int16 の両方に対応しており、対象の SoC に応じて変換時に切り替えられます。Snapdragon 7s Gen 3 の NPU は Hexagon v73 ですが、FP16 に対応していないため、ailia LLM では Int16 に量子化したモデルを使用します。Qualcomm の Snapdragon 7s Gen 3 製品資料(PDF、2 ページの Artificial Intelligence)では、NPU の対応精度として INT4、INT8、INT16 が記載されており、FP16 は含まれていません。Hexagon のバージョンだけでなく、SoC ごとの対応精度に合わせてモデルを選択してください。
ailia LLM は QNN Model を mmap で読み込み、QNN Context を復元して Hexagon NPU(HTP)上で実行します。Prefill は固定長の AR-N グラフで処理し、プロンプトが AR の倍数でない場合は最後のスライスをパディングします。Decode は同じ Context 内の AR-1 グラフで処理し、Prefill 後の KV キャッシュのコピーは発生しません。VLM では画像のパッチ化と位置埋め込みを CPU で行い、16 層の Vision Transformer からプーリング、プロジェクションまでを 1 つの QNN グラフで実行します。音声入力には Audio グラフを使用し、音声エンコーダを NPU で実行します。また、層別トークン埋め込み(PLE)のフロントエンドも各 Transformer グラフ内で実行し、CPU 側にはトークン行の参照とメディア埋め込みの差し替えのみを残しています。これにより、入力処理を CPU で行う場合に比べて Int16 モデルの Prefill が改善します。
Snapdragon 8+ Gen 1(SM8475)、Snapdragon 7s Gen 3(SM7635)、Snapdragon X Plus(SC8380XP)、QCS6490 で Gemma 4 E2B / E4B を測定した Decode / Prefill スループット、Prefill 中の CPU 使用率、VLM・音声入力の応答時間、メモリ使用量は ailia LLM NPU(QNN)ベンチマーク を参照してください。
Android の Qualcomm SoC で LLM を実行する主な手法と ailia LLM の比較です。ailia LLM は NPU を使用でき、Gemma 4 を NPU で実行でき、Hexagon v69 以降の比較的古い SoC にも対応します。
| 手法 | NPU 対応 | Gemma 4 対応 | 対応 SoC(Android) | 備考 |
|---|---|---|---|---|
| llama.cpp | × | ○ | 制約なし(CPU 実行) | CPU 実行です。実験的な Hexagon バックエンドがありますが、NPU 側のライブラリ(Skel)が署名されていないため一般の端末では実行できません。画像エンコーダは CPU で実行されます。 |
| QNN(Qualcomm AI Engine Direct) | ○ | × | Hexagon v65 以降(Snapdragon 845 以降) | 低レベルの推論 API で、LLM のランタイムは提供されません。 |
| QNN GenAI(Qualcomm Genie / GenieX) | ○ | × | Hexagon v79 以降(Snapdragon 8 Elite 以降) | 公式の対応 Android SoC は Snapdragon 8 Elite 系のみで、Hexagon v79 以降が必須です。Gemma 4 の NPU 用(QAIRT)モデルは提供されていません。 |
| LiteRT-LM NPU | ○ | × | Snapdragon 8 Gen 2 / 8 Gen 3 / 8 Elite(SM8550 / SM8650 / SM8750) | Qualcomm 向けに公開されている NPU モデルは Gemma3-1B(4bit、コンテキスト長 1,280)のみです。Gemma 4 の NPU モデルは Google Tensor と Intel 向けにのみ提供されています。テキスト専用で、画像・音声入力には対応していません。 |
| ailia LLM | ○ | ○ | Hexagon v69 以降(Snapdragon 8 Gen 1 以降、7s Gen 3 など) | Snapdragon 8+ Gen 1 などの古い SoC でも NPU を使用でき、Gemma 4 E2B / E4B をテキスト・VLM・ALM とも NPU で実行できます。Int16 への量子化に対応しており、FP16 に非対応の Snapdragon 7s でも実行可能です。 |