sentencepiece-rust

Library New

From-scratch pure-Rust SentencePiece inference (encode/decode), differentially verified against the upstream C++/Python implementation.

Checked · Sources

Downloads
8,442
Dependents
3
Stars
1
Forks
0
Contributors
1
Commits
16
Latest release
0.1.1 ·

About

From the project README ·

A from-scratch, pure-Rust port of SentencePiece inference (encode/decode), differentially verified against the upstream C++/Python implementation.

This is not a binding. The existing sentencepiece crate wraps Google's C++ library; this crate reimplements the tokenisation algorithms in Rust from the reference source, the same way the author's other library ports/reimplement their Python originals and verify against them.

It is also independent of the similarly named sentencepiece-rs crate. This code was written from scratch against the upstream C++/Python reference. It is not a copy, fork, or derivative of that crate or of any other Rust port; the shared subject matter is all they have in common.

Training is out of scope. A trained .model is loaded and used to tokenise.

Correctness goal

The point of this crate is to match upstream SentencePiece exactly:

Read the full README
  • Every module is a line-by-line port of the reference file it names in its header, so its behaviour can be checked against the original.
  • Output is verified against the Python sentencepiece oracle over thousands of real-text lines, both encode and decode. See The Rust tokenizer test and Verification breadth. A difference from the oracle is treated as a bug until it turns out to be an equally-optimal tie (the one documented precision fence).

Two things fall out of that:

  • Zero dependencies, so there is nothing to audit but this crate and std.
  • Speed is measured, not assumed (see Speed), and never traded against matching the oracle.

Why

The scary parts of the upstream repo don't apply to inference:

  • The 138k-line normalization_rule.h is compile-time charsmap generation; a trained model embeds its own precompiled_charsmap, which we just replay.
  • The suffix-array trainer, builder, and esaxx are training-only.
  • The .proto is tiny, so a hand-written proto2 reader replaces prost/protoc and keeps the crate dependency-free.

Milestones

VersionScopeVerified against
v0.1BPE segmentation; ASCII / whitespace normalisation; byte-fallback encodePython sentencepiece, ASCII corpora
v0.2Darts charsmap normaliser to full Unicode input+ Unicode corpora (full-width, ligatures, CJK, …)
v0.3 (current)Unigram Viterbi segmentation; byte-fallback and byte-piece decode3 models (BPE, Unigram, BPE+byte_fallback), encode and decode

Inference is feature-complete for both BPE and Unigram models, with faithful decode (byte-piece reassembly, unk surface, dummy-prefix handling). Both directions are verified against the Python oracle.

Verification breadth

Beyond the small committed corpora, the encoders were run over the upstream training corpora (botchan.txt, 4.3k English lines; wagahaiwa_nekodearu.txt, 2.3k Japanese lines) through all three models: 13,264 lines, encode and decode. The result was 13,258 exact, 6 equal-score reorderings, and 0 real mismatches.

The 6 differences are all runs of repeated punctuation (......., long ----) on the Unigram model. Each is a same-multiset reordering of pieces, that is, an equally-optimal Viterbi path with the same total score. This is a documented precision fence: the compiled oracle binary's optimised float arithmetic resolves these exact ties differently than portable f32, and both segmentations are optimal. See the note in src/unigram.rs. The test counts these separately and only fails on a genuine (different-piece) mismatch.

To reproduce the broad run, point gen_oracle.py at a larger corpus instead of the small corpus_*.txt files. For example, use botchan.txt and wagahaiwa_nekodearu.txt from the upstream SentencePiece repo's data/ directory. Those corpora are not vendored here; download them from the original source.

Layout

src/
  proto.rs       hand-written proto2 wire reader (no deps)
  model.rs       ModelProto -> typed struct (pieces, flags, normalizer spec)
  vocab.rs       piece <-> id maps, scores, byte-fallback ids
  normalizer.rs  whitespace/▁ normalisation + Darts charsmap replay
  bpe.rs         BPE merge encoder
  unigram.rs     Unigram Viterbi best-path (+ piece prefix-trie)
  trie.rs        Darts double-array (common_prefix_search / traverse)
  processor.rs   SentencePieceProcessor: load + encode/decode orchestration
examples/inspect.rs   load a model and dump a summary
tests/oracle.rs       differential test vs the Python oracle
oracle/gen_oracle.py  generates oracle/cases.tsv from Python sentencepiece

Each module names the reference file it ports in its header comment, from the upstream google/sentencepiece source. That source is not vendored here. During development it sits as a local clone outside the repo; clone it from the original if you want to follow the ports.

Try it

# in-tree toolchain (see global setup); plain `cargo` works too
cargo run --example inspect -- tests/models/botchan_1000_bpe.model

Verify against the oracle

# reference implementation, used only to produce the oracle
python -m pip install sentencepiece

# For each (model, output) pair, run over the ASCII corpus (>) then the
# Unicode corpus (>>):
#   botchan_1000_bpe.model          -> cases.tsv          (BPE)
#   test_oss_model_unigram.model    -> cases_unigram.tsv  (Unigram)
#   wagahaiwa_2000_bpe_byte.model   -> cases_byte.tsv     (BPE + byte_fallback)
python oracle/gen_oracle.py tests/models/botchan_1000_bpe.model \
    oracle/corpus_ascii.txt > oracle/cases.tsv
python oracle/gen_oracle.py tests/models/botchan_1000_bpe.model \
    oracle/corpus_unicode.txt >> oracle/cases.tsv
# ... likewise for the Unigram and byte-fallback fixtures.

cargo test

The Rust tokenizer test

tests/oracle.rs is a plain cargo test integration test. It is pure Rust, with no Python at run time. For each fixture model it:

  1. loads the .model with SentencePieceProcessor (the same public API a user calls);
  2. reads the generated oracle/cases*.tsv (hex(text)\t<ids>\thex(decode(ids)));
  3. encodes each text and compares the ids to the oracle's, and decodes the oracle's ids and compares the text, so both directions are checked;
  4. classifies any encode difference: a same-multiset reordering counts as an equally-optimal Viterbi tie (accepted, counted separately), and anything else is a genuine mismatch that fails the test.

It runs across all three fixtures (BPE, Unigram, BPE+byte_fallback) and prints a breakdown, for example:

checked 117 cases against the Python oracle: 116 exact, 1 equal-score reorderings, 0 real mismatches

The test self-skips green on a fresh checkout until the oracle/cases*.tsv files exist, so cargo test never fails just because the oracle hasn't been generated yet. The oracle (Python sentencepiece) is only used to produce those files; the test itself, and the crate, need nothing but Rust.

Speed

A rough throughput cross-check: examples/bench.rs against oracle/bench_oracle.py, same per-line workload, release build, single thread (numbers from one machine; run it yourself for yours):

model / corpusencode, Rustencode, Py spdecode, Rustdecode, Py sp
BPE / English6.7 MB/s2.9 MB/s28 MB/s5.9 MB/s
Unigram / English10.0 MB/s4.8 MB/s26 MB/s5.5 MB/s
BPE + byte / Japanese24.5 MB/s6.2 MB/s50 MB/s6.9 MB/s

Read these as a sign that the port runs at a healthy native throughput, not as proof that Rust beats C++. The Python column includes per-call CPython/FFI overhead (the C++ core in batch mode would be faster), and this crate is straightforward single-threaded inference tuned for matching the oracle rather than for speed (for example, the normaliser re-runs common_prefix_search per position). Even so, native Rust encode runs about 2 to 4 times the per-line Python path, and decode about 5 to 7 times, with no native dependency to link.

cargo run --release --example bench -- tests/models/botchan_1000_bpe.model <corpus> 50
python oracle/bench_oracle.py        tests/models/botchan_1000_bpe.model <corpus> 50

License

Apache-2.0, matching upstream SentencePiece. The bundled tests/models/*.model fixtures originate from the SentencePiece repository.

Runs on
GPU support unknown
What shows it
  • no dependency or feature matches a backend row
Native code
None found
  • No native code found
Python bindings
No Python binding crate found
License
Apache-2.0

Compare with similar projects

Order: strongest tier first, then most downloads, then name, with unmeasured values last. A library counts downloads over 30 days. An app counts binary downloads per release. An app with none counts its crate downloads over 30 days. ML Rust runs this catalogue, so its closest project always gets a row.

sentencepiece-rust beside the projects that share a topic with it
ProjectDownloadsDependentsStarsLatest releaseRuns on (GPU)Native codeLicense
sentencepiece-rust This page New

From-scratch pure-Rust SentencePiece inference (encode/decode), differentially verified against the upstream C++/Python implementation.

8,442 downloads over 30 days 3 1 0.1.1 GPU support unknown No native code found Apache-2.0
text-splitter Featured

Split text into semantic chunks, up to a desired chunk size. Supports calculating length by characters and tokens, and is callable from Rust and Python.

271,174 downloads over 30 days 56 645 0.33.1 GPU support unknown Binds native code: tree-sitter (feature code), tree-sitter-language (feature code) MIT

A high-performance pure-Rust implementation of OpenAI's tiktoken BPE tokenizer

25,714 downloads over 30 days 12 8 4.1.2 GPU support unknown No native code found Apache-2.0 OR MIT
splintr Listed

Tokenizer for Rust and Python: pure Rust by default, with 18 bundled vocabularies and four backends covering BPE, SentencePiece and WordPiece. Its output matches Hugging Face tokenizers id for id on 14 model families, including GPT-2, Qwen, Whisper, T5 and BERT.

Python bindings

8,204 downloads over 30 days 7 63 0.22.2 GPU support unknown Binds native code: pcre2-sys (feature pcre2) MIT
shimmytok Listed

Pure Rust tokenizer for GGUF models with llama.cpp compatibility (SentencePiece + BPE + WPM + UGM + RWKV)

914 downloads over 30 days 2 26 0.8.5 GPU support unknown No native code found Apache-2.0 OR MIT
rustberta-snli App Archived

🦀 A Rust implementation of a RoBERTa classification model for the SNLI dataset

Not measured Not applicable 13 Not measured Not measured Not measured None stated
Why NewIts first release on 24 Jun 2026 is under 180 days old.

The check tests the Featured gates once the first release passes this age limit.

What else the check found

  • The last activity is on 25 Jun 2026, 137 days ago. The rules expect an update within 365 days. Dormancy rule

Exact rules: New. The check on applied thresholds version 9.

Every rule outcome, as the check records it
  • expected-interval: version 0.1.1 base 300, capped at 2 releases = 100, raised to the 365-day floor: E = 365 days
  • dormancy: last activity 2026-06-25, 137 days ago, E = 365: active
  • new: new: first release 2026-06-24 is under 180 days old

Measurements and sources

Each signal row shows the raw value, its source and the fetch date. The Sources list gathers every distinct source on this page. Movement needs more history.

Sources6 sources
Adoption2 active dependents, 2 new dependents in 365 days1 not measured
Adoption measurements
SignalValueSourceFetchedCounts
Active dependents2crates.io database dumpYes
New dependents (365 days)2crates.io database dumpYes
Peak active dependents2crates.io database dumpYes
Active dependents as a share of the peak1crates.io database dumpYes
Binary downloads per releaseNot measuredYes
MaintenanceLatest human commit: 4 months ago3 not measured
Maintenance measurements
SignalValueSourceFetchedCounts
Latest human commit2026-06-25GitHub repositoryYes
Latest release2026-06-25crates.io database dumpYes
First release2026-06-24crates.io database dumpYes
Latest version0.1.1crates.io database dumpYes
Days the oldest outside issue waits for a reply0GitHub repositoryYes
Maintainer replies over 365 days0GitHub repositoryYes
Repository archivedfalseGitHub repositoryYes
Repository gonefalseGitHub repositoryYes
Release commit or manifest in the repotruecrates.io packageYes
Published docstruecrates.io database dumpYes
Latest release fails cargo checkNot measuredYes
Build check dateNot measuredYes
Build check toolchainNot measuredYes
SecurityUnpatched vulnerabilities: none
Security measurements
SignalValueSourceFetchedCounts
Unpatched vulnerabilities past the grace windownoneRustSec advisory databaseYes
RustSec unmaintained advisorynoneRustSec advisory databaseYes
Deprecation noticenonecrates.io database dumpYes
Deprecated requirementnonecrates.io database dumpYes
Distribution2 releases, 8,442 downloads over 30 days5 not measured
Distribution measurements
SignalValueSourceFetchedCounts
Releases2crates.io database dumpYes
Downloads over 30 days8442crates.io database dumpYes
Binary downloads over the last 3 releasesNot measuredYes
Homebrew installs on request over 365 daysNot measuredYes
Repology repository familiesNot measuredYes
Docker pull growth over 90 daysNot measuredYes
Docker pulls at the growth baselineNot measuredYes
Every version a prereleasefalsecrates.io database dumpYes
Every version 0.0.xfalsecrates.io database dumpYes
Every version yankedfalsecrates.io database dumpYes
Crate deleted from crates.iofalsecrates.io database dumpYes
Repository1 stars, 0 forks, 1 contributors, 16 commits. Stars alone never qualify a project.
Repository measurements
SignalValueSourceFetchedCounts
GitHub stars1GitHub repositoryYes — one part of the repository route
GitHub forks0GitHub repositoryYes — one part of the repository route
Contributors with a GitHub account1GitHub contributorsYes — one part of the repository route
Commits on the default branch16GitHub repositoryNo
Repository's main crate left out of the entrynonecrates.io database dumpYes
Facts that never count
Facts that never count measurements
SignalValueSourceFetchedCounts
LicenseApache-2.0crates.io database dumpNo
Runs on, from listed crates and their direct dependenciesGPU support unknowncrates.io database dumpNo
What shows each backendno dependency or feature matches a backend rowcrates.io database dumpNo
Native code, from listed crates and their dependenciesNo native code foundcrates.io database dumpNo
Python bindings, from direct dependencies of listed cratesNo Python binding crate foundcrates.io database dumpNo
Crates that depend on a listed crate directly3crates.io database dumpNo

Corrections and review history

Report a correction

No correction applies to this entry.

Judgments

A judgment enters only through an approved issue and a merged pull request.

No judgment applies to this entry.