Repository navigation
Commit 5c521a5
refactor(llm)!: migrate to modular TypeScript runners and reexport models with expanded context lengths (#1508)
## Description
This PR undertakes an architectural overhaul of the LLM execution
pipeline in `react-native-executorch`. The monolithic native C++
`llm_runner.cpp` has been replaced with specialized, modular TypeScript
runners that drive the prefill and autoregressive decode loops in JS
directly on top of core primitives. In tandem, sampling is now delegated
to a dedicated native C++ kernel (`sampler.cpp`) that replicates the
`llama.cpp` sampling pipeline with in-place logits mutations and unlocks
zero-overhead constrained decoding.
Additionally, model registry definitions have been updated to point to
newly re-exported models with expanded context windows (up to 16k
tokens) and embedded companion reflection schemas, and the NLP example
app has been upgraded with model family-tailored sampling
configurations.
### Key Highlights & Architectural Breakdown
#### 1. TypeScript LLM Runners Architecture
* **Removed Native C++ Runner (`cpp/extensions/llm/llm_runner.*`)**:
Removed the legacy C++ runner class and its associated test suite.
* **Modular Runners (`src/extensions/llm/runners/`)**:
* **`llmTextRunner.ts`**: Handles text-only autoregressive models
driving `token_embedding` and sequential/chunked prefill and
single-token decode.
* **`llmMultimodalRunner.ts`**: Handles interleaved multimodal models
(e.g. LFM 2.5 VL) supporting image prefill embedding projections
alongside text token streams.
* **`llmGemmaRunner.ts`**: Dedicated runner for Gemma models with
specialized position encoding and multimodal projection.
* **Polymorphic Dispatch (`createLLMRunner`)**:
* Inspects the model methods schema via `validateSpec` and automatically
instantiates the matching runner (`text`, `vision`, or `gemma`).
#### 2. Native C++ Sampler Kernel (`cpp/extensions/llm/sampler.cpp`) &
Constrained Decoding
#### Why the Sampler Had to be Implemented in C++:
1. **Large Vocabulary Overhead Across the JSI Boundary**:
Modern LLMs have vocabulary sizes ranging from $32\text{k}$ up to
$151\text{k}+$ tokens (e.g., Qwen/Hammer at $151\,665$ tokens).
Serializing, copying, and sorting/filtering arrays of this size in
JavaScript during every single decode token step introduces unacceptable
Garbage Collection churn, JSI transfer overhead, and token latency
regressions.
2. **In-Place Logits Mutation**:
The native C++ kernel operates directly in-place on the output logits
buffer backing memory, eliminating tensor allocations entirely during
generation.
3. **Execution Order Alignment with `llama.cpp`**:
The pipeline implements the full battle-tested `llama.cpp` sampling
sequence with native performance.
#### Constrained Decoding Capabilities:
By exposing a native constraint masking phase at the very head of the
sampler chain, the pipeline now natively supports **dynamic constrained
decoding**:
* **Whitelisting & Blacklisting**: Token masks (`allowedTokens` /
`bannedTokens`) are applied directly in C++ by masking logits to
$-\infty$ prior to penalties and softmax.
* **Grammars, JSON Schemas & Regex Engines**: Higher-level libraries,
CFG parsers, or user callbacks can feed token constraints per step via
`SamplingConstraintsCallback`, enabling guaranteed structured JSON
generation, regex-guided output, and strict tool-call schema compliance
with zero intermediate tensor allocations.
#### 3. Re-Exported Models with Increased Context Lengths (`models.ts`)
Model definitions in `src/models.ts` have been aligned with the
re-exported models:
* **Companion Schemas & Logits Cast**:
* Re-exported `.pte` artifacts now embed validated `get_model_schema`
method declarations.
* All re-exported models now conform to single schema.
### Introduces a breaking change?
- [x] Yes
- [ ] No
**Task Pipeline & Public API**: slight changes to the runner API that
was marked `@experimental`, higher-level chat session API and API not
marked as experimental **remains fully backward compatible**)
**Removal of Legacy Unquantized XNNPACK FP16 / BF16 Builds**: legacy
unquantized XNNPACK `fp16` and `bf16` variants have been retired and
removed from active model registry definitions and Hugging Face model
cards. These builds were impractical on mobile hardware due to excessive
memory consumption and slow memory-bandwidth-bound decode loops.
### 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)
- [x] Other (chores, tests, code style improvements etc.)
### Tested on
- [x] iOS
- [x] Android
### Testing instructions
- [x] All 38 test suites (4044 tests) pass cleanly across TypeScript API
tests and mock JSI environments.
- [x] Native sampler test suites passed via `LlmOpsTest.cpp`.
- [x] TypeScript type checks.
- [ ] Run LLM example app on-device (both Android and iOS) and
empirically test at least one model from each family. Pay special
attention to Gemma models, and MLX / Vulkan exports as these are the
most fragile.
- [x] Inspect all public HF repos in the LLM collection:
https://huggingface.co/collections/software-mansion/llm-multimodal after
update.
### Screenshots
<!-- Add screenshots here, if applicable -->
### Related issues
Closes #1458 #1483 #1377 #1351
### 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
<!-- Include any additional information, assumptions, or context that
reviewers might need to understand this PR. -->
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>1 parent e05269a commit 5c521a5
48 files changed
Lines changed: 5815 additions & 1341 deletions
File tree
- apps/nlp
- app
- constrained-decoding
- llm
- constants
- docs/docs
- 01-fundamentals
- 02-extensions/natural-language
- 03-core-and-advanced
- packages/react-native-executorch
- __tests__
- api
- __snapshots__
- extensions
- support
- tasks
- cpp
- core
- extensions
- llm
- nlp
- tests
- core
- extensions
- src
- extensions
- llm
- runners
- tasks
- utils
- nlp
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
400 | 400 | | |
401 | 401 | | |
402 | 402 | | |
| 403 | + | |
403 | 404 | | |
404 | 405 | | |
405 | 406 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
58 | 65 | | |
59 | 66 | | |
60 | 67 | | |
0 commit comments