Fix added-token prefix space in SentencePiece fast tokenizers#45822
Fix added-token prefix space in SentencePiece fast tokenizers#45822qflen wants to merge 6 commits into
Conversation
Fixes huggingface#28218. When a user calls add_tokens on a SentencePiece-style fast tokenizer (NLLB, Reformer, MLuke, ...), the tokens-trie splits the input at the new token and runs the Metaspace pre_tokenizer on each chunk. With the default prepend_scheme="always", every chunk is given a leading "▁", so "abcdgym" tokenizes to ["abcd", "▁gym"] and decodes back to "abcd gym". Mirror what SpmConverter.pre_tokenizer already does for non-legacy spm tokenizers: in TokenizersBackend._add_tokens, when a post-init, non-special add_tokens call lands on a standalone Metaspace pre_tokenizer with prepend_scheme="always", swap it for one with prepend_scheme="first" so only the first chunk receives the prefix. Sequence pipelines (WhitespaceSplit + Metaspace) are left untouched because they depend on "always" semantics for whitespace handling. Init-time additions of persisted tokens (e.g. BigBird's <unused_*> slots) are excluded via an _init_complete guard so existing integration tests for those tokenizers stay green.
…fix-space # Conflicts: # tests/tokenization/test_tokenization_fast.py
|
okay but since "always" and "first" are equivalent for normal text (without added tokens), the root fix is just: in |
Fold the fix into `_get_prepend_scheme`: it now returns "first" instead of "always" when `add_prefix_space=True`, so standalone Metaspace pre_tokenizers minted via `convert_slow_tokenizer` no longer leave a spurious "▁" prefix on the chunk after an added token (huggingface#28218). "first" and "always" are identical on a standalone Metaspace for text without added tokens. WhitespaceSplit + Metaspace genuinely needs "always" so each split word keeps its "▁" SentencePiece marker; PegasusConverter and XLNetTokenizer now hardcode that scheme. Drop the `_init_complete` guard and `_align_metaspace_for_added_tokens` helper: the swap to "first" is inlined in `_add_tokens` and triggers when a non-special token is registered (covers .json loads that still bake in "always", whether the token comes from persisted state or a user `add_tokens` call). BigBird's persisted `<sep_*>`/`<length_*>` slots are non-special, so the swap now runs at load time and the chunk after a literal `<s>` loses its prefix; integration fixtures updated to match.
f16c094 to
637e2cf
Compare
|
Thanks for the feedback. Pushed changes. |
The `_add_tokens` override flipped a standalone Metaspace("always") to
"first" on any non-special add, including checkpoint restore. A tokenizer
that loads as a standalone Metaspace (e.g. kosmos-2) re-registers its own
added tokens both inside and after `__init__`, so the flip rewrote the
saved scheme and dropped the "▁" after in-text special tokens, breaking
Kosmos2ProcessorTest::test_full_processor.
Now only realign when a genuinely new (not-yet-present) non-special token
is added to a finished tokenizer: restoring a checkpoint's own vocab is
left alone, while a real user `add_tokens` still gets the huggingface#28218 fix. Add
a checkpoint-restore regression test.
Also restore big_bird's integration expectations. main refactored it into
a self-contained tokenizer that hardcodes prepend_scheme="always", so it
no longer takes the converter's "first" path; it still gets the fix via
`_add_tokens` on a real user add.
b4e25ec to
d9f40a4
Compare
|
[For maintainers] Suggested jobs to run (before merge) run-slow: xlnet |
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
CI recapDashboard: View test results in Grafana |
|
Thanks for working on this! One concern: I'm wondering if we can instead do an existence check instead, like Llama1/2 (aentencepiece) always ships |
|
Actually, was your comment meant for #47017? I think this can be merged as is. |
Fixes #28218
When a user adds a token to a SentencePiece-style fast tokenizer (e.g. NLLB), the tokens-trie splits the input
at the new token and runs the Metaspace pre_tokenizer on each chunk.
The default
prepend_scheme="always"then prefixes every chunk with▁, so"abcdgym"tokenizes to["abcd", "▁gym"]anddecode(encode("abcdgym"))returns"abcd gym".This PR mirrors what
SpmConverter.pre_tokenizeralready does for non-legacy spm tokenizers:when a post-init, non-special
add_tokenscall lands on a standaloneMetaspace(prepend_scheme="always"), the pre_tokenizer is swapped for one withprepend_scheme="first"so only the first chunk receives the prefix.Sequence pipelines (WhitespaceSplit + Metaspace, used by T5, Pegasus, UDOP, LASR) are left untouched because they rely on
"always"for whitespace handling.With
tokenizer.add_tokens(["abcd"])applied to a fresh NLLB tokenizer:"abcdgym"["abcd", "▁gym"]→"abcd gym"["abcd", "gym"]→"abcdgym""I like to walk abcdgym along the beach"[..., "▁walk", "▁", "abcd", "▁gym", "▁along", ...]→"I like to walk abcd gym along the beach"[..., "▁walk", "▁", "abcd", "gym", "▁along", ...]→"I like to walk abcdgym along the beach""abcd gym"(literal space already there)["abcd", "▁gym"]["abcd", "▁gym"](unchanged)Inputs without any added tokens are unchanged (e.g.
"Hello world"→["▁Hello", "▁world"]either way).Code Agent Policy
Before submitting
Issue: Tokenizer adds an additional space after the added token #28218
(
@ArthurZuckerproposed mirroring the SpmConverter approach)Who can review?
@ArthurZucker@itazapAI assistance disclosure
I've used Opus 4.7 Max Effort for debugging and reviewed / double-checked every line.
Coordination:
Tokenizer adds an additional space after the added token #28218 (maintainer guidance to follow
SpmConverter.pre_tokenizerClaimed issue in:
Tokenizer adds an additional space after the added token #28218 (comment))
Why this is not a duplicate:
The only related open PR (SPLIT PR: add_prefix_space fix #31315) targets a different issue (
SPMConverterdoes not always add the user defined symbol -> slow fast is thus not equivalent #30824, plumbingadd_prefix_spacethrough the fast tokenizer); it does not touch the post-init alignment ofprepend_schemefor added tokens.Tests run:
make styleandmake typingandmake fix-repo, all green, no extra diffpytest tests/tokenization/test_tokenization_fast.py(11 passed, 3 skipped)pytest tests/test_tokenization_common.py(18 passed, 3 skipped)pytest tests/models/nllb/test_tokenization_nllb.py(61 passed, 4 skipped)pytest tests/models/big_bird/test_tokenization_big_bird.py(100 passed, 4 skipped)pytest tests/models/{reformer,t5,albert,udop,moshi,seamless_m4t}/test_tokenization_*.py(all green)test_added_token_does_not_get_metaspace_prefixwas verified to fail on main(without the fix) and pass with the fix.