Repository navigation
Conversation
cf97f59 to
14a6fff
Compare
|
@msluszniak Everything except the |
|
Ok, regarding testing:
|
|
Native Sampler tests:
|
|
Typecheck:
|
NaNs can't pass from TS as the
The problem is that there is no clean way to do this upfront, since whole dry/xtc objects are optional, so we would have to define quite a lot of optional C++ values at the top. Imo not parsing upfront isn't that big of a problem, wrong config isn't something that the user can recover from so prior logits modification is fine imo.
Will fix this.
Not worth it imo. The transformer inference itself is O(n^2) and I extensively tested the sampling pipeline---in release build it amounts to < 1% of total inference time.
Will add those. |
This is deliberate. LLM runner has
Will fix. |
I think you are only partially right here and it indeed works for integers, but nor for floats.
I though more about:
It only fails on a fresh clone. I meant a situation when |
|
I've addressed the review findings regarding code implementation. I'll fix the HF now.
|
|
I'm in the middle of the model sweep but one finding: |
|
More from the on-device sweep:
|
Fixed in Llama's tool parser in example app.
Fixed.
Not sure this is in the scope of this PR, the example app is a crude one for general testing. Handling this would require special logic just for these 3 models.
Same as above. |
|
iOS (iPhone 16, MLX) follow-up, checked on host with the published
|
|
Checked with One issue in the PR: runner Compatibility only (RAG side): |
|
@msluszniak I've updated PR description, fixed HF model cards and published missing Gemma model. Everything is ready for next pass.
Fixed.
Fixed.
Fixed.
Added context length columns.
Fixed.
Fixed. It's
Fixed. Now all files are named |
|
@barhanc okay, I will check that tommorow with a focus on gemma runner. |
|
Second pass, Gemma runner:
|
|
Android (S26 Ultra), Gemma:
|
…ol parsing - Check eosIds before emitting to onToken and pushing to generatedTokens across text, gemma, and multimodal runners - Support empty array [] tool call responses in parseHammerToolCalls - Strip <|python_tag|> in parseLlamaToolCalls
…d NLP example app
848bbfe to
a715834
Compare
Fixed. I've also added the LFM 2.5 2.6B model (with 8k context), so you can test it as well. |
|
Thanks @barhanc, the docs for local paths are fine for us, we'll just strip file:// from the picker URIs on our side. BOS: I think we got our wires crossed. The id is fine, the problem is that the variable never gets to the template. Llama 3.2's template starts with {{- bos_token }}, but chatPreprocessor.process only passes tools, messages and add_generation_prompt to render(). So bos_token renders as an empty string, and the tokenizer doesn't add it later either (kNumAddedBosTokens = 0). On the device the Llama prompt starts with <|start_header_id|>system… and the ids go 128006, 9125, … with no 128000 in front. Gemma 4, LFM 2.5 and Bielik have the same issue. Passing bos_token / eos_token from tokenizer_config.json into render() should fix all four, which is what HF's apply_chat_template does. Stop: nice, the session rollback works. One thing is left in the runner: prefill() and generate() still reset isCancelled on entry. Since we're going with the raw runner, a stop that lands right before one of those calls gets lost on our side. In the session, a stop that comes before the prefill starts still waits for the whole prompt to be read before it rolls back, and for Gemma under memory pressure that can be minutes. If the flag were cleared once per turn instead of on entry, both would be fixed. And a small one: a cancelled turn returns finishReason: 'stop', so a 'cancelled' value would make it easier to tell the two apart. Seed: seed++ per call doesn't help within one reply, because generate() passes the same config to sample() at every step (llmTextRunner.ts:195), so every token is drawn at the same quantile. Doing seed + step inside generate() would fix it, and sample stays pure. From my side that's it for this PR. Maybe one more thing: a 'length' finish reason at the context limit, if it's not much work. |
Added.
As per docs, the runner's stop is there to stop active inference, not to schedule a cancellation. In chat session stop schedules an eventual cancellation and a stop that comes before first prefill is a very unlikely timing. Added the
Fixed. |
msluszniak
left a comment
There was a problem hiding this comment.
Tested new lfm2.5 2.6B model on iOS with XNNPACK and MLX and worked correctly on my tests :))


Description
This PR undertakes an architectural overhaul of the LLM execution pipeline in
react-native-executorch. The monolithic native C++llm_runner.cpphas 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 thellama.cppsampling 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
cpp/extensions/llm/llm_runner.*): Removed the legacy C++ runner class and its associated test suite.src/extensions/llm/runners/):llmTextRunner.ts: Handles text-only autoregressive models drivingtoken_embeddingand 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.createLLMRunner):validateSpecand automatically instantiates the matching runner (text,vision, orgemma).2. Native C++ Sampler Kernel (
cpp/extensions/llm/sampler.cpp) & Constrained DecodingWhy the Sampler Had to be Implemented in C++:
Modern LLMs have vocabulary sizes ranging from
The native C++ kernel operates directly in-place on the output logits buffer backing memory, eliminating tensor allocations entirely during generation.
llama.cpp:The pipeline implements the full battle-tested
llama.cppsampling 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:
allowedTokens/bannedTokens) are applied directly in C++ by masking logits toSamplingConstraintsCallback, 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.tshave been aligned with the re-exported models:.pteartifacts now embed validatedget_model_schemamethod declarations.Introduces a breaking change?
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
fp16andbf16variants 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
Tested on
Testing instructions
LlmOpsTest.cpp.Screenshots
Related issues
Closes #1458 #1483 #1377 #1351
Checklist
Additional notes