Gate LiteRT-LM backend by SoC (NPU on verified chips, GPU elsewhere)
Browse files
Runtime/AgentCore/AgentBuilder.cs
CHANGED
|
@@ -142,9 +142,12 @@ namespace OnDeviceAgent.AgentCore
|
|
| 142 |
TurnReminder = TurnReminder,
|
| 143 |
};
|
| 144 |
#if UNITY_ANDROID && !UNITY_EDITOR
|
| 145 |
-
//
|
| 146 |
-
//
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
| 148 |
#else
|
| 149 |
IAgentTransport transport = new OllamaTransport(Endpoint, Model, options.AgentName, Log);
|
| 150 |
#endif
|
|
|
|
| 142 |
TurnReminder = TurnReminder,
|
| 143 |
};
|
| 144 |
#if UNITY_ANDROID && !UNITY_EDITOR
|
| 145 |
+
// Pick NPU only on SoCs verified to run the QNN export (Fold 7 / SM8750); every other device uses GPU
|
| 146 |
+
// (with CPU fallback in the transport). The model file is matched to this same decision in the
|
| 147 |
+
// provisioner — keep them in sync, since the wrong-SoC NPU export aborts nativeCreateEngine natively.
|
| 148 |
+
// UseNpu is read here, on the Unity main thread, so its JNI SoC query runs safely.
|
| 149 |
+
var llmBackend = LiteRtModelProvisioner.UseNpu ? "NPU" : "GPU";
|
| 150 |
+
IAgentTransport transport = new AndroidLlmTransport(dispatcher, llmBackend, Log);
|
| 151 |
#else
|
| 152 |
IAgentTransport transport = new OllamaTransport(Endpoint, Model, options.AgentName, Log);
|
| 153 |
#endif
|
Runtime/AgentCore/Rag/KnowledgeRagComponent.cs
CHANGED
|
@@ -65,7 +65,8 @@ namespace OnDeviceAgent.AgentCore
|
|
| 65 |
|
| 66 |
#if UNITY_ANDROID && !UNITY_EDITOR
|
| 67 |
// Route RAG's LLM through the shared LiteRT-LM singleton - Ollama HTTP doesn't exist on-device and reusing avoids a second model load.
|
| 68 |
-
|
|
|
|
| 69 |
IChatLlm ragLlm = new AndroidChatLlm(
|
| 70 |
(sys, prompt, ct) => androidTransport.RunAsync(sys, prompt, null, null, ct), model);
|
| 71 |
m_Rag = new LightRagKnowledgeService(dbWorkingDir, ragLlm, embedder, dispatcher,
|
|
|
|
| 65 |
|
| 66 |
#if UNITY_ANDROID && !UNITY_EDITOR
|
| 67 |
// Route RAG's LLM through the shared LiteRT-LM singleton - Ollama HTTP doesn't exist on-device and reusing avoids a second model load.
|
| 68 |
+
// Match the main transport's SoC-gated backend so this doesn't request a conflicting one for the shared engine.
|
| 69 |
+
var androidTransport = new AndroidLlmTransport(dispatcher, LiteRtModelProvisioner.UseNpu ? "NPU" : "GPU", Debug.Log);
|
| 70 |
IChatLlm ragLlm = new AndroidChatLlm(
|
| 71 |
(sys, prompt, ct) => androidTransport.RunAsync(sys, prompt, null, null, ct), model);
|
| 72 |
m_Rag = new LightRagKnowledgeService(dbWorkingDir, ragLlm, embedder, dispatcher,
|
Runtime/AgentCore/Runtime/LiteRtModelProvisioner.cs
CHANGED
|
@@ -13,9 +13,22 @@ namespace OnDeviceAgent.AgentCore
|
|
| 13 |
{
|
| 14 |
public static class LiteRtModelProvisioner
|
| 15 |
{
|
| 16 |
-
// Galaxy Z Fold 7
|
| 17 |
-
//
|
| 18 |
-
public const string
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
// Hugging Face source for the runtime download (overridable before the first engine init).
|
| 21 |
public static string HfRepo = "litert-community/gemma-4-E2B-it-litert-lm";
|
|
@@ -28,10 +41,29 @@ public static class LiteRtModelProvisioner
|
|
| 28 |
public static Action<float> OnDownloadProgress;
|
| 29 |
|
| 30 |
// Where `adb push` lands the file during development.
|
| 31 |
-
public
|
| 32 |
|
| 33 |
const string SubDir = "LiteRtModels";
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
// Returns the absolute path to a model file the JNI engine can open, provisioning it on first use.
|
| 36 |
// `persistentDataPath` must be captured by the caller on the Unity main thread.
|
| 37 |
public static async Task<string> EnsureModelAsync(string persistentDataPath, Action<string> log = null, CancellationToken ct = default)
|
|
|
|
| 13 |
{
|
| 14 |
public static class LiteRtModelProvisioner
|
| 15 |
{
|
| 16 |
+
// SoC-specific QNN export for the Hexagon NPU (Galaxy Z Fold 7, SM8750/v79 — verified ~25 tok/s, multimodal).
|
| 17 |
+
// It ONLY runs on its target NPU; loading it elsewhere aborts nativeCreateEngine (native SIGABRT, uncatchable).
|
| 18 |
+
public const string NpuModelFileName = "gemma-4-E2B-it_qualcomm_sm8750.litertlm";
|
| 19 |
+
// GPU/CPU variant — runs on any device.
|
| 20 |
+
public const string GpuModelFileName = "gemma-4-E2B-it.litertlm";
|
| 21 |
+
|
| 22 |
+
// SoC models whose NPU is verified to run NpuModelFileName. Anything else (incl. unknown / pre-API-31) uses GPU.
|
| 23 |
+
static readonly string[] s_NpuCapableSocs = { "SM8750" };
|
| 24 |
+
|
| 25 |
+
// Decided once from the device SoC. True -> NPU model + "NPU" backend; false -> GPU model + "GPU" backend.
|
| 26 |
+
// First access MUST be on the Unity main thread (JNI) — AgentBuilder reads it when building the transport.
|
| 27 |
+
static bool? s_UseNpu;
|
| 28 |
+
public static bool UseNpu => s_UseNpu ??= DetectNpuCapable();
|
| 29 |
+
|
| 30 |
+
// The model file to provision for this device, picked from the SoC capability.
|
| 31 |
+
public static string ModelFileName => UseNpu ? NpuModelFileName : GpuModelFileName;
|
| 32 |
|
| 33 |
// Hugging Face source for the runtime download (overridable before the first engine init).
|
| 34 |
public static string HfRepo = "litert-community/gemma-4-E2B-it-litert-lm";
|
|
|
|
| 41 |
public static Action<float> OnDownloadProgress;
|
| 42 |
|
| 43 |
// Where `adb push` lands the file during development.
|
| 44 |
+
public static string PushSourcePath => "/data/local/tmp/" + ModelFileName;
|
| 45 |
|
| 46 |
const string SubDir = "LiteRtModels";
|
| 47 |
|
| 48 |
+
static bool DetectNpuCapable()
|
| 49 |
+
{
|
| 50 |
+
try
|
| 51 |
+
{
|
| 52 |
+
using var build = new UnityEngine.AndroidJavaClass("android.os.Build");
|
| 53 |
+
// Build.SOC_MODEL exists on API 31+; "" / exception on older OS falls through to the GPU path.
|
| 54 |
+
var soc = (build.GetStatic<string>("SOC_MODEL") ?? "").ToUpperInvariant();
|
| 55 |
+
var npu = false;
|
| 56 |
+
foreach (var s in s_NpuCapableSocs)
|
| 57 |
+
if (soc.Contains(s)) { npu = true; break; }
|
| 58 |
+
UnityEngine.Debug.Log($"[LiteRtModelProvisioner] SOC_MODEL='{soc}' -> {(npu ? "NPU" : "GPU")} path");
|
| 59 |
+
return npu;
|
| 60 |
+
}
|
| 61 |
+
catch (Exception)
|
| 62 |
+
{
|
| 63 |
+
return false;
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
// Returns the absolute path to a model file the JNI engine can open, provisioning it on first use.
|
| 68 |
// `persistentDataPath` must be captured by the caller on the Unity main thread.
|
| 69 |
public static async Task<string> EnsureModelAsync(string persistentDataPath, Action<string> log = null, CancellationToken ct = default)
|