この記事では、Unity Androidアプリの中でローカルLLMを動かし、アプリ内APIとして呼び出す方法を整理します。
ここでいう「ローカルLLM API」は、クラウドのOpenAI APIやGemini APIを呼ぶのではなく、Android端末内に保存したLLMモデルを使って、Unityアプリからローカルに推論を実行する構成です。
この記事で作るもの
完成イメージは次のような構成です。
Unity Android App
│
│ C#から呼び出し
▼
Android Native Plugin / AAR
│
│ JNI
▼
llama.cpp / Local LLM Runtime
│
▼
GGUF Model
Application.persistentDataPath/models/model.gguf
ユーザー視点では、次のように動きます。
初回起動
↓
ローカルLLMモデルが端末にあるか確認
↓
なければモデルをダウンロード
↓
端末内に保存
↓
UnityアプリからローカルLLMを実行
↓
2回目以降はダウンロードせず端末内モデルを利用
前提
この記事では、以下の構成を想定します。
- Unity
- Androidビルド
- Android実機
- llama.cpp
- GGUFモデル
- Android Native Plugin / AAR
- Unity C# から
AndroidJavaObjectで呼び出し
クラウドAPIは使いません。
推論はAndroid端末上で完結します。
なぜHTTP APIサーバーではなくNative Plugin方式にするのか
ローカルLLMを「API」として呼ぶ方法には、大きく2つあります。
方式A:アプリ内でHTTPサーバーを起動する
Unity C#
↓ HTTP
127.0.0.1:8080
↓
llama-server
PCやMacでは、llama-server を立ち上げて http://127.0.0.1:8080/v1/chat/completions に投げる構成が簡単です。
しかし、Androidアプリ内でHTTPサーバーを同梱・起動するのは、次の理由でやや面倒です。
- プロセス管理が必要
- ポート管理が必要
- Androidのライフサイクルと相性が悪い
- バックグラウンド制限を考慮する必要がある
- セキュリティ上、不要なローカルポートを開けたくない
- Unityからのデバッグは楽だが、本番実装では複雑になりやすい
方式B:UnityからNative Pluginを直接呼ぶ
Unity C#
↓ AndroidJavaObject
Kotlin / Java
↓ JNI
C++ wrapper
↓
llama.cpp
Androidアプリ内で完結させるなら、こちらの方が自然です。
この記事では、方式B:Native Plugin方式 を中心に説明します。
全体構成
最終的には、以下のようなファイル構成になります。
UnityProject/
Assets/
Scripts/
LocalLlmManager.cs
ModelDownloader.cs
LocalLlmBridge.cs
Plugins/
Android/
localllm.aar
AndroidNativePlugin/
app or library module
src/main/java/com/example/localllm/LocalLlm.kt
src/main/cpp/native-lib.cpp
src/main/cpp/CMakeLists.txt
src/main/cpp/llama.cpp/...
Unity側は、モデルの確認・ダウンロード・UI制御を担当します。
Android Native Plugin側は、llama.cppの初期化・推論・解放を担当します。
モデルファイルの保存場所
初回ダウンロードしたモデルは、Unityの Application.persistentDataPath に保存します。
string modelPath = Path.Combine(
Application.persistentDataPath,
"models",
"model.gguf"
);
persistentDataPath は、アプリの実行間で保持したいファイルを保存するための場所です。
Androidでは、アプリを更新しても通常は残ります。
ただし、ユーザーがアプリデータを削除した場合は消えます。
モデル選定
Androidで動かす場合、最初は小さいモデルから始めるのが重要です。
| 端末RAM | おすすめ |
|---|---|
| 4GB | 0.5B〜1B Q4 |
| 6GB | 1B〜1.5B Q4 |
| 8GB | 1B〜2B Q4 |
| 12GB以上 | 3B〜4B Q4も検討可能 |
MVPでは、以下がおすすめです。
モデル: 1B〜2B
形式: GGUF
量子化: Q4
用途: 短文生成 / NPC会話 / FAQ / 口調変換
スマホ上で7B以上を動かすのは、メモリ・発熱・速度の面でかなり厳しいです。
実装の流れ
実装は次の順番で進めます。
1. Unityでモデルファイルの存在確認
2. なければ初回ダウンロード
3. SHA256で破損チェック
4. Android Native Pluginを初期化
5. モデルパスをNative Pluginに渡す
6. Unity UIからプロンプトを送る
7. Native側でllama.cpp推論
8. 結果をUnity C#に返す
9. アプリ終了時に解放
1. Unity側:モデルを初回ダウンロードする
まず、モデルファイルが存在するか確認し、なければダウンロードする処理を作ります。
ModelDownloader.cs を作成します。
using System;
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
public class ModelDownloader : MonoBehaviour
{
[SerializeField] private string modelUrl =
"https://example.com/models/model.gguf";
[SerializeField] private string modelFileName =
"model.gguf";
public string ModelPath =>
Path.Combine(Application.persistentDataPath, "models", modelFileName);
public IEnumerator EnsureModelDownloaded(
Action<string> onReady,
Action<float> onProgress,
Action<string> onError
)
{
string modelDir = Path.GetDirectoryName(ModelPath);
if (!Directory.Exists(modelDir))
{
Directory.CreateDirectory(modelDir);
}
if (File.Exists(ModelPath) && new FileInfo(ModelPath).Length > 0)
{
onReady?.Invoke(ModelPath);
yield break;
}
string tempPath = ModelPath + ".tmp";
if (File.Exists(tempPath))
{
File.Delete(tempPath);
}
using (UnityWebRequest request = UnityWebRequest.Get(modelUrl))
{
request.downloadHandler = new DownloadHandlerFile(tempPath);
request.SendWebRequest();
while (!request.isDone)
{
onProgress?.Invoke(request.downloadProgress);
yield return null;
}
if (request.result != UnityWebRequest.Result.Success)
{
if (File.Exists(tempPath))
{
File.Delete(tempPath);
}
onError?.Invoke(request.error);
yield break;
}
}
if (File.Exists(ModelPath))
{
File.Delete(ModelPath);
}
File.Move(tempPath, ModelPath);
onReady?.Invoke(ModelPath);
}
}
ポイントは、DownloadHandlerFile を使っていることです。
LLMモデルは数百MB〜数GBになるため、DownloadHandlerBuffer でメモリに載せるのは危険です。
必ずファイルへ直接保存します。
2. SHA256でモデルの破損チェックをする
大きなモデルファイルは、途中でダウンロードに失敗したり破損したりする可能性があります。
本番では、SHA256チェックを入れます。
using System.IO;
using System.Security.Cryptography;
using System.Text;
public static class HashUtil
{
public static string ComputeSha256(string filePath)
{
using var sha256 = SHA256.Create();
using var stream = File.OpenRead(filePath);
byte[] hash = sha256.ComputeHash(stream);
var sb = new StringBuilder();
foreach (byte b in hash)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
}
使い方です。
string hash = HashUtil.ComputeSha256(modelPath);
if (hash != expectedSha256)
{
File.Delete(modelPath);
Debug.LogError("モデルファイルが破損しています。再ダウンロードしてください。");
}
3. manifest.jsonでモデルを管理する
モデルURLやSHA256をアプリに直書きすると、後から差し替えが面倒です。
本番では、サーバー側に manifest.json を置くのがおすすめです。
{
"version": "1.0.0",
"modelName": "model.gguf",
"url": "https://example.com/models/model.gguf",
"sha256": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"size": 858993459
}
アプリ側の流れは以下です。
1. manifest.jsonを取得
2. ローカルに保存済みのversionと比較
3. バージョンが違えば再ダウンロード
4. SHA256チェック
5. 成功したらモデルを差し替え
これにより、アプリを更新せずにモデルを差し替えられます。
4. Android Native Pluginを作る
Unity C#から直接C++のllama.cppを呼ぶのではなく、Android Native Pluginを作ります。
構成は以下です。
Unity C#
↓ AndroidJavaObject
Kotlin / Java
↓ JNI
C++
↓ llama.cpp
Android StudioでAndroid Libraryモジュールを作成し、AARとしてビルドします。
Kotlin側のラッパー
LocalLlm.kt を作成します。
package com.example.localllm
class LocalLlm {
external fun init(modelPath: String): Boolean
external fun generate(prompt: String, maxTokens: Int): String
external fun release()
companion object {
init {
System.loadLibrary("localllm")
}
}
}
Unityからは、この LocalLlm クラスを呼び出します。
C++側のJNIラッパー
native-lib.cpp を作成します。
実際にはllama.cppのAPIを使って、モデルロード・コンテキスト作成・トークン生成を実装します。
ここでは構造が分かるように、簡略化した形で書きます。
#include <jni.h>
#include <string>
#include <mutex>
// 実際の実装では llama.cpp のヘッダーをincludeします。
// #include "llama.h"
static std::mutex g_mutex;
// static llama_model* g_model = nullptr;
// static llama_context* g_ctx = nullptr;
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_localllm_LocalLlm_init(
JNIEnv* env,
jobject thiz,
jstring model_path
) {
const char* path = env->GetStringUTFChars(model_path, nullptr);
std::lock_guard<std::mutex> lock(g_mutex);
// 疑似コード:
//
// llama_backend_init();
//
// llama_model_params model_params = llama_model_default_params();
// g_model = llama_model_load_from_file(path, model_params);
//
// if (!g_model) {
// env->ReleaseStringUTFChars(model_path, path);
// return JNI_FALSE;
// }
//
// llama_context_params ctx_params = llama_context_default_params();
// ctx_params.n_ctx = 2048;
// ctx_params.n_threads = 4;
//
// g_ctx = llama_init_from_model(g_model, ctx_params);
//
// if (!g_ctx) {
// llama_model_free(g_model);
// g_model = nullptr;
// env->ReleaseStringUTFChars(model_path, path);
// return JNI_FALSE;
// }
env->ReleaseStringUTFChars(model_path, path);
return JNI_TRUE;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_localllm_LocalLlm_generate(
JNIEnv* env,
jobject thiz,
jstring prompt,
jint max_tokens
) {
const char* input = env->GetStringUTFChars(prompt, nullptr);
std::lock_guard<std::mutex> lock(g_mutex);
// 疑似コード:
//
// 1. promptをtokenize
// 2. llama_decodeでpromptを評価
// 3. logitsから次トークンをsample
// 4. max_tokensまで繰り返す
// 5. tokenを文字列に戻す
std::string output = "ここにLLMの生成結果が入ります。";
env->ReleaseStringUTFChars(prompt, input);
return env->NewStringUTF(output.c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_localllm_LocalLlm_release(
JNIEnv* env,
jobject thiz
) {
std::lock_guard<std::mutex> lock(g_mutex);
// if (g_ctx) {
// llama_free(g_ctx);
// g_ctx = nullptr;
// }
//
// if (g_model) {
// llama_model_free(g_model);
// g_model = nullptr;
// }
//
// llama_backend_free();
}
上記は骨組みです。
実際のトークン生成処理は、llama.cppのサンプルや llama-cli の実装を参考にして組み込みます。
CMakeLists.txtの例
Android Native側では、CMakeでllama.cppと自分のラッパーをビルドします。
cmake_minimum_required(VERSION 3.22.1)
project(localllm)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(llama.cpp)
add_library(
localllm
SHARED
native-lib.cpp
)
target_link_libraries(
localllm
llama
android
log
)
実際のllama.cppの構成やバージョンによって、リンクするターゲット名は変わる場合があります。
ビルドエラーが出る場合は、利用しているllama.cppのCMake構成を確認してください。
5. AARをUnityに配置する
Android StudioでAARをビルドしたら、Unityプロジェクトに配置します。
Assets/Plugins/Android/localllm.aar
Unityでは、Android向けのJava/KotlinプラグインやAARを Assets/Plugins/Android に配置して利用します。
6. Unity C#からNative Pluginを呼ぶ
Unity側で、Android Pluginを呼び出すブリッジクラスを作ります。
LocalLlmBridge.cs です。
using System;
using UnityEngine;
public class LocalLlmBridge : IDisposable
{
#if UNITY_ANDROID && !UNITY_EDITOR
private AndroidJavaObject llm;
#endif
public LocalLlmBridge()
{
#if UNITY_ANDROID && !UNITY_EDITOR
llm = new AndroidJavaObject("com.example.localllm.LocalLlm");
#endif
}
public bool Init(string modelPath)
{
#if UNITY_ANDROID && !UNITY_EDITOR
return llm.Call<bool>("init", modelPath);
#else
Debug.Log($"Mock Init: {modelPath}");
return true;
#endif
}
public string Generate(string prompt, int maxTokens)
{
#if UNITY_ANDROID && !UNITY_EDITOR
return llm.Call<string>("generate", prompt, maxTokens);
#else
return $"Mock response: {prompt}";
#endif
}
public void Dispose()
{
#if UNITY_ANDROID && !UNITY_EDITOR
llm?.Call("release");
llm?.Dispose();
#endif
}
}
Unity Editor上では実機のNative Pluginを呼べないため、モック応答を返すようにしています。
7. Unity側の管理クラスを作る
モデルダウンロード、初期化、推論をまとめる LocalLlmManager.cs を作ります。
using System;
using System.Threading.Tasks;
using UnityEngine;
public class LocalLlmManager : MonoBehaviour
{
[SerializeField] private ModelDownloader modelDownloader;
private LocalLlmBridge llm;
private bool isInitialized;
private bool isGenerating;
public event Action<float> OnDownloadProgress;
public event Action<string> OnStatusChanged;
public event Action<string> OnError;
private async void Start()
{
await InitializeAsync();
}
public async Task InitializeAsync()
{
OnStatusChanged?.Invoke("モデル確認中...");
string modelPath = null;
string error = null;
await RunCoroutineAsTask(modelDownloader.EnsureModelDownloaded(
onReady: path => modelPath = path,
onProgress: progress => OnDownloadProgress?.Invoke(progress),
onError: e => error = e
));
if (!string.IsNullOrEmpty(error))
{
OnError?.Invoke($"モデルのダウンロードに失敗しました: {error}");
return;
}
OnStatusChanged?.Invoke("ローカルLLM初期化中...");
llm = new LocalLlmBridge();
bool ok = await Task.Run(() => llm.Init(modelPath));
if (!ok)
{
OnError?.Invoke("ローカルLLMの初期化に失敗しました。");
return;
}
isInitialized = true;
OnStatusChanged?.Invoke("準備完了");
}
public async Task<string> GenerateAsync(string prompt, int maxTokens = 128)
{
if (!isInitialized)
{
throw new InvalidOperationException("LLM is not initialized.");
}
if (isGenerating)
{
throw new InvalidOperationException("LLM is already generating.");
}
isGenerating = true;
try
{
string result = await Task.Run(() =>
{
return llm.Generate(prompt, maxTokens);
});
return result;
}
finally
{
isGenerating = false;
}
}
private Task RunCoroutineAsTask(System.Collections.IEnumerator coroutine)
{
var tcs = new TaskCompletionSource<bool>();
StartCoroutine(WrapCoroutine(coroutine, tcs));
return tcs.Task;
}
private System.Collections.IEnumerator WrapCoroutine(
System.Collections.IEnumerator coroutine,
TaskCompletionSource<bool> tcs
)
{
yield return coroutine;
tcs.SetResult(true);
}
private void OnDestroy()
{
llm?.Dispose();
}
}
重要なのは、推論を Task.Run でバックグラウンド実行している点です。
LLM推論をUnityのメインスレッドで実行すると、UIが固まります。
8. UIから呼び出す
簡単なUI用スクリプトです。
using UnityEngine;
using UnityEngine.UI;
public class LocalLlmUI : MonoBehaviour
{
[SerializeField] private LocalLlmManager llmManager;
[SerializeField] private InputField inputField;
[SerializeField] private Text outputText;
[SerializeField] private Slider progressSlider;
private void Awake()
{
llmManager.OnStatusChanged += status =>
{
outputText.text = status;
};
llmManager.OnDownloadProgress += progress =>
{
progressSlider.value = progress;
};
llmManager.OnError += error =>
{
outputText.text = error;
};
}
public async void OnClickGenerate()
{
string prompt = inputField.text;
outputText.text = "生成中...";
try
{
string result = await llmManager.GenerateAsync(prompt, 128);
outputText.text = result;
}
catch (System.Exception e)
{
outputText.text = e.Message;
}
}
}
9. プロンプト設計
スマホ上のローカルLLMでは、長文生成よりも短文生成に寄せるのが現実的です。
例えば、ゲーム内NPCなら以下のようにします。
あなたはファンタジーRPGの村人です。
次の状況に対して、30文字以内の自然な日本語セリフを1つだけ返してください。
状況: プレイヤーが初めて村に来た
長く自由に生成させるより、制約を強くした方が速度も品質も安定します。
10. Androidビルド設定
Unity側では、以下を確認します。
Platform: Android
Scripting Backend: IL2CPP
Target Architectures: ARM64
Internet Access: Require
Minify: 最初は無効推奨
ローカルLLM推論は32bit端末では現実的ではないため、基本的にARM64を対象にします。
モデルを初回ダウンロードするため、インターネット権限も必要です。
<uses-permission android:name="android.permission.INTERNET" />
UnityのPlayer Settingsで Internet Access を Require にすれば、通常は付与されます。
11. メモリと発熱対策
Android端末では、メモリ・発熱・バッテリーが大きな制約になります。
以下のように設計します。
max_tokens: 64〜256
コンテキスト長: 1024〜2048
モデル: 1B〜2B Q4
同時生成: 1リクエストのみ
履歴: 必要最小限
生成結果: キャッシュする
特にゲーム用途では、毎回LLMに投げるのではなく、生成結果をキャッシュする設計が重要です。
12. アプリ内APIとして設計する
Unity側から見たローカルLLMを、アプリ内APIとして整理すると使いやすくなります。
例えば、以下のようなインターフェースを作ります。
public interface ILocalLlmApi
{
Task InitializeAsync();
Task<string> GenerateAsync(string prompt, int maxTokens);
}
実装クラスです。
public class LocalLlmApi : ILocalLlmApi
{
private readonly LocalLlmManager manager;
public LocalLlmApi(LocalLlmManager manager)
{
this.manager = manager;
}
public Task InitializeAsync()
{
return manager.InitializeAsync();
}
public Task<string> GenerateAsync(string prompt, int maxTokens)
{
return manager.GenerateAsync(prompt, maxTokens);
}
}
こうしておくと、将来的に以下の切り替えがしやすくなります。
開発中: PC上のllama-serverへHTTP接続
本番: Android端末内のNative Pluginで推論
失敗時: クラウドAPIへフォールバック
13. 開発中はHTTP接続でモックする
Android Native Pluginの開発は大変なので、最初から全部作るより、Unity EditorではPC上の llama-server にHTTP接続する形でゲーム側の実装を進めると楽です。
Unity Editor
↓ HTTP
Mac / PC の llama-server
その後、Android実機ではNative Pluginに差し替えます。
Unity Android
↓ AndroidJavaObject
Android Native Plugin
↓
llama.cpp
これにより、UIやゲームロジックの開発を先に進められます。
UnityからOpenAI互換APIを叩くHTTP版
開発用として、PCの llama-server に投げるコード例も載せます。
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
public class OpenAICompatibleClient : MonoBehaviour
{
[SerializeField] private string endpoint =
"http://127.0.0.1:8080/v1/chat/completions";
public IEnumerator Generate(string prompt, System.Action<string> onResult)
{
string json = $@"
{{
""model"": ""local-model"",
""messages"": [
{{
""role"": ""user"",
""content"": ""{EscapeJson(prompt)}""
}}
],
""max_tokens"": 128
}}";
using var request = new UnityWebRequest(endpoint, "POST");
byte[] bodyRaw = Encoding.UTF8.GetBytes(json);
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
onResult?.Invoke(request.error);
yield break;
}
onResult?.Invoke(request.downloadHandler.text);
}
private string EscapeJson(string s)
{
return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
}
}
これはAndroid端末内推論ではなく、開発中のモックとして便利です。
本番ではNative Plugin経由の LocalLlmBridge に切り替えます。
14. HTTPサーバー方式をAndroid内で使う場合
どうしても「Android端末内でOpenAI互換HTTP APIを立てたい」場合は、以下の構成になります。
Unity Android
↓ HTTP
127.0.0.1:ポート
↓
アプリ内HTTPサーバー
↓
llama.cpp
ただし、この方式は実装難度が上がります。
必要になるもの:
- Android内HTTPサーバー
- ポート管理
- サーバー起動・停止処理
- Androidライフサイクル対応
- セキュリティ対策
- バックグラウンド制限への対応
Unityアプリ内で自分だけが使うなら、HTTPサーバー方式より Native Pluginを直接呼ぶ方式 の方がシンプルです。
15. エラー対策
モデルが見つからない
原因:
- ダウンロードされていない
- パスが違う
- persistentDataPathが想定と違う
確認:
Debug.Log(Application.persistentDataPath);
Debug.Log(File.Exists(modelPath));
初期化に失敗する
原因:
- GGUFが壊れている
- llama.cppがそのモデル形式に対応していない
- メモリ不足
- ABIが違う
対策:
- SHA256チェックを入れる
- llama.cppを更新する
- 小さいモデルに変える
- ARM64のみでビルドする
UnityのUIが固まる
原因:
- メインスレッドで推論している
対策:
Task.Runでバックグラウンド実行する
生成中ボタンを無効化する
max_tokensを小さくする
Android実機でだけ落ちる
原因:
- Native PluginのABI不一致
- .soがAARに含まれていない
- ProGuard / Minifyの影響
- メモリ不足
対策:
- ARM64のみ対象にする
- AARの中にlib/arm64-v8a/*.soがあるか確認
- 最初はMinifyを無効にする
- logcatを見る
16. 最小MVP
最初に作るべきMVPは以下です。
1. 1B Q4 GGUFモデルを用意
2. Unityで初回ダウンロード
3. persistentDataPathに保存
4. AAR経由でNative Pluginを呼ぶ
5. llama.cppでモデル初期化
6. 固定プロンプトで短文生成
7. 結果をUnity UIに表示
プロンプト例:
あなたはゲーム内の村人です。
30文字以内で、プレイヤーを歓迎するセリフを1つ返してください。
出力例:
ようこそ、旅の人。ゆっくりしていってね。
まずはこのレベルから始めるのが現実的です。
まとめ
Unity Androidアプリ内でローカルLLM APIを実行するなら、基本構成は次のようになります。
Unity C#
↓
AndroidJavaObject
↓
Kotlin / Java AAR
↓
JNI
↓
llama.cpp
↓
GGUFモデル
重要なポイントは以下です。
- モデルは
Application.persistentDataPathに保存する - 初回利用時にダウンロードする
-
DownloadHandlerFileで直接ファイル保存する - SHA256で破損チェックする
- Native Plugin / AARでllama.cppを呼ぶ
- 推論は必ずバックグラウンドスレッドで実行する
- Androidでは1B〜2B Q4程度から始める
- 本番ではmanifest.jsonでモデルを管理する
- 開発中はPC上のllama-serverにHTTP接続してモックすると楽
ローカルLLMをUnity Androidに組み込むのは簡単ではありませんが、構成を分ければ実装できます。
モデル管理: Unity C#
推論エンジン: llama.cpp
橋渡し: Android Native Plugin
UI制御: Unity
この役割分担にすると、スマホ上で完全オフラインのAI会話・NPC応答・FAQ・短文生成などを実装できます。
参考リンク
-
llama.cpp Android build documentation
https://github.com/ggml-org/llama.cpp/blob/master/docs/android.md -
llama.cpp server / OpenAI compatible API
https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md -
Unity Android plug-ins
https://docs.unity3d.com/Manual/PluginsForAndroid.html -
Unity Application.persistentDataPath
https://docs.unity3d.com/ScriptReference/Application-persistentDataPath.html -
Hugging Face GGUF documentation
https://huggingface.co/docs/hub/en/gguf -
Google AI Edge LLM Inference Android
https://developers.google.com/edge/mediapipe/solutions/genai/llm_inference/android -
Google LiteRT-LM
https://github.com/google-ai-edge/LiteRT-LM