@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
|
|
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
|
|
666
|
-
//
|
|
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.
|
package/lib/modelRegistry.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
85
|
+
return getStore().downloadFile(cleanPath, fileName, headersJson, (progress) => {
|
|
77
86
|
options?.onProgress?.(progress);
|
|
78
87
|
});
|
|
79
88
|
}
|
package/package.json
CHANGED
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.
|
package/src/modelRegistry.ts
CHANGED
|
@@ -9,7 +9,17 @@ export interface ModelDownloadOptions {
|
|
|
9
9
|
onProgress?: (progress: number) => void;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
102
|
+
return getStore().downloadFile(
|
|
93
103
|
cleanPath,
|
|
94
104
|
fileName,
|
|
95
105
|
headersJson,
|