react-native-litert-lm 0.3.4 → 0.3.6

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.
package/README.md CHANGED
@@ -309,11 +309,11 @@ const buffer = tracker.getNativeBuffer();
309
309
 
310
310
  Download `.litertlm` models automatically using the exported URL constants, or manually from [HuggingFace](https://huggingface.co/litert-community):
311
311
 
312
- | Constant | Model | Size | Min RAM | Auth Required |
313
- | :--------------------- | :--------------------------------- | :------- | :------ | :------------ |
314
- | `GEMMA_4_E2B_IT` | Gemma 4 E2B (Multimodal, IT) | 2.58 GB | 4 GB+ | ❌ No |
315
- | `GEMMA_4_E4B_IT` | Gemma 4 E4B (Higher Quality) | 3.65 GB | 6 GB+ | ❌ No |
316
- | `GEMMA_3N_E2B_IT_INT4` | Gemma 3n E2B (Int4, Multimodal) | ~1.3 GB | 4 GB+ | ✅ HuggingFace |
312
+ | Constant | Model | Size | Min RAM | Auth Required |
313
+ | :--------------------- | :------------------------------ | :------ | :------ | :------------- |
314
+ | `GEMMA_4_E2B_IT` | Gemma 4 E2B (Multimodal, IT) | 2.58 GB | 4 GB+ | ❌ No |
315
+ | `GEMMA_4_E4B_IT` | Gemma 4 E4B (Higher Quality) | 3.65 GB | 6 GB+ | ❌ No |
316
+ | `GEMMA_3N_E2B_IT_INT4` | Gemma 3n E2B (Int4, Multimodal) | ~1.3 GB | 4 GB+ | ✅ HuggingFace |
317
317
 
318
318
  > **Recommended:** Use `GEMMA_4_E2B_IT` for most use cases. It's multimodal (text + vision + audio) and downloads directly from HuggingFace without requiring an account.
319
319
  >
@@ -452,7 +452,7 @@ const prompt = applyGemmaTemplate(
452
452
  | react-native-nitro-modules | 0.35.0+ |
453
453
  | Android API | 26+ (ARM64) |
454
454
  | iOS | 15.0+ (ARM64) |
455
- | LiteRT-LM Engine | 0.10.1 |
455
+ | LiteRT-LM Engine | 0.10.2 |
456
456
 
457
457
  ## Platform Support
458
458
 
@@ -491,7 +491,7 @@ Add to your app's `.entitlements` file:
491
491
 
492
492
  ## Building the iOS Engine
493
493
 
494
- The iOS build uses a **Bazel-to-XCFramework pipeline** that compiles the LiteRT-LM C engine and all transitive dependencies into a static library (~83 MB).
494
+ The iOS build uses a **Bazel-to-XCFramework pipeline** that compiles the LiteRT-LM C engine and all transitive dependencies into a static library (~82–84 MB).
495
495
 
496
496
  ### Prerequisites
497
497
 
@@ -506,12 +506,12 @@ The iOS build uses a **Bazel-to-XCFramework pipeline** that compiles the LiteRT-
506
506
 
507
507
  This will:
508
508
 
509
- 1. Clone/checkout LiteRT-LM `v0.10.1` source into `.litert-lm-build/`
510
- 2. Build `//c:engine` for `ios_arm64` and `ios_sim_arm64` via Bazel
511
- 3. Collect all transitive `.o` files (engine, protobuf, re2, sentencepiece, etc.)
512
- 4. Compile C/C++ stubs for unavailable Rust dependencies
513
- 5. Patch `PromptTemplate` to use a simplified template engine (no Rust MinijinjaTemplate)
514
- 6. Merge ~1,900 object files into a static library via `libtool`
509
+ 1. Clone/checkout LiteRT-LM `v0.10.2` source into `.litert-lm-build/`
510
+ 2. Apply `scripts/patches/ios-engine-fixes.patch` (PromptTemplate simplification, linker fixes)
511
+ 3. Build `//c:engine` for `ios_arm64` and `ios_sim_arm64` via Bazel
512
+ 4. Collect all transitive `.o` files (engine, protobuf, re2, sentencepiece, etc.)
513
+ 5. Compile C/C++ stubs for unavailable Rust dependencies
514
+ 6. Merge ~1,909 object files into a static library via `libtool`
515
515
  7. Package into `ios/Frameworks/LiteRTLM.xcframework`
516
516
 
517
517
  ### Output
@@ -520,10 +520,10 @@ This will:
520
520
  ios/Frameworks/LiteRTLM.xcframework/
521
521
  ├── Info.plist
522
522
  ├── ios-arm64/LiteRTLM.framework/ # Device
523
- │ ├── LiteRTLM # ~81 MB static library
523
+ │ ├── LiteRTLM # ~82 MB static library
524
524
  │ └── Headers/litert_lm_engine.h
525
525
  └── ios-arm64-simulator/LiteRTLM.framework/ # Simulator
526
- ├── LiteRTLM # ~83 MB static library
526
+ ├── LiteRTLM # ~84 MB static library
527
527
  └── Headers/litert_lm_engine.h
528
528
  ```
529
529
 
@@ -19,7 +19,7 @@ add_library(
19
19
  # loads the NitroModules shared library. This is required because we're
20
20
  # building a library that depends on NitroModules symbols which are only
21
21
  # available at runtime.
22
- set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-shlib-undefined")
22
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-shlib-undefined,-z,max-page-size=16384")
23
23
 
24
24
  # Include Nitrogen autolinking - this adds all generated sources and links
25
25
  include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/LiteRTLM+autolinking.cmake)
@@ -171,27 +171,40 @@ class HybridLiteRTLM : HybridLiteRTLMSpec() {
171
171
  else -> com.google.ai.edge.litertlm.Backend.CPU()
172
172
  }
173
173
 
174
- // Vision backend: hardcoded to GPU (required by Gemma models)
175
- val lmVisionBackend = com.google.ai.edge.litertlm.Backend.GPU()
176
-
177
- // Audio backend: hardcoded to CPU (optimal for audio processing)
178
- val lmAudioBackend = com.google.ai.edge.litertlm.Backend.CPU()
174
+ // Detect multimodal support from model filename.
175
+ // Only Gemma 3n bundles vision/audio executors; Gemma 4 E2B is text-only.
176
+ // Passing vision/audio backends to a text-only model causes
177
+ // vision_litert_compiled_model_executor init failures.
178
+ val modelFileName = modelPath.substringAfterLast("/").lowercase()
179
+ val isMultimodal = modelFileName.contains("3n") || modelFileName.contains("gemma3")
179
180
 
180
- Log.i(TAG, "Backend config: main=$lmBackend, vision=$lmVisionBackend (hardcoded), audio=$lmAudioBackend (hardcoded)")
181
+ val lmVisionBackend = if (isMultimodal) com.google.ai.edge.litertlm.Backend.GPU() else null
182
+ val lmAudioBackend = if (isMultimodal) com.google.ai.edge.litertlm.Backend.CPU() else null
183
+
184
+ Log.i(TAG, "Backend config: main=$lmBackend, vision=$lmVisionBackend, audio=$lmAudioBackend, multimodal=$isMultimodal")
181
185
 
182
186
  // Get cache directory from application context
183
187
  val cacheDirectory = LiteRTLMInitProvider.applicationContext?.cacheDir?.absolutePath
184
188
  Log.i(TAG, "Using cache directory: $cacheDirectory")
185
189
 
186
- // Create Engine configuration
187
- val engineConfig = EngineConfig(
188
- modelPath = modelPath,
189
- backend = lmBackend,
190
- visionBackend = lmVisionBackend,
191
- audioBackend = lmAudioBackend,
192
- maxNumTokens = maxTokens,
193
- cacheDir = cacheDirectory
194
- )
190
+ // Create Engine configuration — visionBackend/audioBackend are optional
191
+ val engineConfig = if (isMultimodal) {
192
+ EngineConfig(
193
+ modelPath = modelPath,
194
+ backend = lmBackend,
195
+ visionBackend = lmVisionBackend!!,
196
+ audioBackend = lmAudioBackend!!,
197
+ maxNumTokens = maxTokens,
198
+ cacheDir = cacheDirectory
199
+ )
200
+ } else {
201
+ EngineConfig(
202
+ modelPath = modelPath,
203
+ backend = lmBackend,
204
+ maxNumTokens = maxTokens,
205
+ cacheDir = cacheDirectory
206
+ )
207
+ }
195
208
 
196
209
  if (isClosed) return@synchronized
197
210
 
@@ -615,7 +628,19 @@ class HybridLiteRTLM : HybridLiteRTLMSpec() {
615
628
 
616
629
  private fun createNewConversation() {
617
630
  ensureLoaded()
618
- // Dispose old conversation if needed
631
+ // v0.10.2 enforces single-session: close existing conversation first
632
+ conversation?.let { oldConv ->
633
+ try {
634
+ if (oldConv is AutoCloseable) {
635
+ oldConv.close()
636
+ } else {
637
+ oldConv.javaClass.getMethod("close").invoke(oldConv)
638
+ }
639
+ } catch (e: Exception) {
640
+ Log.w(TAG, "Failed to close old conversation: ${e.message}")
641
+ }
642
+ conversation = null
643
+ }
619
644
  conversation = engine!!.createConversation()
620
645
  // Apply system prompt/instruction if set
621
646
  systemPrompt?.let { prompt ->
@@ -366,12 +366,7 @@ void HybridLiteRTLM::loadModelInternal(
366
366
  } else {
367
367
  diag += ", Readable: NO (errno: " + std::to_string(errno) + ")";
368
368
  }
369
-
370
- // Get the native error from the C API
371
- const char* nativeErr = litert_lm_get_last_error();
372
- if (nativeErr && nativeErr[0] != '\0') {
373
- diag += " | Native error: " + std::string(nativeErr);
374
- }
369
+
375
370
 
376
371
  throw std::runtime_error(
377
372
  "Failed to create LiteRT-LM engine. Tried backend '" +
@@ -602,12 +597,7 @@ std::string HybridLiteRTLM::sendMessageWithImageInternal(
602
597
  conversation_, msgJson.c_str(), nullptr);
603
598
 
604
599
  if (!response) {
605
- std::string errMsg = "LiteRT-LM: sendMessageWithImage failed";
606
- const char* nativeErr = litert_lm_get_last_error();
607
- if (nativeErr && nativeErr[0] != '\0') {
608
- errMsg += ": " + std::string(nativeErr);
609
- }
610
- throw std::runtime_error(errMsg);
600
+ throw std::runtime_error("LiteRT-LM: sendMessageWithImage failed");
611
601
  }
612
602
 
613
603
  const char* responseStr = litert_lm_json_response_get_string(response);
@@ -667,12 +657,7 @@ std::string HybridLiteRTLM::sendMessageWithAudioInternal(
667
657
  conversation_, msgJson.c_str(), nullptr);
668
658
 
669
659
  if (!response) {
670
- std::string errMsg = "LiteRT-LM: sendMessageWithAudio failed";
671
- const char* nativeErr = litert_lm_get_last_error();
672
- if (nativeErr && nativeErr[0] != '\0') {
673
- errMsg += ": " + std::string(nativeErr);
674
- }
675
- throw std::runtime_error(errMsg);
660
+ throw std::runtime_error("LiteRT-LM: sendMessageWithAudio failed");
676
661
  }
677
662
 
678
663
  const char* responseStr = litert_lm_json_response_get_string(response);
@@ -182,6 +182,15 @@ LITERT_LM_C_API_EXPORT
182
182
  void litert_lm_engine_settings_set_max_num_tokens(
183
183
  LiteRtLmEngineSettings* settings, int max_num_tokens);
184
184
 
185
+ // Sets whether the engine should load different sections of the litertlm file
186
+ // in parallel. Defaults to true.
187
+ //
188
+ // @param settings The engine settings.
189
+ // @param parallel_file_section_loading Whether to load in parallel.
190
+ LITERT_LM_C_API_EXPORT
191
+ void litert_lm_engine_settings_set_parallel_file_section_loading(
192
+ LiteRtLmEngineSettings* settings, bool parallel_file_section_loading);
193
+
185
194
  // Sets the cache directory for the engine.
186
195
  //
187
196
  // @param settings The engine settings.
@@ -236,14 +245,12 @@ void litert_lm_engine_settings_set_num_decode_tokens(
236
245
  // Returns an empty string if no error has occurred.
237
246
  // The returned pointer is valid until the next C API call on the same thread.
238
247
  LITERT_LM_C_API_EXPORT
239
- const char* litert_lm_get_last_error();
240
248
 
241
249
  // Creates a LiteRT LM Engine from the given settings. The caller is responsible
242
250
  // for destroying the engine using `litert_lm_engine_delete`.
243
251
  //
244
252
  // @param settings The engine settings.
245
253
  // @return A pointer to the created engine, or NULL on failure.
246
- // Call litert_lm_get_last_error() for details on failure.
247
254
  LITERT_LM_C_API_EXPORT
248
255
  LiteRtLmEngine* litert_lm_engine_create(const LiteRtLmEngineSettings* settings);
249
256
 
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "react-native-litert-lm",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "litertLm": {
5
- "version": "0.10.1",
6
- "androidMavenVersion": "0.10.0",
7
- "iosGitTag": "v0.10.1"
5
+ "version": "0.10.2",
6
+ "androidMavenVersion": "0.10.2",
7
+ "iosGitTag": "v0.10.2"
8
8
  },
9
9
  "description": "High-performance LLM inference for React Native using LiteRT-LM. Optimized for Gemma 4 and other on-device language models.",
10
10
  "license": "MIT",
@@ -267,7 +267,7 @@ for ARCH_NAME in "device" "simulator"; do
267
267
  <key>CFBundlePackageType</key>
268
268
  <string>FMWK</string>
269
269
  <key>CFBundleShortVersionString</key>
270
- <string>0.9.0</string>
270
+ <string>0.10.2</string>
271
271
  <key>CFBundleVersion</key>
272
272
  <string>1</string>
273
273
  <key>MinimumOSVersion</key>
@@ -103,13 +103,20 @@ async function main() {
103
103
 
104
104
  log('iOS frameworks installed successfully.');
105
105
  } catch (err) {
106
- // Don't fail the install — iOS frameworks are optional (Android-only users)
107
- log(`Warning: Could not download iOS frameworks: ${err.message}`);
108
- log('iOS builds will not work until frameworks are available.');
109
- log('Run: scripts/download-ios-frameworks.sh to download manually.');
110
-
111
106
  // Cleanup partial download
112
107
  try { fs.unlinkSync(tmpZip); } catch {}
108
+
109
+ log(`Error: Could not download iOS frameworks: ${err.message}`);
110
+ log('iOS builds will not work until frameworks are available.');
111
+ log('Run: ./scripts/download-ios-frameworks.sh to download manually,');
112
+ log(' or: ./scripts/build-ios-engine.sh to build from source.');
113
+
114
+ // Fail fast on macOS so users discover the problem now, not at Xcode link time.
115
+ // Skip SKIP_IOS_FRAMEWORK_DOWNLOAD is already checked above.
116
+ if (process.platform === 'darwin') {
117
+ log('Set SKIP_IOS_FRAMEWORK_DOWNLOAD=1 to suppress this error (e.g. Android-only builds).');
118
+ process.exit(1);
119
+ }
113
120
  }
114
121
  }
115
122
 
@@ -1,30 +0,0 @@
1
- ///
2
- /// LiteRTLMAutolinking.mm
3
- /// Registers the C++ HybridLiteRTLM implementation with NitroModules on iOS.
4
- ///
5
- /// On iOS, there's no JNI_OnLoad equivalent, so we use ObjC +load to register
6
- /// the HybridObject factory before JS tries to create it.
7
- ///
8
-
9
- #import <Foundation/Foundation.h>
10
- #include <NitroModules/HybridObjectRegistry.hpp>
11
- #include "HybridLiteRTLM.hpp"
12
-
13
- @interface LiteRTLMAutolinking : NSObject
14
- @end
15
-
16
- @implementation LiteRTLMAutolinking
17
-
18
- + (void)load {
19
- using namespace margelo::nitro;
20
- using namespace margelo::nitro::litertlm;
21
-
22
- HybridObjectRegistry::registerHybridObjectConstructor(
23
- "LiteRTLM",
24
- []() -> std::shared_ptr<HybridObject> {
25
- return std::make_shared<HybridLiteRTLM>();
26
- }
27
- );
28
- }
29
-
30
- @end