Commit 3f3c746
Add public SentencePieceTokenizer factory methods for Unigram from vocab list and tokenizer.json (#7625)
* Initial plan
* Add public SentencePieceTokenizer.Create(vocab) and CreateFromTokenizerJson APIs
Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
* Fix BOS/EOS positional fallback, normalizer type validation, and prepend_scheme handling
Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
* Fix null normalizer guard, recursive Sequence support, BOS/EOS validation, and add tests
Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
* Generalize SentencePiece special tokens to prefix/suffix lists with post-processor support
Support Hugging Face tokenizer.json post-processor templates that wrap a sequence with
multiple special tokens (XLNet/mBART/NLLB styles) by generalizing the single BOS/EOS
handling into ordered prefix/suffix special-token lists, applied consistently across the
encode/count/index paths.
CreateFromTokenizerJson now:
- parses post_processor (TemplateProcessing, RobertaProcessing, BertProcessing, Sequence)
to resolve the prefix/suffix wrapping, resolving ids via special_tokens -> added_tokens -> vocab.
- reads model.byte_fallback and exposes byteFallback on Create.
- treats added_tokens (special) as the authoritative special-token source.
- tolerates unmodeled sibling steps inside a Sequence normalizer.
- deduces remove_extra_whitespaces from the normalizer Strip/Replace(" {2,}") steps instead
of hardcoding it, matching the Hugging Face fast-tokenizer runtime.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Support JSON-only Unigram normalizer chains and broaden tokenizer.json coverage
Apply the Hugging Face tokenizer.json normalizer chain when loading JSON-only
SentencePiece Unigram tokenizers whose normalizer has content-modifying steps
(per-character Replace, Lowercase, StripAccents, NFC/NFD/NFKC/NFKD, Nmt, Prepend),
running it before the Metaspace pass to match the reference order. Bare-charsmap
models keep their existing fast path; only chain-mode models run the chain, which
is a simple string pipeline (the boundary decode/encode use pooled buffers).
Also broadens coverage of real models:
- Infer Unigram when model.type is absent (older xlm-roberta-base/albert files)
unless BPE merges are present.
- Treat a WhitespaceSplit pre-tokenizer as remove_extra_whitespaces.
Validated against a Hugging Face oracle: 41 JSON-only Unigram models load with no
failures and tokenize identically to the reference except two documented cases
(per-character vs merged unk for out-of-vocabulary runs, and trailing/leading
whitespace exactness on whitespace-collapsing Replace models).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review: byte_fallback init, Metaspace replacement, doc
- Set MaxByteId in the vocab-based Unigram constructor (and validate a contiguous
<0x00>..<0xFF> block) so byte_fallback decode works instead of being inert, and
fix a Debug.Assert that wrongly forbade unknown tokens on the byte-fallback path.
- Reject a Metaspace "replacement" other than U+2581 with NotSupportedException
rather than silently disabling whitespace escaping.
- Correct the CreateFromTokenizerJson remarks: remove_extra_whitespaces is deduced
(defaulting false), not assumed true.
Adds byte-fallback round-trip and validation tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review round 2: Nmt, processor affix validation, cleanup
- Apply Nmt even when it is the only normalizer step (HasRichSteps no longer
treats Nmt as non-rich), so format/control characters are normalized and the
behavior matches the documented step list.
- Validate the RobertaProcessing/BertProcessing cls/sep [token, id] pairs against
the vocabulary and added tokens, rejecting inconsistent files.
- Drop the unused ref treatWhitespaceAsSuffix parameter from ExtractMetaspaceSettings.
Adds standalone-Nmt and inconsistent-processor-affix tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review round 3: clarify charsmap comment, label missing normalizer type
- Reword the ExtractPrecompiledCharsMap Sequence comment: it is reached only for
non-rich Sequences (rich steps such as Nmt route through the managed chain), so
it no longer implies those steps are silently ignored.
- Render a missing/null normalizer type as <missing> in the NotSupportedException
message instead of an empty string.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review round 4: byte-offset gating, base64 + unk_id validation
- Only initialize the byte-piece offsets (ByteCodeToIdOffset and the derived
OneByteUtf8EncodingMaxId/MaxIdByteFallbackId) when byte_fallback is enabled, so a
vocab that merely contains <0xNN> pieces with byte_fallback:false decodes normal
low ids instead of dropping them as byte-fallback pieces.
- Decode a malformed base64 precompiled_charsmap into InvalidDataException via a
shared DecodePrecompiledCharsMap helper used by both the chain and charsmap paths.
- Validate model.unk_id against the parsed vocab length and throw InvalidDataException
for an out-of-range value.
Adds byte-fallback-disabled, malformed-charsmap, and out-of-range-unk_id tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review round 5: validate unk_id and vocab entry value kinds
- Require model.unk_id to be a JSON number, throwing InvalidDataException with a
targeted message instead of letting GetInt32 raise InvalidOperationException.
- Require each model.vocab entry to be a [string piece, number score] pair so a
missing/non-numeric score fails diagnostically rather than via a cast error.
Adds non-numeric unk_id and non-numeric vocab-score tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review round 6: validate JSON value kinds across the loader
Harden the tokenizer.json parser so malformed inputs fail with a diagnostic
InvalidDataException instead of an InvalidOperationException/ArgumentException
escaping from CreateFromTokenizerJson:
- added_tokens: require string content and numeric id.
- post_processor TemplateProcessing: require a string SpecialToken.id and a numeric
special_tokens ids[0].
- RobertaProcessing/BertProcessing cls/sep: check the [token, id] value kinds before
reading.
- Metaspace pre_tokenizer: require a boolean add_prefix_space and a string replacement.
- Replace normalizer: require an object pattern with a string String/Regex, and wrap
invalid Regex construction as InvalidDataException.
- Read all normalizer/pre_tokenizer/post_processor 'type' fields through a shared
GetStringOrNull helper so a non-string type is handled in a controlled way rather
than throwing.
Adds tests covering the new validation paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review round 7: validate template special-token id consistency
ResolveTemplateTokenId now verifies that a post_processor special_tokens id maps
back to the referenced token (via added tokens or the vocabulary), mirroring the
RobertaProcessing/BertProcessing affix validation, so an inconsistent tokenizer.json
fails with InvalidDataException instead of emitting an id whose decoded token differs
from the template's token name.
Adds an inconsistent-template-special-token test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review round 8: remove redundant casts in tests
CreateFromTokenizerJson returns SentencePieceTokenizer, so the explicit casts in the
new tests were unnecessary.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review round 9: guard normalizer Build value kinds
- Require a normalizer entry to be a JSON object at the start of Build, so a non-object
entry in a Sequence array fails with InvalidDataException instead of InvalidOperationException.
- Validate the Precompiled 'precompiled_charsmap' is a string before reading it.
- Validate the Prepend 'prepend' is a string before reading it.
Adds tests for the non-object Sequence entry, non-string Precompiled charsmap, and
non-string Prepend cases.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Tarek review: support null unk_id and reject unknown pre-tokenizers
Two gaps flagged in review, since a full tokenizer.json is broader than the
Unigram model block:
- unk_id: accept a null unk_id (a Unigram model with no unknown token), which HF
permits and the loader previously rejected. It is represented internally as -1,
and the model constructor tolerates that (guards the unk re-insertion and
GetPieceAtIndex). Because out-of-vocabulary input can then only be represented by
byte fallback, a null unk_id without byte_fallback throws NotSupportedException
rather than emitting an invalid id at encode time. A numeric out-of-range unk_id
is still rejected as InvalidDataException.
- pre_tokenizer: reject pre-tokenizer types the loader does not model (Punctuation,
Split, Digits, ByteLevel, BertPreTokenizer, ...) with NotSupportedException,
recursing into a Sequence, instead of silently ignoring and mis-tokenizing,
mirroring the normalizer chain.
The decoder section is still not read; documented and tracked separately.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>1 parent 11ed3fe commit 3f3c746
6 files changed
Lines changed: 3085 additions & 25 deletions
File tree
- src/Microsoft.ML.Tokenizers
- Model
- Normalizer
- test/Microsoft.ML.Tokenizers.Tests
Lines changed: 44 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
62 | 106 | | |
63 | 107 | | |
64 | 108 | | |
| |||
0 commit comments