chore: Release v0.8.4#1108
Merged
msluszniak merged 5 commits intorelease/0.8from Apr 29, 2026
Merged
Conversation
…oid) (#1067) ## Description On 32-bit Android devices (or devices with 64-bit CPUs running 32-bit userspace), the `arm64-v8a` native `.so` files are not found. `ETInstaller.init` throws `RuntimeException` wrapping `UnsatisfiedLinkError`, crashing the app at startup with no consumer-side escape hatch. This PR adds a fallback TurboModule (`ETInstallerUnavailable`) whose `install()` returns `false`. `RnExecutorchPackage` returns it only when the native library fails to load, so JS sees a real linked module but JSI bindings are never injected. This preserves the existing linking-error Proxy for genuinely mislinked installs on supported devices. **Unsupported ABI**: real module exists → `install()` returns `false` → globals not set → `isAvailable` is `false` **Supported but mislinked**: module is `null` → existing Proxy throws linking error (unchanged) No public API break; preserves existing mislink failure behavior. `isAvailable` is a new additive export. ### Introduces a breaking change? - [x] No ### Type of change - [x] Bug fix (change which fixes an issue) ### Tested on - [x] Android ### Testing instructions 1. Build and run on a 32-bit Android device (e.g. Galaxy A13, Moto G Play 2023) 2. App should start without crashing 3. `isAvailable` should be `false` 4. Verify on a supported 64-bit device that everything works as before ### Related issues Fixes #1065 (cherry picked from commit 8f20cee)
## Description
`LLMController.forward` passed `imagePaths` straight through to
`nativeModule.generateMultimodal` with no normalization. The native side
requires the `file://` prefix; without it, native throws `"Read image
error: invalid argument"` with no further context. Callers can plausibly
arrive with either form:
- `ResourceFetcher.fetch` returns raw paths *without* `file://` (per its
own docstring on the `fetch` method).
- Platform image-picker APIs (e.g. `expo-image-picker`) typically return
`file:///...` URIs.
- The same path string passed to a vision module's `forward(...)` works
either way; the asymmetry between vision modules and multimodal LLM is
undocumented.
This PR normalizes each image path inside `LLMController.forward` so
both forms work, and updates the JSDoc on `Message.mediaPath` and
`LLMModule.forward.imagePaths` to document the new contract.
### Introduces a breaking change?
- [ ] Yes
- [x] No
Strictly additive: previously-working calls (paths with `file://`) keep
working unchanged. Previously-failing calls (paths without `file://`)
now succeed.
### Type of change
- [x] Bug fix (change which fixes an issue)
- [ ] New feature
- [ ] Documentation update
- [ ] Other
### Tested on
- [ ] iOS
- [ ] Android
The bare-path failure was reproduced on Android (Samsung Galaxy S24
Ultra) with LFM2-VL-1.6B while building a downstream consumer; both
forms tested manually post-fix on the same device. Re-verification of
both forms on iOS is recommended.
### Testing instructions
```ts
import { LLMModule, LFM2_VL_1_6B_QUANTIZED } from 'react-native-executorch';
const llm = await LLMModule.fromModelName(LFM2_VL_1_6B_QUANTIZED);
// Both should now work; previously only the first did.
await llm.generate([
{ role: 'user', content: 'Describe.', mediaPath: 'file:///absolute/path/to/img.jpg' },
]);
await llm.generate([
{ role: 'user', content: 'Describe.', mediaPath: '/absolute/path/to/img.jpg' },
]);
```
### Related issues
Addresses item 3 of #1086.
### Checklist
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have updated the documentation accordingly
- [ ] My changes generate no new warnings
### Additional notes
The normalizer is module-scope (matching `messagesForChatTemplate` from
#1089) rather than a class method because it doesn't depend on
controller state.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit e8d4305)
…1089) ## Description `LLMController.generate()` collected `imagePaths` from messages with a `mediaPath` set, but never transformed their `content` into the `[{type:'image'}, {type:'text', text}]` form that the chat template needs to emit the `<image>` placeholder. Calling `generate()` directly with a vision-capable model (e.g. LFM2-VL) thus threw `"More images paths provided than '<image>' placeholders in prompt"` from native, even though `sendMessage()` worked because it built its own `historyForTemplate` that did the transformation. This PR moves the transformation into `applyChatTemplate` so both call sites (`generate` and `sendMessage`) get the correct behavior, and removes the now-redundant `historyForTemplate` block from `sendMessage`. The public `Message.content` type stays `string` — external callers always pass plain strings; the controller handles the structured array form internally. The helper is idempotent: messages whose `content` is already an array (e.g. callers who pre-shaped it as a workaround) are passed through unchanged. ### Introduces a breaking change? - [ ] Yes - [x] No Public types are unchanged. `sendMessage` produces an identical rendered chat-template string (the transformation just happens one step later in the pipeline; token count and rendered output are byte-identical). `generate` only changes behavior in cases that previously threw — pure bug fix. ### Type of change - [x] Bug fix (change which fixes an issue) - [ ] New feature (change which adds functionality) - [ ] Documentation update (improves or adds clarity to existing documentation) - [ ] Other (chores, tests, code style improvements etc.) ### Tested on - [ ] iOS - [ ] Android The original bug was reproduced on a vision-capable model (LFM2-VL-1.6B-quantized) on Android while building a downstream consumer app. Re-verification of the fix on a real device is recommended before merge — see Testing instructions below. ~I have not personally re-run the failing scenario after the fix.~ ### Testing instructions To reproduce the original bug (without this PR): ```ts import { LLMModule, LFM2_VL_1_6B_QUANTIZED } from 'react-native-executorch'; const llm = await LLMModule.fromModelName(LFM2_VL_1_6B_QUANTIZED); await llm.generate([ { role: 'user', content: 'Describe this image.', mediaPath: 'file:///path/to/image.jpg' }, ]); // Throws: "More images paths provided than '<image>' placeholders in prompt" ``` With this PR applied, the same call should succeed and return the model's description. Regression check: a vision-capable `sendMessage(text, { imagePath })` flow should continue producing identical output. ### Screenshots N/A (controller change, no UI). ### Related issues Addresses items 1 and 2 of #1086. With item 1 fixed, item 2's `Message.content` type mismatch no longer surfaces in practice because external callers never need to construct the array form themselves (the `as unknown as string` workaround that motivated #2 becomes unnecessary). ### Checklist - [ ] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have updated the documentation accordingly - [ ] My changes generate no new warnings ### Additional notes The `messagesForChatTemplate` helper lives at module scope rather than as a static class method because it doesn't depend on controller state. Internal `any[]` return is a deliberate concession to the dynamic shape the chat-template engine accepts; the public `Message[]` input/output contract stays well-typed. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit b271aa6)
… letterbox vision (#1099) ## Description Adds `min_p` and `repetition_penalty` sampling parameters to `GenerationConfig`, plumbs them through the full stack (`Sampler` → `TextDecoderRunner` → `TextTokenGenerator` → `BaseLLMRunner` / `TextRunner` / `MultimodalRunner` → JSI bindings → `LLMController`), introduces a per-model default `generationConfig` that gets applied automatically on load (populated for Qwen3 and LFM2-VL from their upstream recommendations), and replaces the distorting `cv::resize` in `VisionEncoder` with the existing `resizePadded` helper so multimodal inputs keep their aspect ratio. Also fixes three silent pre-existing bugs surfaced along the way: an xorshift PRNG seeded with `0` that made sampling deterministic, a `Sampler::apply_min_p` renormalization gap, and inline `{}` no-op overrides in `MultimodalRunner` that would desync in future refactors. ### Introduces a breaking change? - [ ] Yes - [x] No ### Type of change - [x] Bug fix (change which fixes an issue) - [x] New feature (change which adds functionality) - [ ] Documentation update (improves or adds clarity to existing documentation) - [ ] Other (chores, tests, code style improvements etc.) ### Tested on - [x] iOS - [ ] Android ### Testing instructions **Sampling parameter plumbing** 1. Open `apps/llm`, load any supported model (e.g. `LFM2_VL_450M_QUANTIZED`). 2. Without any manual `configure()` call, send a prompt. The model card defaults are applied automatically — for LFM2-VL you should now see coherent, non-repetitive descriptions (previously the model often produced generic or looping replies at the library's default `temperature=0.8, topp=0.9`). 3. Optionally override via `useLLM(...)`'s `configure({ generationConfig: { temperature: 0.7, minP: 0.1, repetitionPenalty: 1.05 } })` and confirm the generation style changes. **Letterbox preprocessing** 1. With a multimodal model loaded in `apps/llm` → `multimodal_llm` screen, attach a photo with a non-square aspect ratio (e.g. 3000×2250 from your camera roll). 2. Ask the model to describe it. Before this PR the image was stretched into the PTE's square input shape — the model would sometimes misidentify subjects in wide/tall photos. After, the image is letterboxed so proportions are preserved. ### Screenshots <!-- none --> ### Related issues <!-- none --> ### Checklist - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have updated the documentation accordingly - [x] My changes generate no new warnings ### Additional notes **Per-model recommended defaults** Model presets gain an optional `generationConfig` field; `LLMController.load` applies it before flipping `isReady`, so users see sensible sampling out of the box. User `configure()` calls still override per-field. Populated for: - **Qwen3** family (`temperature=0.6, topp=0.95`, from `generation_config.json`) - **LFM2-VL** family (`temperature=0.1, minP=0.15, repetitionPenalty=1.05`, from the LiquidAI model card) Other presets (Llama, SmolLM2, Hammer, Phi-4, Qwen2.5, LFM2 text) keep the library defaults — these model cards don't publish sampling recommendations, so adding arbitrary values would be guessing. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> (cherry picked from commit 9f1c89f)
cc7f866 to
78e7fd7
Compare
12 tasks
msluszniak
added a commit
that referenced
this pull request
Apr 29, 2026
## Description Backports the sampling and multimodal-rename doc edits from #1099 into the v0.8.x useLLM.md and LLMModule.md pages, plus a JSDoc fence fix on `useInstanceSegmentation.ts`. New `minP` / `repetitionPenalty` / `topP` field names are rendered as plain inline code rather than anchor links, since the v0.8.x `GenerationConfig.md` snapshot doesn't have those entries. ### Introduces a breaking change? - [ ] Yes - [x] No ### Type of change - [ ] Bug fix (change which fixes an issue) - [ ] New feature (change which adds functionality) - [x] Documentation update (improves or adds clarity to existing documentation) - [ ] Other (chores, tests, code style improvements etc.) ### Tested on - [ ] iOS - [ ] Android ### Testing instructions `yarn build` in `docs/`. ### Screenshots ### Related issues Follow-up to #1108 / `v0.8.4`. ### Checklist - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [x] I have updated the documentation accordingly - [x] My changes generate no new warnings ### Additional notes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Patch release v0.8.4 — cherry-picks the following commits from
mainintorelease/0.8:Checklist
mainin chronological order (with-x)0.8.4inpackages/react-native-executorch/package.jsonbare-resource-fetcher,expo-resource-fetcher) untouched by cherry-picks — versions not bumpedDocs
The unversioned doc edits that landed via the #1099 cherry-pick belong on the "Next" version (i.e.
main) and are already there from the original PR. The correspondingdocs/versioned_docs/version-0.8.x/...updates will be done in a separate PR targetingmainafterv0.8.4is published to npm — that PR will also regenerate the v0.8.x api-reference snapshot so anchors for the new sampling fields resolve.