Skip to content

fix(llm): pin the model load mode instead of inheriting upstream defaults - #1492

Open
msluszniak wants to merge 1 commit into
mainfrom
@ms/llm-mmap-load
Open

msluszniak wants to merge 1 commit into
mainfrom
@ms/llm-mmap-load

Conversation

@msluszniak

@msluszniak msluszniak commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Description

Neither factory was called with a load mode, so both inherited upstream's, and the two defaults are wrong in different ways.

create_multimodal_runner defaults to LoadMode::File, which reads the whole .pte into a heap buffer. With gemma4_e2b_mlx_int4.pte (2.9 GB) that walks phys_footprint up in ~93 MB steps to 3296 MB and jetsam kills the app during load.

create_text_llm_runner defaults to MmapUseMlockIgnoreErrors, which leaves footprint alone but wires the model resident. That is the number DeviceInfo.getUsedMemory() and every other memory API reachable from JS reports, so the new API looked like it used 6x the memory of the legacy one on the same model.

Pin Mmap for both, which is what the legacy binding has always passed.

Introduces a breaking change?

  • Yes
  • No

Type of change

  • 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

Testing instructions

iPhone 16 Release, gemma-4-e2b-mlx, after load():

load mode footprint resident
File dies climbing past 3296 MB n/a
MmapUseMlockIgnoreErrors (old text default) 930-954 MB 3135-3282 MB
Mmap (this PR, and legacy) 950-952 MB 355-475 MB

Related issues

#1489

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

Neither mode changes what jetsam sees on the text path, so this is not the fix for #1489's prefill growth. It is what makes the new API's reported memory match the legacy API's.

@msluszniak msluszniak self-assigned this Sep 24, 2026
@msluszniak msluszniak added the bug fix PRs that are fixing bugs label Sep 24, 2026
@msluszniak
msluszniak requested a review from barhanc September 24, 2026 13:15
@msluszniak
msluszniak marked this pull request as ready for review September 24, 2026 13:15

@barhanc barhanc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just a comment nit.

Comment thread packages/react-native-executorch/cpp/extensions/llm/llm_runner.cpp Outdated
…ults

Neither factory was called with a load mode, so both inherited upstream's,
and the two defaults are wrong in different ways.

The multimodal one defaults to LoadMode::File, which reads the whole .pte
into a heap buffer: with gemma4_e2b_mlx_int4.pte that walks phys_footprint
past 3.3 GB and jetsam kills the app during load.

The text one defaults to MmapUseMlockIgnoreErrors, which leaves footprint
alone but wires the model resident: resident_size reads 3135-3282 MB against
355-475 MB for plain Mmap. That is the number DeviceInfo.getUsedMemory() and
every other memory API reachable from JS reports, so the new API appeared to
use 6x the memory of the legacy one on the same model.

Pin Mmap for both, which is what the legacy binding has always passed.
@msluszniak msluszniak changed the title fix(llm): stop the multimodal runner reading the whole model into memory fix(llm): pin the model load mode instead of inheriting upstream defaults Sep 24, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug fix PRs that are fixing bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants