ailia LLM MTP(Multi-Token Prediction)ガイド

ailia LLM 1.5.1 で対応した MTP を使用して、Gemma 4 のトークン生成(Decode)を高速化する方法を説明します。

このページは ailia LLM NPU(QNN)ガイド からの差分として、MTP に必要なモデルと API を説明します。NPU のセットアップ(QNN ランタイムの配置、Android の設定、SoC 名の取得)は QNN ガイドと共通です。測定結果は ailia LLM MTP ベンチマーク を参照してください。

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 グラフ)で評価します。本体の予測と一致した候補はそのまま採用し、最初に食い違った位置では本体の予測したトークンを採用します。

① Assistant が 3 トークンを予想
今日は雨
↓
② 本体が 4 行をまとめて検証(AR-4 を 1 回実行)
今日は雨晴れ
緑: 本体の予測と一致して採用 / 赤: 不一致で破棄 / 紫: 本体が予測したトークン
↓
③ 本体 1 回の実行で 1〜4 トークンが確定
「今日」「は」「晴れ」の 3 トークンが確定し、続きから再び予想します

候補がすべて外れても、本体が予測した 1 トークンは必ず確定します。予想が当たるほど本体の実行回数が減り、生成が速くなります。生成されるトークンは本体の予測に基づいて決まるため、MTP は出力の品質を落とさずに速度を上げる手法です(ただし、AR-4 と AR-1 の数値誤差により、通常生成と完全に同じトークン列になるとは限りません)。temperature を設定したサンプリングでは、本体と Assistant の確率比で候補を受理する方式(Speculative Sampling)を使用し、本体の確率分布に従った出力になります。

MTP の有効化

本体を 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 は変更なし */

QNN ガイドからの差分

モデルのダウンロード

NPU(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 1E2Bgemma4-e2b-sm8475-fp16-mtp.qnn本体(AR-256 / AR-4 / AR-1)3.61 GB
gemma4-e2b-sm8475-fp16-mtp-assistant.qnnAssistant(ドラフトモデル)44.6 MB
gemma4-e2b-sm8475-fp16-mmproj.qnn画像 / 音声エンコーダ(mmproj)1.10 GB
sm7635
(Hexagon v73 / Int16)
Snapdragon 7s Gen 3E2Bgemma4-e2b-sm7635-int16-mtp.qnn本体(AR-256 / AR-4 / AR-1)3.61 GB
gemma4-e2b-sm7635-int16-mtp-assistant.qnnAssistant(ドラフトモデル)46.8 MB
gemma4-e2b-sm7635-int16-mmproj.qnn画像 / 音声エンコーダ(mmproj)0.63 GB
E4Bgemma4-e4b-sm7635-int16-mtp.qnn本体(AR-256 / AR-4 / AR-1)5.56 GB
gemma4-e4b-sm7635-int16-mtp-assistant.qnnAssistant(ドラフトモデル)47.5 MB
gemma4-e4b-sm7635-int16-mmproj.qnn画像 / 音声エンコーダ(mmproj)0.63 GB
sc8380xp
(Hexagon v73 / FP16)
Surface(Snapdragon X Plus)E2Bgemma4-e2b-sc8380xp-fp16-mtp.qnn本体(AR-256 / AR-4 / AR-1)3.63 GB
gemma4-e2b-sc8380xp-fp16-mtp-assistant.qnnAssistant(ドラフトモデル)46.6 MB
gemma4-e2b-sc8380xp-fp16-mmproj.qnn画像 / 音声エンコーダ(mmproj)1.09 GB
E4Bgemma4-e4b-sc8380xp-fp16-mtp.qnn本体(AR-256 / AR-4 / AR-1)5.61 GB
gemma4-e4b-sc8380xp-fp16-mtp-assistant.qnnAssistant(ドラフトモデル)47.0 MB
gemma4-e4b-sc8380xp-fp16-mmproj.qnn画像 / 音声エンコーダ(mmproj)1.10 GB
qcs6490
(Hexagon v68 / Int16)
QCS6490E2Bgemma4-e2b-qcs6490-int16-mtp.qnn本体(AR-256 / AR-4 / AR-1)3.60 GB
gemma4-e2b-qcs6490-int16-mtp-assistant.qnnAssistant(ドラフトモデル)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

CPU では、本体に通常の Gemma 4 の GGUF、Assistant に MTP 用の GGUF を使用します。

モデルファイル内容サイズ
E2Bgemma-4-E2B-it-qat-Q4_0.gguf本体(例)3.35 GB
mtp-gemma-4-E2B-it-Q4_0.ggufAssistant(ドラフトモデル)59.2 MB
E4Bgemma-4-E4B-it-qat-Q4_0.gguf本体(例)5.15 GB
mtp-gemma-4-E4B-it-Q4_0.ggufAssistant(ドラフトモデル)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)を開く API

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 インスタンス
pathAssistant のパス。A 版は UTF-8、W 版はワイド文字列(Windows では UTF-16)で指定します。NPU の本体には -mtp-assistant.qnn、CPU の本体には mtp-gemma-4-*.gguf を指定します。
n_draftAssistant が 1 回に生成する候補(ドラフト)トークン数です。本体はこの候補と自身の 1 トークンをまとめて検証します。1 以上の値を指定し、通常は 3 を指定します。NPU(QNN)では 1〜3 です。0 を指定するとエラーになります。MTP を使用しない場合は、ailiaLLMOpenMtpModelFileA / W を呼び出さないでください。

呼び出し順は次のとおりです。

  1. ailiaLLMCreate
  2. ailiaLLMOpenModelFileA / W で本体を開く(NPU では -mtp.qnn)
  3. ailiaLLMOpenMtpModelFileA / W で Assistant を開く(最初のプロンプト設定より前に 1 回だけ)
  4. 画像・音声入力を使う場合は ailiaLLMOpenMultimodalProjectorFileA / W で mmproj を開く
  5. ailiaLLMSetPrompt / ailiaLLMSetMultimodalPrompt と ailiaLLMGenerate で通常どおり生成

コードサンプル

NPU で SoC 名を取得し、MTP 対応の本体と Assistant を開く例です。モデルファイルはアプリのプライベートストレージにあるものとします。精度(fp16 / int16)は SoC によって異なるため、上記の表に従って選択してください。

C

#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);

Python

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 が送出されます。

Flutter

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 は有効になりません。

JNI(Kotlin)

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 が送出されます。

Unity(C#)

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 */

制約