Skip to content

Akejzman/various speedups - #74

Merged
AlonKejzman merged 3 commits into
mainfrom
akejzman/various-speedups
Oct 8, 2026
Merged

AlonKejzman merged 3 commits into
mainfrom
akejzman/various-speedups

Conversation

@AlonKejzman

Copy link
Copy Markdown
Collaborator

No description provided.

@AlonKejzman
AlonKejzman force-pushed the akejzman/various-speedups branch from 84bca93 to 0325d99 Compare September 30, 2026 13:45
@AlonKejzman
AlonKejzman merged commit 48b0f8a into main Oct 8, 2026
21 checks passed
AlonKejzman added a commit to GuyStone/fastokens that referenced this pull request Oct 8, 2026
Rebasing onto main brings in `Normalizer::is_identity_on` (crusoecloud#74), whose match
did not cover the new `Lowercase` variant, so the crate no longer compiled.
`Lowercase` is the identity exactly when `first_change` finds nothing to
lower, which also holds for every substring, so expose that scan as
`Lowercase::is_normalized` (as `Nfc::is_normalized`) and use it: already
lowercase text keeps the in-place encode path instead of being copied
through normalization.

The HF parity test now also asserts that `is_identity_on` agrees with
whether HF's Lowercase changed the text, since the encode path skips
normalization when it says so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AlonKejzman added a commit that referenced this pull request Oct 8, 2026
* normalizers: implement the Lowercase normalizer

`NormalizerConfig::Lowercase` is already parsed but has no runtime
implementation, so tokenizers declaring `{"type": "Lowercase"}` fail to load
with "unsupported normalizer type: Lowercase". Add the Lowercase normalizer
and wire it into `Normalizer`. This unlocks standalone `Lowercase` and the
CLIP / sentence-transformers shape `Sequence[NFC, Replace, Lowercase]`;
uncased BERT still needs `NFD` + `StripAccents`.

Lowercasing is done per character with `char::to_lowercase`, mirroring HF's
`NormalizedString::lowercase`. This is deliberately not `str::to_lowercase`:
the std method additionally applies the Greek final-sigma rule, rendering
`ὈΔΥΣΣΕΎΣ` as `ὀδυσσεύς` where HF produces `ὀδυσσεύσ`. U+03A3 is the only
character the two disagree on. The "would this character change?" predicate
compares against the mapping itself rather than `char::is_uppercase()`, which
is false for titlecase letters such as `Dž` U+01C5 and would produce wrong
output.

Normalization runs single-threaded over the whole document before rayon
parallelism starts at BPE, so both the scan and the transform take a SWAR fast
path over ASCII, mirroring `ascii_lower_run_end` in `pre_tokenizers::scan`. The
transform is branch-free: `0x80 >> 2 == 0x20` is exactly the ASCII case bit.
Measured against a straightforward `chars().flat_map(char::to_lowercase)` on
~100 KiB inputs: 68x faster on already-lowercase ASCII, 45x on mixed case and
on ALL CAPS, and 0.95x on CJK (both dominated by std's LOWERCASE_TABLE binary
search, which HF pays too).

Tests cover the Cow::Borrowed guarantee, a capital at every one of 28 byte
offsets to pin the SWAR lane indexing and window boundary, titlecase letters,
the 1->2 expansion of `İ` U+0130, and final sigma — the last asserting
inequality with `str::to_lowercase` so a refactor towards it fails loudly.
`local_tests::lowercase_normalizer_matches_huggingface` diffs against the
`tokenizers` crate directly.

Also re-exports `Prepend` at the crate root, which was missed when it landed
in edccd31 (`Nfc` and `Replace` are both there).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* normalizers: cover Lowercase in is_identity_on

Rebasing onto main brings in `Normalizer::is_identity_on` (#74), whose match
did not cover the new `Lowercase` variant, so the crate no longer compiled.
`Lowercase` is the identity exactly when `first_change` finds nothing to
lower, which also holds for every substring, so expose that scan as
`Lowercase::is_normalized` (as `Nfc::is_normalized`) and use it: already
lowercase text keeps the in-place encode path instead of being copied
through normalization.

The HF parity test now also asserts that `is_identity_on` agrees with
whether HF's Lowercase changed the text, since the encode path skips
normalization when it says so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Alon Kejzman <alonkeizman@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant