Allow chunked prefill when num_prompt_tokens > max_seq_len - #19052

Merged
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720
Apr 25, 2026
Merged

Allow chunked prefill when num_prompt_tokens > max_seq_len#19052
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720

Conversation

@navsud

Copy link
Copy Markdown
Contributor

Summary:
Remove the early num_prompt_tokens <= max_seq_len check in TextLLMRunner. TextPrefiller::prefill() already supports chunked prefill — when the prompt is longer than max_seq_len it splits the input into max_seq_len-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with max_seq_len < max_context_len (e.g. a 1024 prefill chunk over a 4096 KV cache).

The total-capacity bound is preserved:

  • For non-sliding-window models (max_seq_len >= max_context_len), the existing pos_ + num_prompt_tokens < max_context_len check is unchanged.
  • For sliding-window models (max_seq_len < max_context_len), a new per-call check num_prompt_tokens < max_context_len ensures the prompt itself fits in KV cache; pos_ doesn't represent consumed capacity for these models since the model handles position wrapping internally.

Differential Revision: D101728720

Summary:
Remove the early `num_prompt_tokens <= max_seq_len` check in TextLLMRunner. `TextPrefiller::prefill()` already supports chunked prefill — when the prompt is longer than `max_seq_len` it splits the input into `max_seq_len`-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with `max_seq_len < max_context_len` (e.g. a 1024 prefill chunk over a 4096 KV cache).
The total-capacity bound is preserved:
- For non-sliding-window models (`max_seq_len >= max_context_len`), the existing `pos_ + num_prompt_tokens < max_context_len` check is unchanged.
- For sliding-window models (`max_seq_len < max_context_len`), a new per-call check `num_prompt_tokens < max_context_len` ensures the prompt itself fits in KV cache; `pos_` doesn't represent consumed capacity for these models since the model handles position wrapping internally.
Differential Revision: D101728720
@pytorch-bot

pytorch-botBot commented Apr 22, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19052

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 1 New Failure, 2 Unrelated Failures

As of commit 501ba12 with merge base 32702ac (image):

NEW FAILURE - The following job has failed:

FLAKY - The following job failed but was likely due to flakiness present on trunk:

BROKEN TRUNK - The following job failed but was present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 22, 2026
@meta-codesync

Copy link
Copy Markdown
Contributor

@navsud has exported this pull request. If you are a Meta employee, you can view the originating Diff in D101728720.

@navsudnavsud added the release notes: none Do not include this in the release notes label Apr 22, 2026
@kirklandsign

Copy link
Copy Markdown
Contributor

@claude review

@claude

claudeBot commented Apr 24, 2026

Copy link
Copy Markdown

Claude finished @kirklandsign's task in 1m 14s —— View job


Review of PR #19052

  • Read the diff and changed files
  • Review logic correctness
  • Post review feedback

Summary: This PR removes the num_prompt_tokens <= max_seq_len guard in TextLLMRunner::generate() and adds a sliding-window-specific capacity check. The motivation is correct: TextPrefiller::prefill() already handles chunked prefill (splitting into max_seq_len-sized chunks at text_prefiller.cpp:41-63), so the old guard was rejecting a valid code path.

Verdict: The change looks correct. A few observations:

What's good:

  • The removed check was redundant/incorrect — it rejected prompts that the downstream prefiller already handles via chunking.
  • The new else branch (text_llm_runner.cpp:161-169) correctly guards the sliding-window case (max_seq_len < max_context_len) with a per-call bound that doesn't involve pos_, which makes sense since the ring buffer manages position wrapping internally.
  • The existing non-sliding-window branch (pos_ + num_prompt_tokens < max_context_len) is preserved unchanged.
  • The comment block at lines 141-150 clearly explains the rationale.

Minor observations (non-blocking):

  1. Strict inequality on capacity check (text_llm_runner.cpp:163): The sliding-window check uses num_prompt_tokens < max_context_len, meaning a prompt of exactly max_context_len tokens is rejected. This matches the existing non-sliding-window check's use of < (line 153), so it's consistent — just noting that a prompt of exactly max_context_len tokens would fill the entire KV cache leaving no room for generation. This is the correct behavior.

  2. resolve_max_new_tokens with effective_pos = 0 for sliding-window (line 196): For sliding-window models, effective_pos is set to 0, so resolve_max_new_tokens(max_context_len, 0) gives the full budget. This was pre-existing logic (not introduced by this PR), but it means the generation loop doesn't account for the prompt tokens that were just prefilled consuming some of the sliding window. This seems intentional since the ring buffer recycles, but worth being aware of for very long generations.

  3. No test coverage for the new path: There are no test additions for the case where num_prompt_tokens > max_seq_len with max_seq_len < max_context_len. An integration or unit test covering chunked prefill through the runner would help prevent regressions on this path, but this can be a follow-up.

Overall this is a clean, well-scoped fix.


@meta-codesync
meta-codesyncBot merged commit 2330652 into pytorch:mainApr 25, 2026
173 of 192 checks passed
zeel2104 pushed a commit to zeel2104/executorch that referenced this pull request May 5, 2026
Differential Revision: D101728720
Pull Request resolved: pytorch#19052
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exportedrelease notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@navsud@kirklandsign
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Allow chunked prefill when num_prompt_tokens > max_seq_len - #19052

Merged
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720
Apr 25, 2026
Merged

Allow chunked prefill when num_prompt_tokens > max_seq_len#19052
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720

Conversation

@navsud

Copy link
Copy Markdown
Contributor

Summary:
Remove the early num_prompt_tokens <= max_seq_len check in TextLLMRunner. TextPrefiller::prefill() already supports chunked prefill — when the prompt is longer than max_seq_len it splits the input into max_seq_len-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with max_seq_len < max_context_len (e.g. a 1024 prefill chunk over a 4096 KV cache).

The total-capacity bound is preserved:

  • For non-sliding-window models (max_seq_len >= max_context_len), the existing pos_ + num_prompt_tokens < max_context_len check is unchanged.
  • For sliding-window models (max_seq_len < max_context_len), a new per-call check num_prompt_tokens < max_context_len ensures the prompt itself fits in KV cache; pos_ doesn't represent consumed capacity for these models since the model handles position wrapping internally.

Differential Revision: D101728720

Summary:
Remove the early `num_prompt_tokens <= max_seq_len` check in TextLLMRunner. `TextPrefiller::prefill()` already supports chunked prefill — when the prompt is longer than `max_seq_len` it splits the input into `max_seq_len`-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with `max_seq_len < max_context_len` (e.g. a 1024 prefill chunk over a 4096 KV cache).
The total-capacity bound is preserved:
- For non-sliding-window models (`max_seq_len >= max_context_len`), the existing `pos_ + num_prompt_tokens < max_context_len` check is unchanged.
- For sliding-window models (`max_seq_len < max_context_len`), a new per-call check `num_prompt_tokens < max_context_len` ensures the prompt itself fits in KV cache; `pos_` doesn't represent consumed capacity for these models since the model handles position wrapping internally.
Differential Revision: D101728720
@pytorch-bot

pytorch-botBot commented Apr 22, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19052

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 1 New Failure, 2 Unrelated Failures

As of commit 501ba12 with merge base 32702ac (image):

NEW FAILURE - The following job has failed:

FLAKY - The following job failed but was likely due to flakiness present on trunk:

BROKEN TRUNK - The following job failed but was present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 22, 2026
@meta-codesync

Copy link
Copy Markdown
Contributor

@navsud has exported this pull request. If you are a Meta employee, you can view the originating Diff in D101728720.

@navsudnavsud added the release notes: none Do not include this in the release notes label Apr 22, 2026
@kirklandsign

Copy link
Copy Markdown
Contributor

@claude review

@claude

claudeBot commented Apr 24, 2026

Copy link
Copy Markdown

Claude finished @kirklandsign's task in 1m 14s —— View job


Review of PR #19052

  • Read the diff and changed files
  • Review logic correctness
  • Post review feedback

Summary: This PR removes the num_prompt_tokens <= max_seq_len guard in TextLLMRunner::generate() and adds a sliding-window-specific capacity check. The motivation is correct: TextPrefiller::prefill() already handles chunked prefill (splitting into max_seq_len-sized chunks at text_prefiller.cpp:41-63), so the old guard was rejecting a valid code path.

Verdict: The change looks correct. A few observations:

What's good:

  • The removed check was redundant/incorrect — it rejected prompts that the downstream prefiller already handles via chunking.
  • The new else branch (text_llm_runner.cpp:161-169) correctly guards the sliding-window case (max_seq_len < max_context_len) with a per-call bound that doesn't involve pos_, which makes sense since the ring buffer manages position wrapping internally.
  • The existing non-sliding-window branch (pos_ + num_prompt_tokens < max_context_len) is preserved unchanged.
  • The comment block at lines 141-150 clearly explains the rationale.

Minor observations (non-blocking):

  1. Strict inequality on capacity check (text_llm_runner.cpp:163): The sliding-window check uses num_prompt_tokens < max_context_len, meaning a prompt of exactly max_context_len tokens is rejected. This matches the existing non-sliding-window check's use of < (line 153), so it's consistent — just noting that a prompt of exactly max_context_len tokens would fill the entire KV cache leaving no room for generation. This is the correct behavior.

  2. resolve_max_new_tokens with effective_pos = 0 for sliding-window (line 196): For sliding-window models, effective_pos is set to 0, so resolve_max_new_tokens(max_context_len, 0) gives the full budget. This was pre-existing logic (not introduced by this PR), but it means the generation loop doesn't account for the prompt tokens that were just prefilled consuming some of the sliding window. This seems intentional since the ring buffer recycles, but worth being aware of for very long generations.

  3. No test coverage for the new path: There are no test additions for the case where num_prompt_tokens > max_seq_len with max_seq_len < max_context_len. An integration or unit test covering chunked prefill through the runner would help prevent regressions on this path, but this can be a follow-up.

Overall this is a clean, well-scoped fix.


@meta-codesync
meta-codesyncBot merged commit 2330652 into pytorch:mainApr 25, 2026
173 of 192 checks passed
zeel2104 pushed a commit to zeel2104/executorch that referenced this pull request May 5, 2026
Differential Revision: D101728720
Pull Request resolved: pytorch#19052
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exportedrelease notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@navsud@kirklandsign
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Allow chunked prefill when num_prompt_tokens > max_seq_len - #19052

Merged
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720
Apr 25, 2026
Merged

Allow chunked prefill when num_prompt_tokens > max_seq_len#19052
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720

Conversation

@navsud

Copy link
Copy Markdown
Contributor

Summary:
Remove the early num_prompt_tokens <= max_seq_len check in TextLLMRunner. TextPrefiller::prefill() already supports chunked prefill — when the prompt is longer than max_seq_len it splits the input into max_seq_len-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with max_seq_len < max_context_len (e.g. a 1024 prefill chunk over a 4096 KV cache).

The total-capacity bound is preserved:

  • For non-sliding-window models (max_seq_len >= max_context_len), the existing pos_ + num_prompt_tokens < max_context_len check is unchanged.
  • For sliding-window models (max_seq_len < max_context_len), a new per-call check num_prompt_tokens < max_context_len ensures the prompt itself fits in KV cache; pos_ doesn't represent consumed capacity for these models since the model handles position wrapping internally.

Differential Revision: D101728720

Summary:
Remove the early `num_prompt_tokens <= max_seq_len` check in TextLLMRunner. `TextPrefiller::prefill()` already supports chunked prefill — when the prompt is longer than `max_seq_len` it splits the input into `max_seq_len`-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with `max_seq_len < max_context_len` (e.g. a 1024 prefill chunk over a 4096 KV cache).
The total-capacity bound is preserved:
- For non-sliding-window models (`max_seq_len >= max_context_len`), the existing `pos_ + num_prompt_tokens < max_context_len` check is unchanged.
- For sliding-window models (`max_seq_len < max_context_len`), a new per-call check `num_prompt_tokens < max_context_len` ensures the prompt itself fits in KV cache; `pos_` doesn't represent consumed capacity for these models since the model handles position wrapping internally.
Differential Revision: D101728720
@pytorch-bot

pytorch-botBot commented Apr 22, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19052

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 1 New Failure, 2 Unrelated Failures

As of commit 501ba12 with merge base 32702ac (image):

NEW FAILURE - The following job has failed:

FLAKY - The following job failed but was likely due to flakiness present on trunk:

BROKEN TRUNK - The following job failed but was present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 22, 2026
@meta-codesync

Copy link
Copy Markdown
Contributor

@navsud has exported this pull request. If you are a Meta employee, you can view the originating Diff in D101728720.

@navsudnavsud added the release notes: none Do not include this in the release notes label Apr 22, 2026
@kirklandsign

Copy link
Copy Markdown
Contributor

@claude review

@claude

claudeBot commented Apr 24, 2026

Copy link
Copy Markdown

Claude finished @kirklandsign's task in 1m 14s —— View job


Review of PR #19052

  • Read the diff and changed files
  • Review logic correctness
  • Post review feedback

Summary: This PR removes the num_prompt_tokens <= max_seq_len guard in TextLLMRunner::generate() and adds a sliding-window-specific capacity check. The motivation is correct: TextPrefiller::prefill() already handles chunked prefill (splitting into max_seq_len-sized chunks at text_prefiller.cpp:41-63), so the old guard was rejecting a valid code path.

Verdict: The change looks correct. A few observations:

What's good:

  • The removed check was redundant/incorrect — it rejected prompts that the downstream prefiller already handles via chunking.
  • The new else branch (text_llm_runner.cpp:161-169) correctly guards the sliding-window case (max_seq_len < max_context_len) with a per-call bound that doesn't involve pos_, which makes sense since the ring buffer manages position wrapping internally.
  • The existing non-sliding-window branch (pos_ + num_prompt_tokens < max_context_len) is preserved unchanged.
  • The comment block at lines 141-150 clearly explains the rationale.

Minor observations (non-blocking):

  1. Strict inequality on capacity check (text_llm_runner.cpp:163): The sliding-window check uses num_prompt_tokens < max_context_len, meaning a prompt of exactly max_context_len tokens is rejected. This matches the existing non-sliding-window check's use of < (line 153), so it's consistent — just noting that a prompt of exactly max_context_len tokens would fill the entire KV cache leaving no room for generation. This is the correct behavior.

  2. resolve_max_new_tokens with effective_pos = 0 for sliding-window (line 196): For sliding-window models, effective_pos is set to 0, so resolve_max_new_tokens(max_context_len, 0) gives the full budget. This was pre-existing logic (not introduced by this PR), but it means the generation loop doesn't account for the prompt tokens that were just prefilled consuming some of the sliding window. This seems intentional since the ring buffer recycles, but worth being aware of for very long generations.

  3. No test coverage for the new path: There are no test additions for the case where num_prompt_tokens > max_seq_len with max_seq_len < max_context_len. An integration or unit test covering chunked prefill through the runner would help prevent regressions on this path, but this can be a follow-up.

Overall this is a clean, well-scoped fix.


@meta-codesync
meta-codesyncBot merged commit 2330652 into pytorch:mainApr 25, 2026
173 of 192 checks passed
zeel2104 pushed a commit to zeel2104/executorch that referenced this pull request May 5, 2026
Differential Revision: D101728720
Pull Request resolved: pytorch#19052
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exportedrelease notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@navsud@kirklandsign
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Allow chunked prefill when num_prompt_tokens > max_seq_len - #19052

Merged
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720
Apr 25, 2026
Merged

Allow chunked prefill when num_prompt_tokens > max_seq_len#19052
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720

Conversation

@navsud

Copy link
Copy Markdown
Contributor

Summary:
Remove the early num_prompt_tokens <= max_seq_len check in TextLLMRunner. TextPrefiller::prefill() already supports chunked prefill — when the prompt is longer than max_seq_len it splits the input into max_seq_len-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with max_seq_len < max_context_len (e.g. a 1024 prefill chunk over a 4096 KV cache).

The total-capacity bound is preserved:

  • For non-sliding-window models (max_seq_len >= max_context_len), the existing pos_ + num_prompt_tokens < max_context_len check is unchanged.
  • For sliding-window models (max_seq_len < max_context_len), a new per-call check num_prompt_tokens < max_context_len ensures the prompt itself fits in KV cache; pos_ doesn't represent consumed capacity for these models since the model handles position wrapping internally.

Differential Revision: D101728720

Summary:
Remove the early `num_prompt_tokens <= max_seq_len` check in TextLLMRunner. `TextPrefiller::prefill()` already supports chunked prefill — when the prompt is longer than `max_seq_len` it splits the input into `max_seq_len`-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with `max_seq_len < max_context_len` (e.g. a 1024 prefill chunk over a 4096 KV cache).
The total-capacity bound is preserved:
- For non-sliding-window models (`max_seq_len >= max_context_len`), the existing `pos_ + num_prompt_tokens < max_context_len` check is unchanged.
- For sliding-window models (`max_seq_len < max_context_len`), a new per-call check `num_prompt_tokens < max_context_len` ensures the prompt itself fits in KV cache; `pos_` doesn't represent consumed capacity for these models since the model handles position wrapping internally.
Differential Revision: D101728720
@pytorch-bot

pytorch-botBot commented Apr 22, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19052

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 1 New Failure, 2 Unrelated Failures

As of commit 501ba12 with merge base 32702ac (image):

NEW FAILURE - The following job has failed:

FLAKY - The following job failed but was likely due to flakiness present on trunk:

BROKEN TRUNK - The following job failed but was present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 22, 2026
@meta-codesync

Copy link
Copy Markdown
Contributor

@navsud has exported this pull request. If you are a Meta employee, you can view the originating Diff in D101728720.

@navsudnavsud added the release notes: none Do not include this in the release notes label Apr 22, 2026
@kirklandsign

Copy link
Copy Markdown
Contributor

@claude review

@claude

claudeBot commented Apr 24, 2026

Copy link
Copy Markdown

Claude finished @kirklandsign's task in 1m 14s —— View job


Review of PR #19052

  • Read the diff and changed files
  • Review logic correctness
  • Post review feedback

Summary: This PR removes the num_prompt_tokens <= max_seq_len guard in TextLLMRunner::generate() and adds a sliding-window-specific capacity check. The motivation is correct: TextPrefiller::prefill() already handles chunked prefill (splitting into max_seq_len-sized chunks at text_prefiller.cpp:41-63), so the old guard was rejecting a valid code path.

Verdict: The change looks correct. A few observations:

What's good:

  • The removed check was redundant/incorrect — it rejected prompts that the downstream prefiller already handles via chunking.
  • The new else branch (text_llm_runner.cpp:161-169) correctly guards the sliding-window case (max_seq_len < max_context_len) with a per-call bound that doesn't involve pos_, which makes sense since the ring buffer manages position wrapping internally.
  • The existing non-sliding-window branch (pos_ + num_prompt_tokens < max_context_len) is preserved unchanged.
  • The comment block at lines 141-150 clearly explains the rationale.

Minor observations (non-blocking):

  1. Strict inequality on capacity check (text_llm_runner.cpp:163): The sliding-window check uses num_prompt_tokens < max_context_len, meaning a prompt of exactly max_context_len tokens is rejected. This matches the existing non-sliding-window check's use of < (line 153), so it's consistent — just noting that a prompt of exactly max_context_len tokens would fill the entire KV cache leaving no room for generation. This is the correct behavior.

  2. resolve_max_new_tokens with effective_pos = 0 for sliding-window (line 196): For sliding-window models, effective_pos is set to 0, so resolve_max_new_tokens(max_context_len, 0) gives the full budget. This was pre-existing logic (not introduced by this PR), but it means the generation loop doesn't account for the prompt tokens that were just prefilled consuming some of the sliding window. This seems intentional since the ring buffer recycles, but worth being aware of for very long generations.

  3. No test coverage for the new path: There are no test additions for the case where num_prompt_tokens > max_seq_len with max_seq_len < max_context_len. An integration or unit test covering chunked prefill through the runner would help prevent regressions on this path, but this can be a follow-up.

Overall this is a clean, well-scoped fix.


@meta-codesync
meta-codesyncBot merged commit 2330652 into pytorch:mainApr 25, 2026
173 of 192 checks passed
zeel2104 pushed a commit to zeel2104/executorch that referenced this pull request May 5, 2026
Differential Revision: D101728720
Pull Request resolved: pytorch#19052
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exportedrelease notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@navsud@kirklandsign
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Allow chunked prefill when num_prompt_tokens > max_seq_len - #19052

Merged
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720
Apr 25, 2026
Merged

Allow chunked prefill when num_prompt_tokens > max_seq_len#19052
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720

Conversation

@navsud

Copy link
Copy Markdown
Contributor

Summary:
Remove the early num_prompt_tokens <= max_seq_len check in TextLLMRunner. TextPrefiller::prefill() already supports chunked prefill — when the prompt is longer than max_seq_len it splits the input into max_seq_len-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with max_seq_len < max_context_len (e.g. a 1024 prefill chunk over a 4096 KV cache).

The total-capacity bound is preserved:

  • For non-sliding-window models (max_seq_len >= max_context_len), the existing pos_ + num_prompt_tokens < max_context_len check is unchanged.
  • For sliding-window models (max_seq_len < max_context_len), a new per-call check num_prompt_tokens < max_context_len ensures the prompt itself fits in KV cache; pos_ doesn't represent consumed capacity for these models since the model handles position wrapping internally.

Differential Revision: D101728720

Summary:
Remove the early `num_prompt_tokens <= max_seq_len` check in TextLLMRunner. `TextPrefiller::prefill()` already supports chunked prefill — when the prompt is longer than `max_seq_len` it splits the input into `max_seq_len`-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with `max_seq_len < max_context_len` (e.g. a 1024 prefill chunk over a 4096 KV cache).
The total-capacity bound is preserved:
- For non-sliding-window models (`max_seq_len >= max_context_len`), the existing `pos_ + num_prompt_tokens < max_context_len` check is unchanged.
- For sliding-window models (`max_seq_len < max_context_len`), a new per-call check `num_prompt_tokens < max_context_len` ensures the prompt itself fits in KV cache; `pos_` doesn't represent consumed capacity for these models since the model handles position wrapping internally.
Differential Revision: D101728720
@pytorch-bot

pytorch-botBot commented Apr 22, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19052

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 1 New Failure, 2 Unrelated Failures

As of commit 501ba12 with merge base 32702ac (image):

NEW FAILURE - The following job has failed:

FLAKY - The following job failed but was likely due to flakiness present on trunk:

BROKEN TRUNK - The following job failed but was present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 22, 2026
@meta-codesync

Copy link
Copy Markdown
Contributor

@navsud has exported this pull request. If you are a Meta employee, you can view the originating Diff in D101728720.

@navsudnavsud added the release notes: none Do not include this in the release notes label Apr 22, 2026
@kirklandsign

Copy link
Copy Markdown
Contributor

@claude review

@claude

claudeBot commented Apr 24, 2026

Copy link
Copy Markdown

Claude finished @kirklandsign's task in 1m 14s —— View job


Review of PR #19052

  • Read the diff and changed files
  • Review logic correctness
  • Post review feedback

Summary: This PR removes the num_prompt_tokens <= max_seq_len guard in TextLLMRunner::generate() and adds a sliding-window-specific capacity check. The motivation is correct: TextPrefiller::prefill() already handles chunked prefill (splitting into max_seq_len-sized chunks at text_prefiller.cpp:41-63), so the old guard was rejecting a valid code path.

Verdict: The change looks correct. A few observations:

What's good:

  • The removed check was redundant/incorrect — it rejected prompts that the downstream prefiller already handles via chunking.
  • The new else branch (text_llm_runner.cpp:161-169) correctly guards the sliding-window case (max_seq_len < max_context_len) with a per-call bound that doesn't involve pos_, which makes sense since the ring buffer manages position wrapping internally.
  • The existing non-sliding-window branch (pos_ + num_prompt_tokens < max_context_len) is preserved unchanged.
  • The comment block at lines 141-150 clearly explains the rationale.

Minor observations (non-blocking):

  1. Strict inequality on capacity check (text_llm_runner.cpp:163): The sliding-window check uses num_prompt_tokens < max_context_len, meaning a prompt of exactly max_context_len tokens is rejected. This matches the existing non-sliding-window check's use of < (line 153), so it's consistent — just noting that a prompt of exactly max_context_len tokens would fill the entire KV cache leaving no room for generation. This is the correct behavior.

  2. resolve_max_new_tokens with effective_pos = 0 for sliding-window (line 196): For sliding-window models, effective_pos is set to 0, so resolve_max_new_tokens(max_context_len, 0) gives the full budget. This was pre-existing logic (not introduced by this PR), but it means the generation loop doesn't account for the prompt tokens that were just prefilled consuming some of the sliding window. This seems intentional since the ring buffer recycles, but worth being aware of for very long generations.

  3. No test coverage for the new path: There are no test additions for the case where num_prompt_tokens > max_seq_len with max_seq_len < max_context_len. An integration or unit test covering chunked prefill through the runner would help prevent regressions on this path, but this can be a follow-up.

Overall this is a clean, well-scoped fix.


@meta-codesync
meta-codesyncBot merged commit 2330652 into pytorch:mainApr 25, 2026
173 of 192 checks passed
zeel2104 pushed a commit to zeel2104/executorch that referenced this pull request May 5, 2026
Differential Revision: D101728720
Pull Request resolved: pytorch#19052
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exportedrelease notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@navsud@kirklandsign
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Allow chunked prefill when num_prompt_tokens > max_seq_len - #19052

Merged
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720
Apr 25, 2026
Merged

Allow chunked prefill when num_prompt_tokens > max_seq_len#19052
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720

Conversation

@navsud

Copy link
Copy Markdown
Contributor

Summary:
Remove the early num_prompt_tokens <= max_seq_len check in TextLLMRunner. TextPrefiller::prefill() already supports chunked prefill — when the prompt is longer than max_seq_len it splits the input into max_seq_len-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with max_seq_len < max_context_len (e.g. a 1024 prefill chunk over a 4096 KV cache).

The total-capacity bound is preserved:

  • For non-sliding-window models (max_seq_len >= max_context_len), the existing pos_ + num_prompt_tokens < max_context_len check is unchanged.
  • For sliding-window models (max_seq_len < max_context_len), a new per-call check num_prompt_tokens < max_context_len ensures the prompt itself fits in KV cache; pos_ doesn't represent consumed capacity for these models since the model handles position wrapping internally.

Differential Revision: D101728720

Summary:
Remove the early `num_prompt_tokens <= max_seq_len` check in TextLLMRunner. `TextPrefiller::prefill()` already supports chunked prefill — when the prompt is longer than `max_seq_len` it splits the input into `max_seq_len`-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with `max_seq_len < max_context_len` (e.g. a 1024 prefill chunk over a 4096 KV cache).
The total-capacity bound is preserved:
- For non-sliding-window models (`max_seq_len >= max_context_len`), the existing `pos_ + num_prompt_tokens < max_context_len` check is unchanged.
- For sliding-window models (`max_seq_len < max_context_len`), a new per-call check `num_prompt_tokens < max_context_len` ensures the prompt itself fits in KV cache; `pos_` doesn't represent consumed capacity for these models since the model handles position wrapping internally.
Differential Revision: D101728720
@pytorch-bot

pytorch-botBot commented Apr 22, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19052

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 1 New Failure, 2 Unrelated Failures

As of commit 501ba12 with merge base 32702ac (image):

NEW FAILURE - The following job has failed:

FLAKY - The following job failed but was likely due to flakiness present on trunk:

BROKEN TRUNK - The following job failed but was present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 22, 2026
@meta-codesync

Copy link
Copy Markdown
Contributor

@navsud has exported this pull request. If you are a Meta employee, you can view the originating Diff in D101728720.

@navsudnavsud added the release notes: none Do not include this in the release notes label Apr 22, 2026
@kirklandsign

Copy link
Copy Markdown
Contributor

@claude review

@claude

claudeBot commented Apr 24, 2026

Copy link
Copy Markdown

Claude finished @kirklandsign's task in 1m 14s —— View job


Review of PR #19052

  • Read the diff and changed files
  • Review logic correctness
  • Post review feedback

Summary: This PR removes the num_prompt_tokens <= max_seq_len guard in TextLLMRunner::generate() and adds a sliding-window-specific capacity check. The motivation is correct: TextPrefiller::prefill() already handles chunked prefill (splitting into max_seq_len-sized chunks at text_prefiller.cpp:41-63), so the old guard was rejecting a valid code path.

Verdict: The change looks correct. A few observations:

What's good:

  • The removed check was redundant/incorrect — it rejected prompts that the downstream prefiller already handles via chunking.
  • The new else branch (text_llm_runner.cpp:161-169) correctly guards the sliding-window case (max_seq_len < max_context_len) with a per-call bound that doesn't involve pos_, which makes sense since the ring buffer manages position wrapping internally.
  • The existing non-sliding-window branch (pos_ + num_prompt_tokens < max_context_len) is preserved unchanged.
  • The comment block at lines 141-150 clearly explains the rationale.

Minor observations (non-blocking):

  1. Strict inequality on capacity check (text_llm_runner.cpp:163): The sliding-window check uses num_prompt_tokens < max_context_len, meaning a prompt of exactly max_context_len tokens is rejected. This matches the existing non-sliding-window check's use of < (line 153), so it's consistent — just noting that a prompt of exactly max_context_len tokens would fill the entire KV cache leaving no room for generation. This is the correct behavior.

  2. resolve_max_new_tokens with effective_pos = 0 for sliding-window (line 196): For sliding-window models, effective_pos is set to 0, so resolve_max_new_tokens(max_context_len, 0) gives the full budget. This was pre-existing logic (not introduced by this PR), but it means the generation loop doesn't account for the prompt tokens that were just prefilled consuming some of the sliding window. This seems intentional since the ring buffer recycles, but worth being aware of for very long generations.

  3. No test coverage for the new path: There are no test additions for the case where num_prompt_tokens > max_seq_len with max_seq_len < max_context_len. An integration or unit test covering chunked prefill through the runner would help prevent regressions on this path, but this can be a follow-up.

Overall this is a clean, well-scoped fix.


@meta-codesync
meta-codesyncBot merged commit 2330652 into pytorch:mainApr 25, 2026
173 of 192 checks passed
zeel2104 pushed a commit to zeel2104/executorch that referenced this pull request May 5, 2026
Differential Revision: D101728720
Pull Request resolved: pytorch#19052
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exportedrelease notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@navsud@kirklandsign
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Allow chunked prefill when num_prompt_tokens > max_seq_len - #19052

Merged
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720
Apr 25, 2026
Merged

Allow chunked prefill when num_prompt_tokens > max_seq_len#19052
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720

Conversation

@navsud

Copy link
Copy Markdown
Contributor

Summary:
Remove the early num_prompt_tokens <= max_seq_len check in TextLLMRunner. TextPrefiller::prefill() already supports chunked prefill — when the prompt is longer than max_seq_len it splits the input into max_seq_len-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with max_seq_len < max_context_len (e.g. a 1024 prefill chunk over a 4096 KV cache).

The total-capacity bound is preserved:

  • For non-sliding-window models (max_seq_len >= max_context_len), the existing pos_ + num_prompt_tokens < max_context_len check is unchanged.
  • For sliding-window models (max_seq_len < max_context_len), a new per-call check num_prompt_tokens < max_context_len ensures the prompt itself fits in KV cache; pos_ doesn't represent consumed capacity for these models since the model handles position wrapping internally.

Differential Revision: D101728720

Summary:
Remove the early `num_prompt_tokens <= max_seq_len` check in TextLLMRunner. `TextPrefiller::prefill()` already supports chunked prefill — when the prompt is longer than `max_seq_len` it splits the input into `max_seq_len`-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with `max_seq_len < max_context_len` (e.g. a 1024 prefill chunk over a 4096 KV cache).
The total-capacity bound is preserved:
- For non-sliding-window models (`max_seq_len >= max_context_len`), the existing `pos_ + num_prompt_tokens < max_context_len` check is unchanged.
- For sliding-window models (`max_seq_len < max_context_len`), a new per-call check `num_prompt_tokens < max_context_len` ensures the prompt itself fits in KV cache; `pos_` doesn't represent consumed capacity for these models since the model handles position wrapping internally.
Differential Revision: D101728720
@pytorch-bot

pytorch-botBot commented Apr 22, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19052

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 1 New Failure, 2 Unrelated Failures

As of commit 501ba12 with merge base 32702ac (image):

NEW FAILURE - The following job has failed:

FLAKY - The following job failed but was likely due to flakiness present on trunk:

BROKEN TRUNK - The following job failed but was present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 22, 2026
@meta-codesync

Copy link
Copy Markdown
Contributor

@navsud has exported this pull request. If you are a Meta employee, you can view the originating Diff in D101728720.

@navsudnavsud added the release notes: none Do not include this in the release notes label Apr 22, 2026
@kirklandsign

Copy link
Copy Markdown
Contributor

@claude review

@claude

claudeBot commented Apr 24, 2026

Copy link
Copy Markdown

Claude finished @kirklandsign's task in 1m 14s —— View job


Review of PR #19052

  • Read the diff and changed files
  • Review logic correctness
  • Post review feedback

Summary: This PR removes the num_prompt_tokens <= max_seq_len guard in TextLLMRunner::generate() and adds a sliding-window-specific capacity check. The motivation is correct: TextPrefiller::prefill() already handles chunked prefill (splitting into max_seq_len-sized chunks at text_prefiller.cpp:41-63), so the old guard was rejecting a valid code path.

Verdict: The change looks correct. A few observations:

What's good:

  • The removed check was redundant/incorrect — it rejected prompts that the downstream prefiller already handles via chunking.
  • The new else branch (text_llm_runner.cpp:161-169) correctly guards the sliding-window case (max_seq_len < max_context_len) with a per-call bound that doesn't involve pos_, which makes sense since the ring buffer manages position wrapping internally.
  • The existing non-sliding-window branch (pos_ + num_prompt_tokens < max_context_len) is preserved unchanged.
  • The comment block at lines 141-150 clearly explains the rationale.

Minor observations (non-blocking):

  1. Strict inequality on capacity check (text_llm_runner.cpp:163): The sliding-window check uses num_prompt_tokens < max_context_len, meaning a prompt of exactly max_context_len tokens is rejected. This matches the existing non-sliding-window check's use of < (line 153), so it's consistent — just noting that a prompt of exactly max_context_len tokens would fill the entire KV cache leaving no room for generation. This is the correct behavior.

  2. resolve_max_new_tokens with effective_pos = 0 for sliding-window (line 196): For sliding-window models, effective_pos is set to 0, so resolve_max_new_tokens(max_context_len, 0) gives the full budget. This was pre-existing logic (not introduced by this PR), but it means the generation loop doesn't account for the prompt tokens that were just prefilled consuming some of the sliding window. This seems intentional since the ring buffer recycles, but worth being aware of for very long generations.

  3. No test coverage for the new path: There are no test additions for the case where num_prompt_tokens > max_seq_len with max_seq_len < max_context_len. An integration or unit test covering chunked prefill through the runner would help prevent regressions on this path, but this can be a follow-up.

Overall this is a clean, well-scoped fix.


@meta-codesync
meta-codesyncBot merged commit 2330652 into pytorch:mainApr 25, 2026
173 of 192 checks passed
zeel2104 pushed a commit to zeel2104/executorch that referenced this pull request May 5, 2026
Differential Revision: D101728720
Pull Request resolved: pytorch#19052
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exportedrelease notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@navsud@kirklandsign
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Allow chunked prefill when num_prompt_tokens > max_seq_len - #19052

Merged
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720
Apr 25, 2026
Merged

Allow chunked prefill when num_prompt_tokens > max_seq_len#19052
meta-codesync[bot] merged 1 commit into
pytorch:mainfrom
navsud:export-D101728720

Conversation

@navsud

Copy link
Copy Markdown
Contributor

Summary:
Remove the early num_prompt_tokens <= max_seq_len check in TextLLMRunner. TextPrefiller::prefill() already supports chunked prefill — when the prompt is longer than max_seq_len it splits the input into max_seq_len-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with max_seq_len < max_context_len (e.g. a 1024 prefill chunk over a 4096 KV cache).

The total-capacity bound is preserved:

  • For non-sliding-window models (max_seq_len >= max_context_len), the existing pos_ + num_prompt_tokens < max_context_len check is unchanged.
  • For sliding-window models (max_seq_len < max_context_len), a new per-call check num_prompt_tokens < max_context_len ensures the prompt itself fits in KV cache; pos_ doesn't represent consumed capacity for these models since the model handles position wrapping internally.

Differential Revision: D101728720

Summary:
Remove the early `num_prompt_tokens <= max_seq_len` check in TextLLMRunner. `TextPrefiller::prefill()` already supports chunked prefill — when the prompt is longer than `max_seq_len` it splits the input into `max_seq_len`-sized chunks and prefills them sequentially. The previous check rejected this valid case, breaking models exported with `max_seq_len < max_context_len` (e.g. a 1024 prefill chunk over a 4096 KV cache).
The total-capacity bound is preserved:
- For non-sliding-window models (`max_seq_len >= max_context_len`), the existing `pos_ + num_prompt_tokens < max_context_len` check is unchanged.
- For sliding-window models (`max_seq_len < max_context_len`), a new per-call check `num_prompt_tokens < max_context_len` ensures the prompt itself fits in KV cache; `pos_` doesn't represent consumed capacity for these models since the model handles position wrapping internally.
Differential Revision: D101728720
@pytorch-bot

pytorch-botBot commented Apr 22, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19052

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 1 New Failure, 2 Unrelated Failures

As of commit 501ba12 with merge base 32702ac (image):

NEW FAILURE - The following job has failed:

FLAKY - The following job failed but was likely due to flakiness present on trunk:

BROKEN TRUNK - The following job failed but was present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 22, 2026
@meta-codesync

Copy link
Copy Markdown
Contributor

@navsud has exported this pull request. If you are a Meta employee, you can view the originating Diff in D101728720.

@navsudnavsud added the release notes: none Do not include this in the release notes label Apr 22, 2026
@kirklandsign

Copy link
Copy Markdown
Contributor

@claude review

@claude

claudeBot commented Apr 24, 2026

Copy link
Copy Markdown

Claude finished @kirklandsign's task in 1m 14s —— View job


Review of PR #19052

  • Read the diff and changed files
  • Review logic correctness
  • Post review feedback

Summary: This PR removes the num_prompt_tokens <= max_seq_len guard in TextLLMRunner::generate() and adds a sliding-window-specific capacity check. The motivation is correct: TextPrefiller::prefill() already handles chunked prefill (splitting into max_seq_len-sized chunks at text_prefiller.cpp:41-63), so the old guard was rejecting a valid code path.

Verdict: The change looks correct. A few observations:

What's good:

  • The removed check was redundant/incorrect — it rejected prompts that the downstream prefiller already handles via chunking.
  • The new else branch (text_llm_runner.cpp:161-169) correctly guards the sliding-window case (max_seq_len < max_context_len) with a per-call bound that doesn't involve pos_, which makes sense since the ring buffer manages position wrapping internally.
  • The existing non-sliding-window branch (pos_ + num_prompt_tokens < max_context_len) is preserved unchanged.
  • The comment block at lines 141-150 clearly explains the rationale.

Minor observations (non-blocking):

  1. Strict inequality on capacity check (text_llm_runner.cpp:163): The sliding-window check uses num_prompt_tokens < max_context_len, meaning a prompt of exactly max_context_len tokens is rejected. This matches the existing non-sliding-window check's use of < (line 153), so it's consistent — just noting that a prompt of exactly max_context_len tokens would fill the entire KV cache leaving no room for generation. This is the correct behavior.

  2. resolve_max_new_tokens with effective_pos = 0 for sliding-window (line 196): For sliding-window models, effective_pos is set to 0, so resolve_max_new_tokens(max_context_len, 0) gives the full budget. This was pre-existing logic (not introduced by this PR), but it means the generation loop doesn't account for the prompt tokens that were just prefilled consuming some of the sliding window. This seems intentional since the ring buffer recycles, but worth being aware of for very long generations.

  3. No test coverage for the new path: There are no test additions for the case where num_prompt_tokens > max_seq_len with max_seq_len < max_context_len. An integration or unit test covering chunked prefill through the runner would help prevent regressions on this path, but this can be a follow-up.

Overall this is a clean, well-scoped fix.


@meta-codesync
meta-codesyncBot merged commit 2330652 into pytorch:mainApr 25, 2026
173 of 192 checks passed
zeel2104 pushed a commit to zeel2104/executorch that referenced this pull request May 5, 2026
Differential Revision: D101728720
Pull Request resolved: pytorch#19052
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.fb-exportedmeta-exportedrelease notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@navsud@kirklandsign