Skip to content

fix(llm): chunk prefill at the bound the graph declares - #1491

Merged
msluszniak merged 3 commits into
mainfrom
@ms/llm-prefill-bound
Sep 25, 2026
Merged

msluszniak merged 3 commits into
mainfrom
@ms/llm-prefill-bound

Conversation

@msluszniak

@msluszniak msluszniak commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Description

Upstream sizes prefill chunks from get_max_seq_len, which is the KV context budget rather than the widest tensor the graph accepts. TextPrefiller then never chunks, and any prompt past the real bound fails in internal_resize_contiguous with Error::NotSupported. Both gemma4 builds with a sliding window disagree:

model get_max_seq_len real forward bound
gemma4_e2b_mlx_int4 2048 511
gemma_4_e2b_xnnpack_8da4w 4048 1024

We now read the real bound from method_meta(...).input_tensor_meta(0) and lower the prefiller's chunk size to it, which is what the legacy runner has always done. MultimodalPrefiller has no chunking at all, so its text inputs are split into TOKENS pieces instead.

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

apps/nlp, gemma-4-e2b-mlx, iPhone 16 Release. Send a prompt over 511 tokens (~6000 chars). A/B on the same binary:

prompt before after
407 tokens ok ok
1012 tokens Error::NotSupported, KV position does not move ok, 1495 MB peak, coherent output

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

Not gated on a backend. The clamp is a no-op when the two numbers agree, which is the case for the Vulkan gemma4 build (128/128) and for every other LLM checked, where the bound is just max_seq_len - 1.

The legacy runner reads the same metadata but only when uses_backend("MLXBackend"), so it is still exposed on gemma4 XNNPACK.

…seq_len

Upstream sizes its prefill chunks from `get_max_seq_len`, which is the KV
context budget rather than the widest tensor the graph accepts.
gemma4_e2b_mlx_int4 advertises 2048 while its `forward` token input is
DYNAMIC_BOUND to 511, so nothing ever chunks and the first prompt past 511
tokens fails in TensorImpl::internal_resize_contiguous with
Error::NotSupported. The Vulkan gemma4 export bounds at 128.

Read the real capacity off the method metadata, the way the legacy runner
already does, and lower the prefiller's chunk size to it. The multimodal
runner has no chunking at all, so split its text inputs to fit instead.

@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.

The comments should be cleaned a bit as well. Other than that it looks correct.

Comment on lines +180 to +181
// Text that fits is left as text, because upstream only echoes an input it can
// still read back as a string.

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.

Regarding this comment, I think we should delete the echo option from LLMRunner as now it sometimes works and sometimes doesn't when the input gets chunked.

Chunked prompts reach the runner as token inputs, which upstream cannot
echo, so echo only worked for short prompts.
@msluszniak
msluszniak requested a review from barhanc September 25, 2026 13:33
@msluszniak
msluszniak merged commit 10bfbcf into main Sep 25, 2026
7 checks passed
@msluszniak
msluszniak deleted the @ms/llm-prefill-bound branch September 25, 2026 13:53
msluszniak added a commit that referenced this pull request Sep 25, 2026
…1494) (#1497)

## Description

Cherry-picks five fixes from `main` onto `release/0.10` for the v0.10.3
patch, and pins `nativeLibsVersion` to `0.10.4`.

- 3bf8168 `fix(libs): stop shipping backends the app opted out of`
(#1466)
- e76e0b9 `fix(llm): keep every terminal token out of the chat
response` (#1486)
- daafa64 `fix(llm): pin the model load mode instead of inheriting
upstream defaults` (#1492)
- 10bfbcf `fix(llm): chunk prefill at the bound the graph declares`
(#1491)
- 8406b98 `fix(install): make the native lib download work on Windows`
(#1494)

`v0.10.4-libs` carries the XNNPACK weights-cache fixes (PReLU
use-after-free, and the retained weights that got Kokoro XNNPACK killed
on iPhone). Docs hunks from #1466 and #1494 are left out, per the
patch-release rule. Every touched file matches `main` except
`package.json` and `download-libs.js`, which lacks #1479's Vulkan task
map.

### Introduces a breaking change?

- [ ] Yes
- [x] No

### 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

- [x] iOS
- [x] Android

### Testing instructions

- `yarn jest __tests__`, `yarn typecheck`, `yarn lint`,
`scripts/run-native-tests.sh`
- `apps/speech` Release on iPhone 16 and Galaxy S26 Ultra: Kokoro EN_US
XNNPACK synthesizes, and a 12-layer PReLU `.pte` matches eager output.

### Screenshots

### Related issues

### 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

The version bump follows in a separate `Release v0.10.3` PR once this
merges.

---------

Co-authored-by: Mateusz Słuszniak <msluszniak1@gmail.com>
Co-authored-by: Bartosz Hanc <bartosz.hanc02@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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