Sky-Kim commited on
Commit
fc540fe
·
1 Parent(s): 2e7837a

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
- // Galaxy Z Fold 7 (SM8750/v79) Hexagon NPU verified working (~25 tok/s, text + multimodal, streaming).
146
- // Transport falls back to CPU if NPU init fails. (GPU/CPU need the plain model in LiteRtModelProvisioner.)
147
- IAgentTransport transport = new AndroidLlmTransport(dispatcher, "NPU", Log);
 
 
 
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
- var androidTransport = new AndroidLlmTransport(dispatcher, "GPU", Debug.Log);
 
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 (SM8750 / Hexagon v79) NPU export — verified working (~25 tok/s, multimodal). The plain
17
- // "gemma-4-E2B-it.litertlm" is the GPU/CPU variant (the NPU QNN export only runs on NPU).
18
- public const string ModelFileName = "gemma-4-E2B-it_qualcomm_sm8750.litertlm";
 
 
 
 
 
 
 
 
 
 
 
 
 
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 const string PushSourcePath = "/data/local/tmp/" + ModelFileName;
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)