Skip to content

FIX: Chunk large inputs to prevent tiktoken-rs stack overflow - #9

Merged
xfalcox merged 1 commit into
mainfrom
fix/tiktoken-stack-overflow-chunking
Feb 26, 2026
Merged

FIX: Chunk large inputs to prevent tiktoken-rs stack overflow#9
xfalcox merged 1 commit into
mainfrom
fix/tiktoken-stack-overflow-chunking

Conversation

@xfalcox

@xfalcox xfalcox commented Feb 26, 2026

Copy link
Copy Markdown
Member

Summary

  • tiktoken-rs v0.9.1 uses fancy-regex for BPE tokenization, which can stack overflow on large inputs due to catastrophic backtracking (openai/tiktoken#245). The Rust code .unwrap()s the error, panics across the FFI boundary, and aborts the entire Ruby process — this is not rescuable from Ruby.
  • A secondary issue is quadratic BPE merge time for long non-whitespace runs (openai/tiktoken#195), which causes the process to hang indefinitely.
  • Python tiktoken fixed the backtracking in v0.8.0 with possessive quantifiers (openai/tiktoken#258), but tiktoken-rs 0.9.1 and tiktoken_ruby 0.0.15.1 have not ported the fix, and no newer versions are available.

The crash (from GDB / container logs):

tiktoken-rs-0.9.1/src/vendor_tiktoken.rs:267:33:
called `Result::unwrap()` on an `Err` value: RuntimeError(StackOverflow)

thread caused non-unwinding panic. aborting.

Fix

Adds a safe_encode method that chunks text larger than 50K characters at whitespace boundaries before passing to tiktoken. Since BPE token boundaries never span whitespace, chunked encoding produces identical results for normal text. For pathological inputs (e.g. 500K repeated characters with no whitespace), it completes in seconds instead of crashing or hanging.

OpenAiCl100kTokenizer inherits from OpenAiTokenizer and gets the fix automatically.

Test plan

  • Verify normal text tokenization is unchanged (encode, decode, truncate, size, below_limit?)
  • Verify large text with whitespace produces identical token output to direct encoding
  • Verify pathological inputs (e.g. "^" * 500_000) complete without crashing
  • Verify cl100k_base encoding also works via inheritance

@xfalcox
xfalcox force-pushed the fix/tiktoken-stack-overflow-chunking branch 2 times, most recently from 32a7108 to 312046b Compare February 26, 2026 16:42
@xfalcox
xfalcox force-pushed the fix/tiktoken-stack-overflow-chunking branch from 312046b to aa1191b Compare February 26, 2026 17:35
tiktoken-rs v0.9.1 uses fancy-regex for BPE tokenization, which can
stack overflow on large inputs due to catastrophic backtracking
(openai/tiktoken#245). The Rust code
unwraps the error, panics across the FFI boundary, and aborts the
entire Ruby process — this is not rescuable from Ruby.

A secondary issue is quadratic BPE merge time for long non-whitespace
runs (openai/tiktoken#195), which causes
the process to hang indefinitely.

Python tiktoken fixed the backtracking in v0.8.0 with possessive
quantifiers (openai/tiktoken#258), but
tiktoken-rs 0.9.1 and tiktoken_ruby 0.0.15.1 have not ported the
fix, and no newer versions are available.

This adds a safe_encode method that chunks text larger than 50K
characters at whitespace boundaries before passing to tiktoken.
BPE token boundaries never span whitespace, so chunked encoding
produces identical results for normal text. For pathological inputs
(e.g. 500K repeated characters with no whitespace), it completes in
seconds instead of crashing or hanging.
@xfalcox
xfalcox force-pushed the fix/tiktoken-stack-overflow-chunking branch from aa1191b to c4cef25 Compare February 26, 2026 17:35
@xfalcox
xfalcox merged commit f949774 into main Feb 26, 2026
5 checks passed
@xfalcox
xfalcox deleted the fix/tiktoken-stack-overflow-chunking branch February 26, 2026 17:37
xfalcox added a commit to discourse/discourse that referenced this pull request Feb 26, 2026
Bumps discourse_ai-tokenizers from 0.4 to 0.4.1 for a fix for
tiktoken-rs stack overflow crash that aborts the Ruby process when
encoding large inputs
(discourse/discourse_ai-tokenizers#9)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants