@dr33m/react-native-litert-lm 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -58,6 +58,10 @@ class HybridLiteRTLM : HybridLiteRTLMSpec() {
58
58
  /** Cached result of OpenCL availability probe (null = not yet checked). */
59
59
  @Volatile
60
60
  private var openCLAvailable: Boolean? = null
61
+
62
+ /** Cached result of NPU/QNN availability probe (null = not yet checked). */
63
+ @Volatile
64
+ private var npuAvailable: Boolean? = null
61
65
 
62
66
  /**
63
67
  * Initialize the native library.
@@ -197,6 +201,41 @@ class HybridLiteRTLM : HybridLiteRTLMSpec() {
197
201
  }
198
202
  }
199
203
 
204
+ // NPU hardware check: probe for QNN HTP runtime libraries.
205
+ // LiteRT-LM's NPU delegate requires Qualcomm QNN HTP (Hexagon Tensor
206
+ // Processor) runtime. Without it, Engine() crashes with SIGSEGV
207
+ // that Kotlin's try/catch cannot intercept.
208
+ // Unlike GPU (which throws a catchable Java exception on failure),
209
+ // NPU failure is a native crash — so we MUST detect before attempting.
210
+ if (npuAvailable == null) {
211
+ val hasNpu = run {
212
+ // Check for QNN HTP libraries in system vendor paths.
213
+ // These are only present on devices with Qualcomm NPU support.
214
+ val qnnPaths = arrayOf(
215
+ "/vendor/lib64/libQnnHtp.so",
216
+ "/vendor/lib/libQnnHtp.so",
217
+ "/system/vendor/lib64/libQnnHtp.so",
218
+ "/system/lib64/libQnnHtp.so",
219
+ "/vendor/lib64/libQnnSystem.so",
220
+ "/vendor/lib/libQnnSystem.so"
221
+ )
222
+ var found = false
223
+ for (path in qnnPaths) {
224
+ if (java.io.File(path).exists()) {
225
+ found = true
226
+ break
227
+ }
228
+ }
229
+ found
230
+ }
231
+ npuAvailable = hasNpu
232
+ if (!hasNpu) {
233
+ Log.w(TAG, "QNN HTP libraries not found — NPU backend unavailable")
234
+ } else {
235
+ Log.i(TAG, "QNN HTP libraries found — NPU backend may be available")
236
+ }
237
+ }
238
+
200
239
  // Detect multimodal support. Check config.multimodal flag first, then fall back to filename sniffing.
201
240
  // Only Gemma 3n bundles vision/audio executors; Gemma 4 E2B is text-only.
202
241
  // Passing vision/audio backends to a text-only model causes
@@ -259,12 +298,13 @@ class HybridLiteRTLM : HybridLiteRTLMSpec() {
259
298
  }
260
299
  }
261
300
  Backend.NPU -> {
301
+ val hasNpu = npuAvailable ?: false
262
302
  val nativeLibDir = LiteRTLMInitProvider.applicationContext?.applicationInfo?.nativeLibraryDir
263
- Log.i(TAG, "NPU backend requested - nativeLibraryDir=$nativeLibDir")
264
- if (nativeLibDir != null) {
303
+ Log.i(TAG, "NPU backend requested - available=$hasNpu, nativeLibraryDir=$nativeLibDir")
304
+ if (hasNpu && nativeLibDir != null) {
265
305
  com.google.ai.edge.litertlm.Backend.NPU(nativeLibraryDir = nativeLibDir)
266
306
  } else {
267
- Log.w(TAG, "NPU requested but nativeLibraryDir unavailable — using CPU directly")
307
+ Log.w(TAG, "NPU requested but hardware unavailable — using CPU directly")
268
308
  backend = Backend.CPU
269
309
  com.google.ai.edge.litertlm.Backend.CPU()
270
310
  }
@@ -662,10 +702,10 @@ class HybridLiteRTLM : HybridLiteRTLMSpec() {
662
702
  tool(apiTool)
663
703
  }
664
704
 
665
- // Create conversation with explicit SamplerConfig (required by Gallery pattern).
666
- // GPU backend may fail silently without proper sampler params.
705
+ // Create conversation config. NPU backend does not support SamplerConfig
706
+ // (matching Gallery app pattern setting sampler params on NPU causes crashes).
667
707
  val convConfig = ConversationConfig(
668
- samplerConfig = SamplerConfig(
708
+ samplerConfig = if (backend == Backend.NPU) null else SamplerConfig(
669
709
  topK = topK,
670
710
  topP = topP.toDouble(),
671
711
  temperature = temperature.toDouble(),
package/lib/index.d.ts CHANGED
@@ -84,6 +84,23 @@ export type ModelId = (typeof Models)[keyof typeof Models];
84
84
  * ```
85
85
  */
86
86
  export declare function getRecommendedBackend(): Backend;
87
+ /**
88
+ * Check whether the native LiteRT-LM module loaded successfully on this
89
+ * device. Some devices (unsupported ABI, missing native libs) can't load
90
+ * the native module at all — call this before showing any on-device AI UI
91
+ * so those devices degrade gracefully instead of throwing when a model is
92
+ * loaded. Result is cached after the first call.
93
+ *
94
+ * @returns true if native inference is available on this device
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * if (!isNativeAvailable()) {
99
+ * // hide/disable on-device AI features
100
+ * }
101
+ * ```
102
+ */
103
+ export declare function isNativeAvailable(): boolean;
87
104
  /**
88
105
  * Check if a backend configuration is supported on the current platform.
89
106
  * Returns a warning message if the configuration may have issues.
package/lib/index.js CHANGED
@@ -16,8 +16,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.GEMMA_4_E4B_IT = exports.GEMMA_4_E2B_IT = exports.GEMMA_3N_E2B_IT_INT4 = exports.Models = exports.createLLM = exports.ModelRegistry = exports.createNativeBuffer = exports.createMemoryTracker = void 0;
18
18
  exports.getRecommendedBackend = getRecommendedBackend;
19
+ exports.isNativeAvailable = isNativeAvailable;
19
20
  exports.checkBackendSupport = checkBackendSupport;
20
21
  exports.checkMultimodalSupport = checkMultimodalSupport;
22
+ const react_native_nitro_modules_1 = require("react-native-nitro-modules");
21
23
  const react_native_1 = require("react-native");
22
24
  var memoryTracker_1 = require("./memoryTracker");
23
25
  Object.defineProperty(exports, "createMemoryTracker", { enumerable: true, get: function () { return memoryTracker_1.createMemoryTracker; } });
@@ -103,6 +105,35 @@ function getRecommendedBackend() {
103
105
  // GPU is faster but may fail on some models/devices.
104
106
  return "cpu";
105
107
  }
108
+ let nativeAvailable = null;
109
+ /**
110
+ * Check whether the native LiteRT-LM module loaded successfully on this
111
+ * device. Some devices (unsupported ABI, missing native libs) can't load
112
+ * the native module at all — call this before showing any on-device AI UI
113
+ * so those devices degrade gracefully instead of throwing when a model is
114
+ * loaded. Result is cached after the first call.
115
+ *
116
+ * @returns true if native inference is available on this device
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * if (!isNativeAvailable()) {
121
+ * // hide/disable on-device AI features
122
+ * }
123
+ * ```
124
+ */
125
+ function isNativeAvailable() {
126
+ if (nativeAvailable === null) {
127
+ try {
128
+ react_native_nitro_modules_1.NitroModules.createHybridObject("ModelStore");
129
+ nativeAvailable = true;
130
+ }
131
+ catch {
132
+ nativeAvailable = false;
133
+ }
134
+ }
135
+ return nativeAvailable;
136
+ }
106
137
  /**
107
138
  * Check if a backend configuration is supported on the current platform.
108
139
  * Returns a warning message if the configuration may have issues.
@@ -3,7 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ModelRegistry = void 0;
4
4
  const react_native_nitro_modules_1 = require("react-native-nitro-modules");
5
5
  const modelPath_1 = require("./modelPath");
6
- const nativeStore = react_native_nitro_modules_1.NitroModules.createHybridObject("ModelStore");
6
+ let nativeStore = null;
7
+ // Lazily created — a module-scope createHybridObject() would throw at import
8
+ // time on devices where the native lib failed to load, crashing app startup
9
+ // before any capability check can run.
10
+ function getStore() {
11
+ if (!nativeStore) {
12
+ nativeStore = react_native_nitro_modules_1.NitroModules.createHybridObject("ModelStore");
13
+ }
14
+ return nativeStore;
15
+ }
7
16
  /**
8
17
  * High-performance Model Registry for react-native-litert-lm.
9
18
  *
@@ -19,7 +28,7 @@ exports.ModelRegistry = {
19
28
  * @returns true if cached and has size > 0
20
29
  */
21
30
  isCached(pathOrUrl) {
22
- return nativeStore.isCached((0, modelPath_1.resolveModelFileName)(pathOrUrl));
31
+ return getStore().isCached((0, modelPath_1.resolveModelFileName)(pathOrUrl));
23
32
  },
24
33
  /**
25
34
  * Get the absolute local path of a cached model.
@@ -29,7 +38,7 @@ exports.ModelRegistry = {
29
38
  * @returns The absolute local path
30
39
  */
31
40
  getFilePath(pathOrUrl) {
32
- return nativeStore.getFilePath((0, modelPath_1.resolveModelFileName)(pathOrUrl));
41
+ return getStore().getFilePath((0, modelPath_1.resolveModelFileName)(pathOrUrl));
33
42
  },
34
43
  /**
35
44
  * List all locally cached model files.
@@ -37,7 +46,7 @@ exports.ModelRegistry = {
37
46
  * @returns Array of ModelFile descriptors containing path, size, and mod time
38
47
  */
39
48
  listCachedFiles() {
40
- return nativeStore.listCachedFiles();
49
+ return getStore().listCachedFiles();
41
50
  },
42
51
  /**
43
52
  * Delete a cached model file.
@@ -46,7 +55,7 @@ exports.ModelRegistry = {
46
55
  * @param pathOrUrl Filename, local path, or download URL to delete
47
56
  */
48
57
  deleteFile(pathOrUrl) {
49
- nativeStore.deleteFile((0, modelPath_1.resolveModelFileName)(pathOrUrl));
58
+ getStore().deleteFile((0, modelPath_1.resolveModelFileName)(pathOrUrl));
50
59
  },
51
60
  /**
52
61
  * Resolve a model path or URL.
@@ -73,7 +82,7 @@ exports.ModelRegistry = {
73
82
  throw new Error(`Invalid model URL: ${cleanPath}`);
74
83
  }
75
84
  const headersJson = options?.headers ? JSON.stringify(options.headers) : "{}";
76
- return nativeStore.downloadFile(cleanPath, fileName, headersJson, (progress) => {
85
+ return getStore().downloadFile(cleanPath, fileName, headersJson, (progress) => {
77
86
  options?.onProgress?.(progress);
78
87
  });
79
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dr33m/react-native-litert-lm",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "litertLm": {
5
5
  "version": "0.12.0",
6
6
  "androidMavenVersion": "0.12.0",
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import type {
8
8
  Role,
9
9
  GenerationStats,
10
10
  MemoryUsage,
11
+ ModelStore,
11
12
  } from "./specs/LiteRTLM.nitro";
12
13
 
13
14
  export type {
@@ -125,6 +126,36 @@ export function getRecommendedBackend(): Backend {
125
126
  return "cpu";
126
127
  }
127
128
 
129
+ let nativeAvailable: boolean | null = null;
130
+
131
+ /**
132
+ * Check whether the native LiteRT-LM module loaded successfully on this
133
+ * device. Some devices (unsupported ABI, missing native libs) can't load
134
+ * the native module at all — call this before showing any on-device AI UI
135
+ * so those devices degrade gracefully instead of throwing when a model is
136
+ * loaded. Result is cached after the first call.
137
+ *
138
+ * @returns true if native inference is available on this device
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * if (!isNativeAvailable()) {
143
+ * // hide/disable on-device AI features
144
+ * }
145
+ * ```
146
+ */
147
+ export function isNativeAvailable(): boolean {
148
+ if (nativeAvailable === null) {
149
+ try {
150
+ NitroModules.createHybridObject<ModelStore>("ModelStore");
151
+ nativeAvailable = true;
152
+ } catch {
153
+ nativeAvailable = false;
154
+ }
155
+ }
156
+ return nativeAvailable;
157
+ }
158
+
128
159
  /**
129
160
  * Check if a backend configuration is supported on the current platform.
130
161
  * Returns a warning message if the configuration may have issues.
@@ -9,7 +9,17 @@ export interface ModelDownloadOptions {
9
9
  onProgress?: (progress: number) => void;
10
10
  }
11
11
 
12
- const nativeStore = NitroModules.createHybridObject<ModelStore>("ModelStore");
12
+ let nativeStore: ModelStore | null = null;
13
+
14
+ // Lazily created — a module-scope createHybridObject() would throw at import
15
+ // time on devices where the native lib failed to load, crashing app startup
16
+ // before any capability check can run.
17
+ function getStore(): ModelStore {
18
+ if (!nativeStore) {
19
+ nativeStore = NitroModules.createHybridObject<ModelStore>("ModelStore");
20
+ }
21
+ return nativeStore;
22
+ }
13
23
 
14
24
  /**
15
25
  * High-performance Model Registry for react-native-litert-lm.
@@ -26,7 +36,7 @@ export const ModelRegistry = {
26
36
  * @returns true if cached and has size > 0
27
37
  */
28
38
  isCached(pathOrUrl: string): boolean {
29
- return nativeStore.isCached(resolveModelFileName(pathOrUrl));
39
+ return getStore().isCached(resolveModelFileName(pathOrUrl));
30
40
  },
31
41
 
32
42
  /**
@@ -37,7 +47,7 @@ export const ModelRegistry = {
37
47
  * @returns The absolute local path
38
48
  */
39
49
  getFilePath(pathOrUrl: string): string {
40
- return nativeStore.getFilePath(resolveModelFileName(pathOrUrl));
50
+ return getStore().getFilePath(resolveModelFileName(pathOrUrl));
41
51
  },
42
52
 
43
53
  /**
@@ -46,7 +56,7 @@ export const ModelRegistry = {
46
56
  * @returns Array of ModelFile descriptors containing path, size, and mod time
47
57
  */
48
58
  listCachedFiles(): ModelFile[] {
49
- return nativeStore.listCachedFiles();
59
+ return getStore().listCachedFiles();
50
60
  },
51
61
 
52
62
  /**
@@ -56,7 +66,7 @@ export const ModelRegistry = {
56
66
  * @param pathOrUrl Filename, local path, or download URL to delete
57
67
  */
58
68
  deleteFile(pathOrUrl: string): void {
59
- nativeStore.deleteFile(resolveModelFileName(pathOrUrl));
69
+ getStore().deleteFile(resolveModelFileName(pathOrUrl));
60
70
  },
61
71
 
62
72
  /**
@@ -89,7 +99,7 @@ export const ModelRegistry = {
89
99
 
90
100
  const headersJson = options?.headers ? JSON.stringify(options.headers) : "{}";
91
101
 
92
- return nativeStore.downloadFile(
102
+ return getStore().downloadFile(
93
103
  cleanPath,
94
104
  fileName,
95
105
  headersJson,