Skip to content

Commit 5c521a5

Browse files
barhancclaude
andauthored
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

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.cspell-wordlist.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ skiplist
400400
skiplisted
401401
startoftext
402402
maxsim
403+
multinomial
403404
adb
404405
benchmarkable
405406
benchprobe

‎apps/nlp/app/_layout.tsx‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ export default function Layout() {
5555
title: 'LLM',
5656
}}
5757
/>
58+
<Drawer.Screen
59+
name="constrained-decoding/index"
60+
options={{
61+
drawerLabel: 'Potion Lab',
62+
title: 'Constrained Decoding',
63+
}}
64+
/>
5865
</Drawer>
5966
);
6067
}

0 commit comments

Comments
 (0)