ailia LLM 1.5.1 で対応した MTP を使用して、Gemma 4 のトークン生成(Decode)を高速化する方法を説明します。
このページは ailia LLM NPU(QNN)ガイド からの差分として、MTP に必要なモデルと API を説明します。NPU のセットアップ(QNN ランタイムの配置、Android の設定、SoC 名の取得)は QNN ガイドと共通です。測定結果は ailia LLM MTP ベンチマーク を参照してください。
LLM は通常、1 回の推論で 1 トークンずつ文章を生成します。次のトークンは直前までのトークンに依存するため、1,000 トークンを生成するには大きな本体モデルを 1,000 回、順番に実行する必要があります。NPU は多くの行を同時に計算するのが得意ですが、1 トークンずつの処理では重みの読み出しが支配的になり、その性能を活かしにくくなります。
MTP(Multi-Token Prediction)は、小さな Assistant(ドラフトモデル) で次の数トークンを先に予想し、大きな本体モデルでその予想をまとめて検証する仕組みです。ailia LLM では、Assistant が 3 トークンの候補を作り、本体は「確定済みの 1 トークン + 候補 3 トークン」の 4 行を 1 回の推論(AR-4 グラフ)で評価します。本体の予測と一致した候補はそのまま採用し、最初に食い違った位置では本体の予測したトークンを採用します。
候補がすべて外れても、本体が予測した 1 トークンは必ず確定します。予想が当たるほど本体の実行回数が減り、生成が速くなります。生成されるトークンは本体の予測に基づいて決まるため、MTP は出力の品質を落とさずに速度を上げる手法です(ただし、AR-4 と AR-1 の数値誤差により、通常生成と完全に同じトークン列になるとは限りません)。temperature を設定したサンプリングでは、本体と Assistant の確率比で候補を受理する方式(Speculative Sampling)を使用し、本体の確率分布に従った出力になります。
本体を MTP 対応版(-mtp.qnn)に差し替え、Assistant(-mtp-assistant.qnn)を配置したうえで、本体を開いた後に Assistant を開く 1 行を追加します。プロンプトの設定と生成のコードは変更不要です。
ailiaLLMOpenModelFileA(llm, "gemma4-e2b-sm7635-int16-mtp.qnn", 0); /* 本体(MTP 対応版) */
ailiaLLMOpenMtpModelFileA(llm, "gemma4-e2b-sm7635-int16-mtp-assistant.qnn", 3); /* 追加 */
/* 以降の ailiaLLMSetPrompt / ailiaLLMGenerate は変更なし */
libailia_llm.so と libailia_llm_qnn.so(Windows では ailia_llm.dll と ailia_llm_qnn.dll)は同じバージョンを組み合わせて配置してください。ailiaLLMSetPrompt / ailiaLLMSetMultimodalPrompt)と ailiaLLMGenerate の使い方は変わりません。ailiaLLMGenerate は MTP でも 1 回の呼び出しで 1 トークンを返し、内部で確定済みのトークンを順に返します。-mmproj.qnn)を使用します。本体と Assistant は必ず同じ SoC・モデル・精度の組み合わせで使用してください。いずれも ailia LLM 1.5.1(QAIRT 2.47.0.260601)向けで、コンテキスト長は 8192 です。重みは本体・Assistant とも 4bit(W4)で、FP16 / Int16 は活性値の精度です。
| SoC | 端末の例 | モデル | ファイル | 内容 | サイズ |
|---|---|---|---|---|---|
| sm8475 (Hexagon v69 / FP16) | Snapdragon 8+ Gen 1 | E2B | gemma4-e2b-sm8475-fp16-mtp.qnn | 本体(AR-256 / AR-4 / AR-1) | 3.61 GB |
| gemma4-e2b-sm8475-fp16-mtp-assistant.qnn | Assistant(ドラフトモデル) | 44.6 MB | |||
| gemma4-e2b-sm8475-fp16-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 1.10 GB | |||
| sm7635 (Hexagon v73 / Int16) | Snapdragon 7s Gen 3 | E2B | gemma4-e2b-sm7635-int16-mtp.qnn | 本体(AR-256 / AR-4 / AR-1) | 3.61 GB |
| gemma4-e2b-sm7635-int16-mtp-assistant.qnn | Assistant(ドラフトモデル) | 46.8 MB | |||
| gemma4-e2b-sm7635-int16-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 0.63 GB | |||
| E4B | gemma4-e4b-sm7635-int16-mtp.qnn | 本体(AR-256 / AR-4 / AR-1) | 5.56 GB | ||
| gemma4-e4b-sm7635-int16-mtp-assistant.qnn | Assistant(ドラフトモデル) | 47.5 MB | |||
| gemma4-e4b-sm7635-int16-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 0.63 GB | |||
| sc8380xp (Hexagon v73 / FP16) | Surface(Snapdragon X Plus) | E2B | gemma4-e2b-sc8380xp-fp16-mtp.qnn | 本体(AR-256 / AR-4 / AR-1) | 3.63 GB |
| gemma4-e2b-sc8380xp-fp16-mtp-assistant.qnn | Assistant(ドラフトモデル) | 46.6 MB | |||
| gemma4-e2b-sc8380xp-fp16-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 1.09 GB | |||
| E4B | gemma4-e4b-sc8380xp-fp16-mtp.qnn | 本体(AR-256 / AR-4 / AR-1) | 5.61 GB | ||
| gemma4-e4b-sc8380xp-fp16-mtp-assistant.qnn | Assistant(ドラフトモデル) | 47.0 MB | |||
| gemma4-e4b-sc8380xp-fp16-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 1.10 GB | |||
| qcs6490 (Hexagon v68 / Int16) | QCS6490 | E2B | gemma4-e2b-qcs6490-int16-mtp.qnn | 本体(AR-256 / AR-4 / AR-1) | 3.60 GB |
| gemma4-e2b-qcs6490-int16-mtp-assistant.qnn | Assistant(ドラフトモデル) | 44.7 MB | |||
| gemma4-e2b-qcs6490-int16-mmproj.qnn | 画像 / 音声エンコーダ(mmproj) | 0.63 GB |
画像・音声入力には、本体と同じ SoC・モデル・精度の -mmproj.qnn を使用します。
MTP 対応の本体は、Assistant を開かなければ従来どおり AR-1 で 1 トークンずつ生成します。MTP の有無を切り替える場合でも、本体ファイルは MTP 対応版の 1 つで済みます。
CPU では、本体に通常の Gemma 4 の GGUF、Assistant に MTP 用の GGUF を使用します。
| モデル | ファイル | 内容 | サイズ |
|---|---|---|---|
| E2B | gemma-4-E2B-it-qat-Q4_0.gguf | 本体(例) | 3.35 GB |
| mtp-gemma-4-E2B-it-Q4_0.gguf | Assistant(ドラフトモデル) | 59.2 MB | |
| E4B | gemma-4-E4B-it-qat-Q4_0.gguf | 本体(例) | 5.15 GB |
| mtp-gemma-4-E4B-it-Q4_0.gguf | Assistant(ドラフトモデル) | 59.7 MB |
Assistant は本体と語彙・hidden サイズが一致している必要があります。E2B の本体には E2B の Assistant、E4B の本体には E4B の Assistant を組み合わせてください。
MTP に対応した Android 向けの評価版 APK は ailia-models-kotlin-qnn.apk です。
MTP に対応した Windows on Arm 向けの評価版アプリは ailia-models-flutter-qnn-v261017.zip です。Snapdragon X Plus と QCS6490 で動作します。
Assistant は ailiaLLMOpenMtpModelFileA / W で開きます。本体の ailiaLLMOpenModelFileA / W と同様に、パスの文字列の種類に応じて使い分けます。
int ailiaLLMOpenMtpModelFileA(struct AILIALLM *llm, const char *path, unsigned int n_draft);
int ailiaLLMOpenMtpModelFileW(struct AILIALLM *llm, const wchar_t *path, unsigned int n_draft);
| 引数 | 内容 |
|---|---|
llm | 本体モデルを開いた AILIALLM インスタンス |
path | Assistant のパス。A 版は UTF-8、W 版はワイド文字列(Windows では UTF-16)で指定します。NPU の本体には -mtp-assistant.qnn、CPU の本体には mtp-gemma-4-*.gguf を指定します。 |
n_draft | Assistant が 1 回に生成する候補(ドラフト)トークン数です。本体はこの候補と自身の 1 トークンをまとめて検証します。1 以上の値を指定し、通常は 3 を指定します。NPU(QNN)では 1〜3 です。0 を指定するとエラーになります。MTP を使用しない場合は、ailiaLLMOpenMtpModelFileA / W を呼び出さないでください。 |
呼び出し順は次のとおりです。
ailiaLLMCreateailiaLLMOpenModelFileA / W で本体を開く(NPU では -mtp.qnn)ailiaLLMOpenMtpModelFileA / W で Assistant を開く(最初のプロンプト設定より前に 1 回だけ)ailiaLLMOpenMultimodalProjectorFileA / W で mmproj を開くailiaLLMSetPrompt / ailiaLLMSetMultimodalPrompt と ailiaLLMGenerate で通常どおり生成.qnn)に Assistant を開くと、エラーになります。ailiaLLMDestroy)して作り直します。2 回目の呼び出しは INVALID_STATE になります。CONTEXT_FULL を返します。NPU で SoC 名を取得し、MTP 対応の本体と Assistant を開く例です。モデルファイルはアプリのプライベートストレージにあるものとします。精度(fp16 / int16)は SoC によって異なるため、上記の表に従って選択してください。
#include "ailia_llm.h"
#include <stdio.h>
#include <string.h>
/* model_dir: アプリのプライベートストレージ。成功時は呼び出し側が Destroy する。 */
int open_qnn_mtp_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;
/* sm8475 / sc8380xp は FP16、sm7635 / qcs6490 は Int16 */
const char *precision =
(strcmp(soc, "sm8475") == 0 || strcmp(soc, "sc8380xp") == 0) ? "fp16" : "int16";
char target[512], assistant[512];
int n1 = snprintf(target, sizeof(target), "%s/gemma4-e2b-%s-%s-mtp.qnn", model_dir, soc, precision);
int n2 = snprintf(assistant, sizeof(assistant), "%s/gemma4-e2b-%s-%s-mtp-assistant.qnn", model_dir, soc, precision);
if (n1 < 0 || (size_t)n1 >= sizeof(target) || n2 < 0 || (size_t)n2 >= sizeof(assistant))
return AILIA_LLM_STATUS_INVALID_ARGUMENT;
struct AILIALLM *llm = NULL;
status = ailiaLLMCreate(&llm);
if (status != AILIA_LLM_STATUS_SUCCESS) return status;
status = ailiaLLMOpenModelFileA(llm, target, 0); /* 本体 */
if (status == AILIA_LLM_STATUS_SUCCESS)
status = ailiaLLMOpenMtpModelFileA(llm, assistant, 3); /* ドラフトモデル */
if (status != AILIA_LLM_STATUS_SUCCESS) { ailiaLLMDestroy(llm); return status; }
*out = llm;
return AILIA_LLM_STATUS_SUCCESS;
}
CPU の場合は、本体に GGUF、Assistant に MTP 用 GGUF を指定します。
ailiaLLMOpenModelFileA(llm, "gemma-4-E2B-it-qat-Q4_0.gguf", 4096);
ailiaLLMOpenMtpModelFileA(llm, "mtp-gemma-4-E2B-it-Q4_0.gguf", 3);
from pathlib import Path
import ailia_llm
soc = ailia_llm.AiliaLLM.get_qnn_model_name()
precision = "fp16" if soc in ("sm8475", "sc8380xp") else "int16"
model_dir = Path("/data/user/0/your.package/files")
llm = ailia_llm.AiliaLLM()
llm.open(str(model_dir / f"gemma4-e2b-{soc}-{precision}-mtp.qnn"), n_ctx=0)
llm.open_mtp_model(str(model_dir / f"gemma4-e2b-{soc}-{precision}-mtp-assistant.qnn"), n_draft=3)
for delta in llm.generate([{"role": "user", "content": "自己紹介してください。"}]):
print(delta, end="", flush=True)
CPU の場合は llm.open("gemma-4-E2B-it-qat-Q4_0.gguf") の後に llm.open_mtp_model("mtp-gemma-4-E2B-it-Q4_0.gguf", n_draft=3) を呼び出します。失敗時は AiliaLLMError が送出されます。
import 'package:ailia_llm/ailia_llm_model.dart';
final soc = AiliaLLMModel.getQNNModelName();
final precision = (soc == 'sm8475' || soc == 'sc8380xp') ? 'fp16' : 'int16';
const dir = '/data/user/0/your.package/files';
final model = AiliaLLMModel();
model.open('$dir/gemma4-e2b-$soc-$precision-mtp.qnn', 0);
model.openMtpModel('$dir/gemma4-e2b-$soc-$precision-mtp-assistant.qnn', 3);
// 使用後は model.close();
CPU の場合は model.open('gemma-4-E2B-it-qat-Q4_0.gguf', 4096) の後に model.openMtpModel('mtp-gemma-4-E2B-it-Q4_0.gguf', 3) を呼び出します。失敗時は Exception が送出され、本体は開いたまま MTP は有効になりません。
context は Android の Activity または Application の Context です。
import axip.ailia_llm.AiliaLLM
val soc = AiliaLLM.getQNNModelName()
val precision = if (soc == "sm8475" || soc == "sc8380xp") "fp16" else "int16"
val dir = context.filesDir
val llm = AiliaLLM()
llm.openModelFile("$dir/gemma4-e2b-$soc-$precision-mtp.qnn", 0)
llm.openMtpModel("$dir/gemma4-e2b-$soc-$precision-mtp-assistant.qnn", 3)
// 使用後は llm.close()
CPU の場合は llm.openModelFile("gemma-4-E2B-it-qat-Q4_0.gguf", 4096) の後に llm.openMtpModel("mtp-gemma-4-E2B-it-Q4_0.gguf", 3) を呼び出します。失敗時は RuntimeException が送出されます。
using System;
using System.IO;
using UnityEngine;
using ailiaLLM;
string soc = AiliaLLMModel.GetQNNModelName();
string precision = (soc == "sm8475" || soc == "sc8380xp") ? "fp16" : "int16";
string dir = Application.persistentDataPath;
var llm = new AiliaLLMModel();
if (!llm.Create()) throw new Exception("ailiaLLMCreate failed");
if (!llm.Open(Path.Combine(dir, $"gemma4-e2b-{soc}-{precision}-mtp.qnn"), 0))
throw new Exception(llm.GetErrorDetail());
if (!llm.OpenMtpModel(Path.Combine(dir, $"gemma4-e2b-{soc}-{precision}-mtp-assistant.qnn"), 3))
throw new Exception(llm.GetErrorDetail());
// 使用後は llm.Dispose();
Assistant を開いた後に、同じ SoC・モデル・精度の mmproj を開きます。プロンプトの設定方法は通常の マルチモーダル ガイド と同じです。
ailiaLLMOpenModelFileA(llm, "gemma4-e2b-sm7635-int16-mtp.qnn", 0);
ailiaLLMOpenMtpModelFileA(llm, "gemma4-e2b-sm7635-int16-mtp-assistant.qnn", 3);
ailiaLLMOpenMultimodalProjectorFileA(llm, "gemma4-e2b-sm7635-int16-mmproj.qnn");
/* 以降は ailiaLLMSetMultimodalPrompt / ailiaLLMGenerate */