react-native-litert-lm 0.3.4 → 0.3.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.
package/README.md CHANGED
@@ -82,6 +82,8 @@ The `example/` directory contains a fully functional test app with a dark-themed
82
82
  npm install
83
83
  ```
84
84
 
85
+ > **Important:** Use `npm` (not `pnpm`) for the example app. The library is linked via `file:..`, and npm creates a symlink so the iOS XCFramework is visible to CocoaPods. pnpm copies files based on the `files` array and misses `ios/Frameworks/`.
86
+
85
87
  3. **Create a development build and run:**
86
88
 
87
89
  ```bash
@@ -452,7 +454,7 @@ const prompt = applyGemmaTemplate(
452
454
  | react-native-nitro-modules | 0.35.0+ |
453
455
  | Android API | 26+ (ARM64) |
454
456
  | iOS | 15.0+ (ARM64) |
455
- | LiteRT-LM Engine | 0.10.1 |
457
+ | LiteRT-LM Engine | 0.10.2 |
456
458
 
457
459
  ## Platform Support
458
460
 
@@ -491,7 +493,7 @@ Add to your app's `.entitlements` file:
491
493
 
492
494
  ## Building the iOS Engine
493
495
 
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).
496
+ 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
497
 
496
498
  ### Prerequisites
497
499
 
@@ -506,12 +508,12 @@ The iOS build uses a **Bazel-to-XCFramework pipeline** that compiles the LiteRT-
506
508
 
507
509
  This will:
508
510
 
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`
511
+ 1. Clone/checkout LiteRT-LM `v0.10.2` source into `.litert-lm-build/`
512
+ 2. Apply `scripts/patches/ios-engine-fixes.patch` (PromptTemplate simplification, linker fixes)
513
+ 3. Build `//c:engine` for `ios_arm64` and `ios_sim_arm64` via Bazel
514
+ 4. Collect all transitive `.o` files (engine, protobuf, re2, sentencepiece, etc.)
515
+ 5. Compile C/C++ stubs for unavailable Rust dependencies
516
+ 6. Merge ~1,909 object files into a static library via `libtool`
515
517
  7. Package into `ios/Frameworks/LiteRTLM.xcframework`
516
518
 
517
519
  ### Output
@@ -520,10 +522,10 @@ This will:
520
522
  ios/Frameworks/LiteRTLM.xcframework/
521
523
  ├── Info.plist
522
524
  ├── ios-arm64/LiteRTLM.framework/ # Device
523
- │ ├── LiteRTLM # ~81 MB static library
525
+ │ ├── LiteRTLM # ~82 MB static library
524
526
  │ └── Headers/litert_lm_engine.h
525
527
  └── ios-arm64-simulator/LiteRTLM.framework/ # Simulator
526
- ├── LiteRTLM # ~83 MB static library
528
+ ├── LiteRTLM # ~84 MB static library
527
529
  └── Headers/litert_lm_engine.h
528
530
  ```
529
531
 
@@ -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.5",
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>
@@ -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