Skip to content

chore: Release v0.8.4#1108

Merged
msluszniak merged 5 commits intorelease/0.8from
@ms/release-0.8.4
Apr 29, 2026
Merged

chore: Release v0.8.4#1108
msluszniak merged 5 commits intorelease/0.8from
@ms/release-0.8.4

Conversation

@msluszniak
Copy link
Copy Markdown
Member

@msluszniak msluszniak commented Apr 28, 2026

Summary

Patch release v0.8.4 — cherry-picks the following commits from main into release/0.8:

Checklist

  • Commits cherry-picked from main in chronological order (with -x)
  • Version bumped to 0.8.4 in packages/react-native-executorch/package.json
  • Adapter packages (bare-resource-fetcher, expo-resource-fetcher) untouched by cherry-picks — versions not bumped

Docs

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 corresponding docs/versioned_docs/version-0.8.x/... updates will be done in a separate PR targeting main after v0.8.4 is published to npm — that PR will also regenerate the v0.8.x api-reference snapshot so anchors for the new sampling fields resolve.

radko93 and others added 5 commits April 28, 2026 18:32
…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)
@msluszniak msluszniak marked this pull request as draft April 28, 2026 17:07
@msluszniak msluszniak self-assigned this Apr 28, 2026
@msluszniak msluszniak added the chore PRs that are chores label Apr 28, 2026
@msluszniak msluszniak marked this pull request as ready for review April 28, 2026 17:22
@msluszniak msluszniak requested review from NorbertKlockiewicz, chmjkb and mkopcins and removed request for chmjkb and mkopcins April 28, 2026 17:44
Copy link
Copy Markdown
Contributor

@NorbertKlockiewicz NorbertKlockiewicz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@msluszniak msluszniak merged commit bc0fec4 into release/0.8 Apr 29, 2026
2 of 4 checks passed
@msluszniak msluszniak deleted the @ms/release-0.8.4 branch April 29, 2026 08:16
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore PRs that are chores

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants